Merge "Add new ImsException to better handle ImsService errors"
diff --git a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
index a7a81f2..767434d 100644
--- a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
+++ b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
@@ -91,7 +91,7 @@
     private static ConversationActions.Request createConversationActionsRequest(CharSequence text) {
         ConversationActions.Message message =
                 new ConversationActions.Message.Builder(
-                        ConversationActions.Message.PERSON_USER_REMOTE)
+                        ConversationActions.Message.PERSON_USER_OTHERS)
                         .setText(text)
                         .build();
         return new ConversationActions.Request.Builder(Collections.singletonList(message))
diff --git a/api/current.txt b/api/current.txt
index c0a9c48..bb6dbeb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5466,9 +5466,11 @@
 
   public static final class Notification.BubbleMetadata implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean getAutoExpandBubble();
     method public int getDesiredHeight();
     method public android.graphics.drawable.Icon getIcon();
     method public android.app.PendingIntent getIntent();
+    method public boolean getSuppressInitialNotification();
     method public CharSequence getTitle();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.Notification.BubbleMetadata> CREATOR;
@@ -5477,9 +5479,11 @@
   public static class Notification.BubbleMetadata.Builder {
     ctor public Notification.BubbleMetadata.Builder();
     method public android.app.Notification.BubbleMetadata build();
+    method public android.app.Notification.BubbleMetadata.Builder setAutoExpandBubble(boolean);
     method public android.app.Notification.BubbleMetadata.Builder setDesiredHeight(int);
     method public android.app.Notification.BubbleMetadata.Builder setIcon(android.graphics.drawable.Icon);
     method public android.app.Notification.BubbleMetadata.Builder setIntent(android.app.PendingIntent);
+    method public android.app.Notification.BubbleMetadata.Builder setSuppressInitialNotification(boolean);
     method public android.app.Notification.BubbleMetadata.Builder setTitle(CharSequence);
   }
 
@@ -6600,7 +6604,6 @@
     method @Nullable public String[] getAccountTypesWithManagementDisabled();
     method @Nullable public java.util.List<android.content.ComponentName> getActiveAdmins();
     method @NonNull public java.util.Set<java.lang.String> getAffiliationIds(@NonNull android.content.ComponentName);
-    method public java.util.List<java.lang.String> getAlwaysOnVpnLockdownWhitelist(@NonNull android.content.ComponentName);
     method @Nullable public String getAlwaysOnVpnPackage(@NonNull android.content.ComponentName);
     method @WorkerThread @NonNull public android.os.Bundle getApplicationRestrictions(@Nullable android.content.ComponentName, String);
     method @Deprecated @Nullable public String getApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName);
@@ -6675,7 +6678,6 @@
     method public boolean isActivePasswordSufficient();
     method public boolean isAdminActive(@NonNull android.content.ComponentName);
     method public boolean isAffiliatedUser();
-    method public boolean isAlwaysOnVpnLockdownEnabled(@NonNull android.content.ComponentName);
     method public boolean isApplicationHidden(@NonNull android.content.ComponentName, String);
     method public boolean isBackupServiceEnabled(@NonNull android.content.ComponentName);
     method @Deprecated public boolean isCallerApplicationRestrictionsManagingPackage();
@@ -6713,7 +6715,6 @@
     method public void setAccountManagementDisabled(@NonNull android.content.ComponentName, String, boolean);
     method public void setAffiliationIds(@NonNull android.content.ComponentName, @NonNull java.util.Set<java.lang.String>);
     method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
-    method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean, @Nullable java.util.List<java.lang.String>) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
     method public boolean setApplicationHidden(@NonNull android.content.ComponentName, String, boolean);
     method @WorkerThread public void setApplicationRestrictions(@Nullable android.content.ComponentName, String, android.os.Bundle);
     method @Deprecated public void setApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName, @Nullable String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -11434,6 +11435,7 @@
     method public int getSessionId();
     method public long getSize();
     method public int getStagedSessionErrorCode();
+    method public String getStagedSessionErrorMessage();
     method public boolean isActive();
     method public boolean isMultiPackage();
     method public boolean isSealed();
@@ -25951,7 +25953,6 @@
     method @NonNull public abstract android.media.MediaSession2 onGetPrimarySession();
     method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2);
     method public final void removeSession(@NonNull android.media.MediaSession2);
-    field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
   }
 
   public static class MediaSession2Service.MediaNotification {
@@ -27474,6 +27475,7 @@
     method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens();
     method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo);
     method public void notifySession2Created(@NonNull android.media.Session2Token);
+    method public void notifySession2Destroyed(@NonNull android.media.Session2Token);
     method public void removeOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener);
     method public void removeOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener);
   }
@@ -29843,7 +29845,7 @@
     method @Deprecated public boolean disableNetwork(int);
     method @Deprecated public boolean disconnect();
     method @Deprecated public boolean enableNetwork(int, boolean);
-    method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
+    method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
     method public android.net.wifi.WifiInfo getConnectionInfo();
     method public android.net.DhcpInfo getDhcpInfo();
     method public int getMaxNumberOfNetworkSuggestionsPerApp();
@@ -30334,26 +30336,26 @@
   }
 
   public class WifiP2pManager {
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void addServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void cancelConnect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void clearLocalServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void clearServiceRequests(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public android.net.wifi.p2p.WifiP2pManager.Channel initialize(android.content.Context, android.os.Looper, android.net.wifi.p2p.WifiP2pManager.ChannelListener);
     method public void removeGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void removeLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void removeServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void requestConnectionInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener);
     method public void requestDiscoveryState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.DiscoveryStateListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener);
     method public void requestNetworkInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.NetworkInfoListener);
     method public void requestP2pState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.P2pStateListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener);
     method public void setDnsSdResponseListeners(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.DnsSdServiceResponseListener, android.net.wifi.p2p.WifiP2pManager.DnsSdTxtRecordListener);
     method public void setServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ServiceResponseListener);
     method public void setUpnpServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.UpnpServiceResponseListener);
@@ -38747,6 +38749,7 @@
 
   public static final class Settings.Panel {
     field public static final String ACTION_INTERNET_CONNECTIVITY = "android.settings.panel.action.INTERNET_CONNECTIVITY";
+    field public static final String ACTION_NFC = "android.settings.panel.action.NFC";
     field public static final String ACTION_VOLUME = "android.settings.panel.action.VOLUME";
   }
 
@@ -53343,8 +53346,8 @@
     method @Nullable public CharSequence getText();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR;
-    field public static final android.app.Person PERSON_USER_LOCAL;
-    field public static final android.app.Person PERSON_USER_REMOTE;
+    field public static final android.app.Person PERSON_USER_OTHERS;
+    field public static final android.app.Person PERSON_USER_SELF;
   }
 
   public static final class ConversationActions.Message.Builder {
@@ -62383,20 +62386,20 @@
     method public abstract Object array();
     method public abstract int arrayOffset();
     method public final int capacity();
-    method public final java.nio.Buffer clear();
-    method public final java.nio.Buffer flip();
+    method public java.nio.Buffer clear();
+    method public java.nio.Buffer flip();
     method public abstract boolean hasArray();
     method public final boolean hasRemaining();
     method public abstract boolean isDirect();
     method public abstract boolean isReadOnly();
     method public final int limit();
-    method public final java.nio.Buffer limit(int);
-    method public final java.nio.Buffer mark();
+    method public java.nio.Buffer limit(int);
+    method public java.nio.Buffer mark();
     method public final int position();
-    method public final java.nio.Buffer position(int);
+    method public java.nio.Buffer position(int);
     method public final int remaining();
-    method public final java.nio.Buffer reset();
-    method public final java.nio.Buffer rewind();
+    method public java.nio.Buffer reset();
+    method public java.nio.Buffer rewind();
   }
 
   public class BufferOverflowException extends java.lang.RuntimeException {
diff --git a/api/system-current.txt b/api/system-current.txt
index 8e18f04..b885269 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -883,8 +883,8 @@
 
   public final class ClassificationsRequest implements android.os.Parcelable {
     method public int describeContents();
-    method public android.os.Bundle getExtras();
-    method public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections();
+    method @Nullable public android.os.Bundle getExtras();
+    method @NonNull public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ClassificationsRequest> CREATOR;
   }
@@ -898,8 +898,8 @@
   public final class ContentClassification implements android.os.Parcelable {
     ctor public ContentClassification(@NonNull String, @NonNull android.os.Bundle);
     method public int describeContents();
-    method public android.os.Bundle getExtras();
-    method public String getId();
+    method @NonNull public android.os.Bundle getExtras();
+    method @NonNull public String getId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentClassification> CREATOR;
   }
@@ -907,8 +907,8 @@
   public final class ContentSelection implements android.os.Parcelable {
     ctor public ContentSelection(@NonNull String, @NonNull android.os.Bundle);
     method public int describeContents();
-    method public android.os.Bundle getExtras();
-    method public String getId();
+    method @NonNull public android.os.Bundle getExtras();
+    method @NonNull public String getId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentSelection> CREATOR;
   }
@@ -930,8 +930,8 @@
 
   public final class SelectionsRequest implements android.os.Parcelable {
     method public int describeContents();
-    method public android.os.Bundle getExtras();
-    method public android.graphics.Point getInterestPoint();
+    method @Nullable public android.os.Bundle getExtras();
+    method @Nullable public android.graphics.Point getInterestPoint();
     method public int getTaskId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.SelectionsRequest> CREATOR;
@@ -1853,9 +1853,15 @@
   public final class HdmiControlManager {
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void addHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
     method @Nullable public android.hardware.hdmi.HdmiClient getClient(int);
+    method @Nullable public java.util.List<android.hardware.hdmi.HdmiDeviceInfo> getConnectedDevicesList();
+    method public int getPhysicalAddress();
     method @Nullable public android.hardware.hdmi.HdmiPlaybackClient getPlaybackClient();
+    method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient();
     method @Nullable public android.hardware.hdmi.HdmiTvClient getTvClient();
+    method public boolean isRemoteDeviceConnected(android.hardware.hdmi.HdmiDeviceInfo);
+    method public void powerOffRemoteDevice(android.hardware.hdmi.HdmiDeviceInfo);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void removeHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
+    method public void requestRemoteDeviceToBecomeActiveSource(android.hardware.hdmi.HdmiDeviceInfo);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setStandbyMode(boolean);
     field public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE";
     field public static final int AVR_VOLUME_MUTED = 101; // 0x65
@@ -1945,6 +1951,9 @@
     field public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 10; // 0xa
   }
 
+  @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface HdmiControlManager.ControlCallbackResult {
+  }
+
   public static interface HdmiControlManager.HotplugEventListener {
     method public void onReceived(android.hardware.hdmi.HdmiHotplugEvent);
   }
@@ -2071,6 +2080,16 @@
   public abstract static class HdmiRecordSources.RecordSource {
   }
 
+  public class HdmiSwitchClient extends android.hardware.hdmi.HdmiClient {
+    method public int getDeviceType();
+    method public void selectPort(int, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener);
+    method public void selectPort(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener);
+  }
+
+  public static interface HdmiSwitchClient.OnSelectListener {
+    method public void onSelect(@android.hardware.hdmi.HdmiControlManager.ControlCallbackResult int);
+  }
+
   public class HdmiTimerRecordSources {
     method public static boolean checkTimerRecordSource(int, byte[]);
     method public static android.hardware.hdmi.HdmiTimerRecordSources.Duration durationOf(int, int);
@@ -3395,6 +3414,15 @@
     method public void stop();
   }
 
+  public final class Session2Token implements android.os.Parcelable {
+    ctor public Session2Token(@NonNull android.content.Context, @NonNull String, @Nullable android.os.Bundle);
+    method public void destroy();
+    method @NonNull public android.os.Bundle getExtras();
+    method public int getPid();
+    method public boolean isDestroyed();
+    field public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service";
+  }
+
   public static class SubtitleData.Builder {
     ctor public SubtitleData.Builder();
     ctor public SubtitleData.Builder(@NonNull android.media.SubtitleData);
@@ -4659,14 +4687,13 @@
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,android.net.wifi.hotspot2.PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(@NonNull java.util.Set<android.net.wifi.hotspot2.OsuProvider>);
-    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState();
     method public boolean isDeviceToDeviceRttSupported();
     method public boolean isPortableHotspotSupported();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled();
     method public boolean isWifiScannerSupported();
-    method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void registerNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback, @Nullable android.os.Handler);
     method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void removeWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.WifiUsabilityStatsListener);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void save(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission("android.permission.WIFI_SET_DEVICE_MOBILITY_STATE") public void setDeviceMobilityState(int);
@@ -4676,7 +4703,6 @@
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public boolean startScan(android.os.WorkSource);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(android.net.wifi.hotspot2.OsuProvider, android.net.wifi.hotspot2.ProvisioningCallback, @Nullable android.os.Handler);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession();
-    method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void unregisterNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback);
     method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void updateWifiUsabilityScore(int, int, int);
     field public static final int CHANGE_REASON_ADDED = 0; // 0x0
     field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2
@@ -4713,19 +4739,6 @@
     method public void onSuccess();
   }
 
-  public static interface WifiManager.NetworkRequestMatchCallback {
-    method public void onAbort();
-    method public void onMatch(@NonNull java.util.List<android.net.wifi.ScanResult>);
-    method public void onUserSelectionCallbackRegistration(@NonNull android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback);
-    method public void onUserSelectionConnectFailure(@NonNull android.net.wifi.WifiConfiguration);
-    method public void onUserSelectionConnectSuccess(@NonNull android.net.wifi.WifiConfiguration);
-  }
-
-  public static interface WifiManager.NetworkRequestUserSelectionCallback {
-    method public void reject();
-    method public void select(@NonNull android.net.wifi.WifiConfiguration);
-  }
-
   public static interface WifiManager.WifiUsabilityStatsListener {
     method public void onStatsUpdated(int, boolean, android.net.wifi.WifiUsabilityStatsEntry);
   }
@@ -5474,8 +5487,9 @@
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract int onCountPermissionApps(@NonNull java.util.List<java.lang.String>, boolean, boolean);
     method @NonNull public abstract java.util.List<android.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(@NonNull String);
+    method @NonNull public abstract java.util.List<android.permission.RuntimePermissionUsageInfo> onGetPermissionUsages(boolean, long);
     method public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream);
-    method @NonNull public abstract java.util.List<android.permission.RuntimePermissionUsageInfo> onPermissionUsageResult(boolean, long);
+    method public abstract boolean onIsApplicationQualifiedForRole(@NonNull String, @NonNull String);
     method public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String);
     method @NonNull public abstract java.util.Map<java.lang.String,java.util.List<java.lang.String>> onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String);
     field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
@@ -5667,6 +5681,11 @@
     method public void onPropertyChanged(String, String, String);
   }
 
+  public static interface DeviceConfig.Storage {
+    field public static final String ISOLATED_STORAGE_ENABLED = "isolated_storage_enabled";
+    field public static final String NAMESPACE = "storage";
+  }
+
   public static interface DeviceConfig.Telephony {
     field public static final String NAMESPACE = "telephony";
     field public static final String PROPERTY_ENABLE_RAMPING_RINGER = "enable_ramping_ringer";
diff --git a/api/test-current.txt b/api/test-current.txt
index 8e638fd..0f2ba12 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -11,6 +11,7 @@
     field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
+    field public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
     field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
   }
 
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 8fb01b4..d6b4737f 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -2492,24 +2492,32 @@
 
 /*
  * Logs when a binary push state changes.
- * Logged in Play store
+ * Logged by the installer via public api.
  */
 message BinaryPushStateChanged {
-    // Binary package name.
-    optional string binary_name = 1;
-    // Version code.
-    optional int64 version = 2;
-    // State
+    // Name of the train.
+    optional string train_name = 1;
+    // Version code for a "train" of packages that need to be installed atomically
+    optional int64 train_version_code = 2;
+    // After installation of this package, device requires a restart.
+    optional bool requires_staging = 3;
+    // Rollback should be enabled for this install.
+    optional bool rollback_enabled = 4;
+    // Requires low latency monitoring if possible.
+    optional bool requires_low_latency_monitor = 5;
+
     enum State {
-        STATE_UNKNOWN = 0;
-        DOWNLOAD_START = 1;
-        DOWNLOAD_DONE = 2;
-        INSTALL_START = 3;
-        INSTALL_DONE = 4;
-        REBOOT_START = 5;
-        REBOOT_DONE = 6;
+        UNKNOWN = 0;
+        INSTALL_REQUESTED = 1;
+        INSTALL_STARTED = 2;
+        INSTALL_STAGED_NOT_READY = 3;
+        INSTALL_STAGED_READY = 4;
+        INSTALL_SUCCESS = 5;
+        INSTALL_FAILURE = 6;
+        INSTALL_CANCELLED = 7;
+        INSTALLER_ROLLBACK_REQUESTED = 8;
     }
-    optional State state = 3;
+    optional State state = 6;
 }
 
 /** Represents USB port overheat event. */
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ab2430c..364d3c9 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -39,6 +39,7 @@
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.ArrayMap;
@@ -1712,6 +1713,12 @@
     /** @hide */
     public static final String KEY_HISTORICAL_OPS = "historical_ops";
 
+    /** System properties for debug logging of noteOp call sites */
+    private static final String DEBUG_LOGGING_ENABLE_PROP = "appops.logging_enabled";
+    private static final String DEBUG_LOGGING_PACKAGES_PROP = "appops.logging_packages";
+    private static final String DEBUG_LOGGING_OPS_PROP = "appops.logging_ops";
+    private static final String DEBUG_LOGGING_TAG = "AppOpsManager";
+
     /**
      * Retrieve the op switch that controls the given operation.
      * @hide
@@ -4469,6 +4476,7 @@
      */
     @UnsupportedAppUsage
     public int noteOpNoThrow(int op, int uid, String packageName) {
+        logNoteOpIfNeeded(op, packageName);
         try {
             return mService.noteOperation(op, uid, packageName);
         } catch (RemoteException e) {
@@ -4834,4 +4842,45 @@
 
         return AppOpsManager.MODE_DEFAULT;
     }
+
+    private static void logNoteOpIfNeeded(int op, String callingPackage) {
+        // Check if debug logging propety is enabled.
+        if (!SystemProperties.getBoolean(DEBUG_LOGGING_ENABLE_PROP, false)) {
+            return;
+        }
+        // Check if this package should be logged.
+        String packages = SystemProperties.get(DEBUG_LOGGING_PACKAGES_PROP, "");
+        if (!"".equals(packages) && callingPackage != null) {
+            boolean found = false;
+            for (String pkg : packages.split(",")) {
+                if (callingPackage.equals(pkg)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return;
+            }
+        }
+        String opStr = opToName(op);
+        // Check if this app op should be logged
+        String logOps = SystemProperties.get(DEBUG_LOGGING_OPS_PROP, "");
+        if (!"".equals(logOps)) {
+            boolean found = false;
+            for (String logOp : logOps.split(",")) {
+                if (opStr.equals(logOp)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return;
+            }
+        }
+
+        // Log a stack trace
+        Exception here = new Exception("HERE!");
+        android.util.Log.i(DEBUG_LOGGING_TAG, "Note operation package= " + callingPackage
+                + " op= " + opStr, here);
+    }
 }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index cc6c999..db6ad3d 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -434,6 +434,11 @@
     void registerRemoteAnimationForNextActivityStart(in String packageName,
            in RemoteAnimationAdapter adapter);
 
+    /**
+     * Registers remote animations for a display.
+     */
+    void registerRemoteAnimationsForDisplay(int displayId, in RemoteAnimationDefinition definition);
+
     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
     void alwaysShowUnsupportedCompileSdkWarning(in ComponentName activity);
 
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index b8d748d..7c550d4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -84,7 +84,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ContrastColorUtil;
-import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -5257,7 +5256,11 @@
          * @hide
          */
         public RemoteViews makeAmbientNotification() {
-            return createHeadsUpContentView(false /* increasedHeight */);
+            RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */);
+            if (headsUpContentView != null) {
+                return headsUpContentView;
+            }
+            return createContentView();
         }
 
         private void hideLine1Text(RemoteViews result) {
@@ -8399,6 +8402,30 @@
         private CharSequence mTitle;
         private Icon mIcon;
         private int mDesiredHeight;
+        private int mFlags;
+
+        /**
+         * If set and the app creating the bubble is in the foreground, the bubble will be posted
+         * in its expanded state, with the contents of {@link #getIntent()} in a floating window.
+         *
+         * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p>
+         *
+         * <p>Generally this flag should only be set if the user has performed an action to request
+         * or create a bubble.</p>
+         */
+        private static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
+
+        /**
+         * If set and the app creating the bubble is in the foreground, the bubble will be posted
+         * <b>without</b> the associated notification in the notification shade. Subsequent update
+         * notifications to this bubble will post a notification in the shade.
+         *
+         * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p>
+         *
+         * <p>Generally this flag should only be set if the user has performed an action to request
+         * or create a bubble.</p>
+         */
+        private static final int FLAG_SUPPRESS_INITIAL_NOTIFICATION = 0x00000002;
 
         private BubbleMetadata(PendingIntent intent, CharSequence title, Icon icon, int height) {
             mPendingIntent = intent;
@@ -8412,6 +8439,7 @@
             mTitle = in.readCharSequence();
             mIcon = Icon.CREATOR.createFromParcel(in);
             mDesiredHeight = in.readInt();
+            mFlags = in.readInt();
         }
 
         /**
@@ -8444,6 +8472,24 @@
             return mDesiredHeight;
         }
 
+        /**
+         * @return whether this bubble should auto expand when it is posted.
+         *
+         * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean)
+         */
+        public boolean getAutoExpandBubble() {
+            return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0;
+        }
+
+        /**
+         * @return whether this bubble should suppress the initial notification when it is posted.
+         *
+         * @see BubbleMetadata.Builder#setSuppressInitialNotification(boolean)
+         */
+        public boolean getSuppressInitialNotification() {
+            return (mFlags & FLAG_SUPPRESS_INITIAL_NOTIFICATION) != 0;
+        }
+
         public static final Parcelable.Creator<BubbleMetadata> CREATOR =
                 new Parcelable.Creator<BubbleMetadata>() {
 
@@ -8469,6 +8515,11 @@
             out.writeCharSequence(mTitle);
             mIcon.writeToParcel(out, 0);
             out.writeInt(mDesiredHeight);
+            out.writeInt(mFlags);
+        }
+
+        private void setFlags(int flags) {
+            mFlags = flags;
         }
 
         /**
@@ -8480,6 +8531,7 @@
             private CharSequence mTitle;
             private Icon mIcon;
             private int mDesiredHeight;
+            private int mFlags;
 
             /**
              * Constructs a new builder object.
@@ -8539,6 +8591,39 @@
             }
 
             /**
+             * If set and the app creating the bubble is in the foreground, the bubble will be
+             * posted in its expanded state, with the contents of {@link #getIntent()} in a
+             * floating window.
+             *
+             * <p>If the app creating the bubble is not in the foreground this flag has no effect.
+             * </p>
+             *
+             * <p>Generally this flag should only be set if the user has performed an action to
+             * request or create a bubble.</p>
+             */
+            public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) {
+                setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand);
+                return this;
+            }
+
+            /**
+             * If set and the app creating the bubble is in the foreground, the bubble will be
+             * posted <b>without</b> the associated notification in the notification shade.
+             * Subsequent update notifications to this bubble will post a notification in the shade.
+             *
+             * <p>If the app creating the bubble is not in the foreground this flag has no effect.
+             * </p>
+             *
+             * <p>Generally this flag should only be set if the user has performed an action to
+             * request or create a bubble.</p>
+             */
+            public BubbleMetadata.Builder setSuppressInitialNotification(
+                    boolean shouldSupressNotif) {
+                setFlag(FLAG_SUPPRESS_INITIAL_NOTIFICATION, shouldSupressNotif);
+                return this;
+            }
+
+            /**
              * Creates the {@link BubbleMetadata} defined by this builder.
              * <p>Will throw {@link IllegalStateException} if required fields have not been set
              * on this builder.</p>
@@ -8553,7 +8638,22 @@
                 if (mIcon == null) {
                     throw new IllegalStateException("Must supply an icon for the bubble");
                 }
-                return new BubbleMetadata(mPendingIntent, mTitle, mIcon, mDesiredHeight);
+                BubbleMetadata data = new BubbleMetadata(mPendingIntent, mTitle, mIcon,
+                        mDesiredHeight);
+                data.setFlags(mFlags);
+                return data;
+            }
+
+            /**
+             * @hide
+             */
+            public BubbleMetadata.Builder setFlag(int mask, boolean value) {
+                if (value) {
+                    mFlags |= mask;
+                } else {
+                    mFlags &= ~mask;
+                }
+                return this;
             }
         }
     }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 8ca3544..2514eee 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5059,16 +5059,11 @@
     }
 
     /**
-     * Service-specific error code used in implementation of {@code setAlwaysOnVpnPackage} methods.
-     * @hide
-     */
-    public static final int ERROR_VPN_PACKAGE_NOT_FOUND = 1;
-
-    /**
      * Called by a device or profile owner to configure an always-on VPN connection through a
      * specific application for the current user. This connection is automatically granted and
      * persisted after a reboot.
-     * <p> To support the always-on feature, an app must
+     * <p>
+     * To support the always-on feature, an app must
      * <ul>
      *     <li>declare a {@link android.net.VpnService} in its manifest, guarded by
      *         {@link android.Manifest.permission#BIND_VPN_SERVICE};</li>
@@ -5077,13 +5072,12 @@
      *         {@link android.net.VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}.</li>
      * </ul>
      * The call will fail if called with the package name of an unsupported VPN app.
-     * <p> Enabling lockdown via {@code lockdownEnabled} argument carries the risk that any failure
-     * of the VPN provider could break networking for all apps.
      *
      * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} to
      *        remove an existing always-on VPN configuration.
      * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
-     *        {@code false} otherwise. This has no effect when clearing.
+     *        {@code false} otherwise. This carries the risk that any failure of the VPN provider
+     *        could break networking for all apps. This has no effect when clearing.
      * @throws SecurityException if {@code admin} is not a device or a profile owner.
      * @throws NameNotFoundException if {@code vpnPackage} is not installed.
      * @throws UnsupportedOperationException if {@code vpnPackage} exists but does not support being
@@ -5092,46 +5086,11 @@
     public void setAlwaysOnVpnPackage(@NonNull ComponentName admin, @Nullable String vpnPackage,
             boolean lockdownEnabled)
             throws NameNotFoundException, UnsupportedOperationException {
-        setAlwaysOnVpnPackage(admin, vpnPackage, lockdownEnabled, Collections.emptyList());
-    }
-
-    /**
-     * A version of {@link #setAlwaysOnVpnPackage(ComponentName, String, boolean)} that allows the
-     * admin to specify a set of apps that should be able to access the network directly when VPN
-     * is not connected. When VPN connects these apps switch over to VPN if allowed to use that VPN.
-     * System apps can always bypass VPN.
-     * <p> Note that the system doesn't update the whitelist when packages are installed or
-     * uninstalled, the admin app must call this method to keep the list up to date.
-     *
-     * @param vpnPackage package name for an installed VPN app on the device, or {@code null}
-     *         to remove an existing always-on VPN configuration
-     * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
-     *         {@code false} otherwise. This has no effect when clearing.
-     * @param lockdownWhitelist Packages that will be able to access the network directly when VPN
-     *         is in lockdown mode but not connected. Has no effect when clearing.
-     * @throws SecurityException if {@code admin} is not a device or a profile
-     *         owner.
-     * @throws NameNotFoundException if {@code vpnPackage} or one of
-     *         {@code lockdownWhitelist} is not installed.
-     * @throws UnsupportedOperationException if {@code vpnPackage} exists but does
-     *         not support being set as always-on, or if always-on VPN is not
-     *         available.
-     */
-    public void setAlwaysOnVpnPackage(@NonNull ComponentName admin, @Nullable String vpnPackage,
-            boolean lockdownEnabled, @Nullable List<String> lockdownWhitelist)
-            throws NameNotFoundException, UnsupportedOperationException {
         throwIfParentInstance("setAlwaysOnVpnPackage");
         if (mService != null) {
             try {
-                mService.setAlwaysOnVpnPackage(
-                        admin, vpnPackage, lockdownEnabled, lockdownWhitelist);
-            } catch (ServiceSpecificException e) {
-                switch (e.errorCode) {
-                    case ERROR_VPN_PACKAGE_NOT_FOUND:
-                        throw new NameNotFoundException(e.getMessage());
-                    default:
-                        throw new RuntimeException(
-                                "Unknown error setting always-on VPN: " + e.errorCode);
+                if (!mService.setAlwaysOnVpnPackage(admin, vpnPackage, lockdownEnabled)) {
+                    throw new NameNotFoundException(vpnPackage);
                 }
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -5140,51 +5099,6 @@
     }
 
     /**
-     * Called by device or profile owner to query whether current always-on VPN is configured in
-     * lockdown mode. Returns {@code false} when no always-on configuration is set.
-     *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     *
-     * @throws SecurityException if {@code admin} is not a device or a profile owner.
-     *
-     * @see #setAlwaysOnVpnPackage(ComponentName, String, boolean)
-     */
-    public boolean isAlwaysOnVpnLockdownEnabled(@NonNull ComponentName admin) {
-        throwIfParentInstance("isAlwaysOnVpnLockdownEnabled");
-        if (mService != null) {
-            try {
-                return mService.isAlwaysOnVpnLockdownEnabled(admin);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Called by device or profile owner to query the list of packages that are allowed to access
-     * the network directly when always-on VPN is in lockdown mode but not connected. Returns
-     * {@code null} when always-on VPN is not active or not in lockdown mode.
-     *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     *
-     * @throws SecurityException if {@code admin} is not a device or a profile owner.
-     *
-     * @see #setAlwaysOnVpnPackage(ComponentName, String, boolean, List)
-     */
-    public List<String> getAlwaysOnVpnLockdownWhitelist(@NonNull ComponentName admin) {
-        throwIfParentInstance("getAlwaysOnVpnLockdownWhitelist");
-        if (mService != null) {
-            try {
-                return mService.getAlwaysOnVpnLockdownWhitelist(admin);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-        return null;
-    }
-
-    /**
      * Called by a device or profile owner to read the name of the package administering an
      * always-on VPN connection for the current user. If there is no such package, or the always-on
      * VPN is provided by the system instead of by an application, {@code null} will be returned.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 5790fda..1751a91c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -187,10 +187,8 @@
     void setCertInstallerPackage(in ComponentName who, String installerPackage);
     String getCertInstallerPackage(in ComponentName who);
 
-    boolean setAlwaysOnVpnPackage(in ComponentName who, String vpnPackage, boolean lockdown, in List<String> lockdownWhitelist);
+    boolean setAlwaysOnVpnPackage(in ComponentName who, String vpnPackage, boolean lockdown);
     String getAlwaysOnVpnPackage(in ComponentName who);
-    boolean isAlwaysOnVpnLockdownEnabled(in ComponentName who);
-    List<String> getAlwaysOnVpnLockdownWhitelist(in ComponentName who);
 
     void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity);
     void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName);
diff --git a/core/java/android/app/contentsuggestions/ClassificationsRequest.java b/core/java/android/app/contentsuggestions/ClassificationsRequest.java
index 3f71518..9bb39e5 100644
--- a/core/java/android/app/contentsuggestions/ClassificationsRequest.java
+++ b/core/java/android/app/contentsuggestions/ClassificationsRequest.java
@@ -26,6 +26,11 @@
 import java.util.List;
 
 /**
+ * Request object used when asking {@link ContentSuggestionsManager} to classify content selections.
+ *
+ * <p>The request contains a list of {@link ContentSelection} objects to be classified along with
+ * implementation specific extras.
+ *
  * @hide
  */
 @SystemApi
@@ -44,14 +49,14 @@
     /**
      * Return request selections.
      */
-    public List<ContentSelection> getSelections() {
+    public @NonNull List<ContentSelection> getSelections() {
         return mSelections;
     }
 
     /**
-     * Return the request extras.
+     * Return the request extras or {@code null} if there are none.
      */
-    public Bundle getExtras() {
+    public @Nullable Bundle getExtras() {
         return mExtras;
     }
 
diff --git a/core/java/android/app/contentsuggestions/ContentClassification.java b/core/java/android/app/contentsuggestions/ContentClassification.java
index f18520f..2a00b40 100644
--- a/core/java/android/app/contentsuggestions/ContentClassification.java
+++ b/core/java/android/app/contentsuggestions/ContentClassification.java
@@ -23,6 +23,9 @@
 import android.os.Parcelable;
 
 /**
+ * Represents the classification of a content suggestion. The result of a
+ * {@link ClassificationsRequest} to {@link ContentSuggestionsManager}.
+ *
  * @hide
  */
 @SystemApi
@@ -32,6 +35,12 @@
     @NonNull
     private final Bundle mExtras;
 
+    /**
+     * Default constructor.
+     *
+     * @param classificationId implementation specific id for the selection /  classification.
+     * @param extras containing the classification data.
+     */
     public ContentClassification(@NonNull String classificationId, @NonNull Bundle extras) {
         mClassificationId = classificationId;
         mExtras = extras;
@@ -40,14 +49,14 @@
     /**
      * Return the classification id.
      */
-    public String getId() {
+    public @NonNull String getId() {
         return mClassificationId;
     }
 
     /**
      * Return the classification extras.
      */
-    public Bundle getExtras() {
+    public @NonNull Bundle getExtras() {
         return mExtras;
     }
 
diff --git a/core/java/android/app/contentsuggestions/ContentSelection.java b/core/java/android/app/contentsuggestions/ContentSelection.java
index a8917f7..16b4f3f 100644
--- a/core/java/android/app/contentsuggestions/ContentSelection.java
+++ b/core/java/android/app/contentsuggestions/ContentSelection.java
@@ -23,6 +23,9 @@
 import android.os.Parcelable;
 
 /**
+ * Represents a suggested selection within a set of on screen content. The result of a
+ * {@link SelectionsRequest} to {@link ContentSuggestionsManager}.
+ *
  * @hide
  */
 @SystemApi
@@ -32,6 +35,12 @@
     @NonNull
     private final Bundle mExtras;
 
+    /**
+     * Default constructor.
+     *
+     * @param selectionId implementation specific id for the selection.
+     * @param extras containing the data that represents the selection.
+     */
     public ContentSelection(@NonNull String selectionId, @NonNull Bundle extras) {
         mSelectionId = selectionId;
         mExtras = extras;
@@ -40,14 +49,14 @@
     /**
      * Return the selection id.
      */
-    public String getId() {
+    public @NonNull String getId() {
         return mSelectionId;
     }
 
     /**
      * Return the selection extras.
      */
-    public Bundle getExtras() {
+    public @NonNull Bundle getExtras() {
         return mExtras;
     }
 
diff --git a/core/java/android/app/contentsuggestions/SelectionsRequest.java b/core/java/android/app/contentsuggestions/SelectionsRequest.java
index 16f3e6b..e3c8bc5 100644
--- a/core/java/android/app/contentsuggestions/SelectionsRequest.java
+++ b/core/java/android/app/contentsuggestions/SelectionsRequest.java
@@ -25,6 +25,12 @@
 import android.os.Parcelable;
 
 /**
+ * The request object used to request content selections from {@link ContentSuggestionsManager}.
+ *
+ * <p>Selections are requested for a given taskId as specified by
+ * {@link android.app.ActivityManager} and optionally take an interest point that specifies the
+ * point on the screen that should be considered as the most important.
+ *
  * @hide
  */
 @SystemApi
@@ -49,16 +55,17 @@
     }
 
     /**
-     * Return the request point of interest.
+     * Return the request point of interest or {@code null} if there is no point of interest for
+     * this request.
      */
-    public Point getInterestPoint() {
+    public @Nullable Point getInterestPoint() {
         return mInterestPoint;
     }
 
     /**
-     * Return the request extras.
+     * Return the request extras or {@code null} if there aren't any.
      */
-    public Bundle getExtras() {
+    public @Nullable Bundle getExtras() {
         return mExtras;
     }
 
@@ -99,6 +106,11 @@
         private Point mInterestPoint;
         private Bundle mExtras;
 
+        /**
+         * Default constructor.
+         *
+         * @param taskId of the type used by {@link android.app.ActivityManager}
+         */
         public Builder(int taskId) {
             mTaskId = taskId;
         }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index edd765b..9e7aaf6 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -9976,9 +9976,21 @@
     }
 
     /** @hide */
+    public void writeToProto(ProtoOutputStream proto) {
+        // Same input parameters that toString() gives to toShortString().
+        writeToProtoWithoutFieldId(proto, true, true, true, false);
+    }
+
+    /** @hide */
     public void writeToProto(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp,
             boolean extras, boolean clip) {
         long token = proto.start(fieldId);
+        writeToProtoWithoutFieldId(proto, secure, comp, extras, clip);
+        proto.end(token);
+    }
+
+    private void writeToProtoWithoutFieldId(ProtoOutputStream proto, boolean secure, boolean comp,
+            boolean extras, boolean clip) {
         if (mAction != null) {
             proto.write(IntentProto.ACTION, mAction);
         }
@@ -10023,7 +10035,6 @@
         if (mSelector != null) {
             proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip));
         }
-        proto.end(token);
     }
 
     /**
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index c702b16..276853d 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -76,4 +76,6 @@
 
     // System API used by framework's ShareSheet (ChooserActivity)
     ParceledListSlice getShareTargets(String packageName, in IntentFilter filter, int userId);
+
+    boolean hasShareTargets(String packageName, String packageToCheck, int userId);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 73b1f4e..2dc014c 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1710,6 +1710,7 @@
         /** {@hide} */
         public boolean isSessionFailed;
         private int mStagedSessionErrorCode;
+        private String mStagedSessionErrorMessage;
 
         /** {@hide} */
         @UnsupportedAppUsage
@@ -1749,6 +1750,7 @@
             isSessionReady = source.readBoolean();
             isSessionFailed = source.readBoolean();
             mStagedSessionErrorCode = source.readInt();
+            mStagedSessionErrorMessage = source.readString();
         }
 
         /**
@@ -2066,9 +2068,19 @@
             return mStagedSessionErrorCode;
         }
 
+        /**
+         * Text description of the error code returned by {@code getStagedSessionErrorCode}, or
+         * empty string if no error was encountered.
+         */
+        public String getStagedSessionErrorMessage() {
+            return mStagedSessionErrorMessage;
+        }
+
         /** {@hide} */
-        public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode) {
+        public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode,
+                                              String errorMessage) {
             mStagedSessionErrorCode = errorCode;
+            mStagedSessionErrorMessage = errorMessage;
         }
 
         @Override
@@ -2106,6 +2118,7 @@
             dest.writeBoolean(isSessionReady);
             dest.writeBoolean(isSessionFailed);
             dest.writeInt(mStagedSessionErrorCode);
+            dest.writeString(mStagedSessionErrorMessage);
         }
 
         public static final Parcelable.Creator<SessionInfo>
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 4f7acd9..849fd03 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -635,4 +635,21 @@
                     }
                 };
     }
+
+    /**
+     * Used by framework's ShareSheet (ChooserActivity.java) to check if a given package has share
+     * target definitions in it's resources.
+     *
+     * @param packageName Package to check for share targets.
+     * @return True if the package has any share target definitions, False otherwise.
+     * @hide
+     */
+    public boolean hasShareTargets(@NonNull String packageName) {
+        try {
+            return mService.hasShareTargets(mContext.getPackageName(), packageName,
+                    injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index 28e9535..27f0b04 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -23,12 +23,14 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.metrics.LogMaker;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
+import android.provider.Settings.Secure;
 
 import com.android.internal.R;
 import com.android.internal.logging.MetricsLogger;
@@ -123,6 +125,42 @@
     @SystemApi
     public static final int AUTO_MODE_TWILIGHT = 2;
 
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED, COLOR_MODE_AUTOMATIC})
+    public @interface ColorMode {}
+
+    /**
+     * Color mode with natural colors.
+     *
+     * @hide
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_NATURAL = 0;
+    /**
+     * Color mode with boosted colors.
+     *
+     * @hide
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_BOOSTED = 1;
+    /**
+     * Color mode with saturated colors.
+     *
+     * @hide
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_SATURATED = 2;
+    /**
+     * Color mode with automatic colors.
+     *
+     * @hide
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_AUTOMATIC = 3;
+
     private final ColorDisplayManagerInternal mManager;
     private MetricsLogger mMetricsLogger;
 
@@ -286,6 +324,24 @@
     }
 
     /**
+     * Sets the current display color mode.
+     *
+     * @hide
+     */
+    public void setColorMode(int colorMode) {
+        mManager.setColorMode(colorMode);
+    }
+
+    /**
+     * Gets the current display color mode.
+     *
+     * @hide
+     */
+    public int getColorMode() {
+        return mManager.getColorMode();
+    }
+
+    /**
      * Returns whether the device has a wide color gamut display.
      *
      * @hide
@@ -384,6 +440,18 @@
         return mManager.getTransformCapabilities();
     }
 
+    /**
+     * Returns whether accessibility transforms are currently enabled, which determines whether
+     * color modes are currently configurable for this device.
+     *
+     * @hide
+     */
+    public static boolean areAccessibilityTransformsEnabled(Context context) {
+        final ContentResolver cr = context.getContentResolver();
+        return Secure.getInt(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0) == 1
+                || Secure.getInt(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0) == 1;
+    }
+
     private MetricsLogger getMetricsLogger() {
         if (mMetricsLogger == null) {
             mMetricsLogger = new MetricsLogger();
@@ -528,6 +596,22 @@
             }
         }
 
+        int getColorMode() {
+            try {
+                return mCdm.getColorMode();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void setColorMode(int colorMode) {
+            try {
+                mCdm.setColorMode(colorMode);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         int getTransformCapabilities() {
             try {
                 return mCdm.getTransformCapabilities();
diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl
index 1918fd5..30e76cf 100644
--- a/core/java/android/hardware/display/IColorDisplayManager.aidl
+++ b/core/java/android/hardware/display/IColorDisplayManager.aidl
@@ -38,4 +38,7 @@
     boolean setNightDisplayCustomStartTime(in Time time);
     Time getNightDisplayCustomEndTime();
     boolean setNightDisplayCustomEndTime(in Time time);
+
+    int getColorMode();
+    void setColorMode(int colorMode);
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index a98b31a..56020b2 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
@@ -33,6 +34,8 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.List;
 
 /**
@@ -106,9 +109,24 @@
     public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
     public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3;
 
+    @IntDef ({
+        RESULT_SUCCESS,
+        RESULT_TIMEOUT,
+        RESULT_SOURCE_NOT_AVAILABLE,
+        RESULT_TARGET_NOT_AVAILABLE,
+        RESULT_ALREADY_IN_PROGRESS,
+        RESULT_EXCEPTION,
+        RESULT_INCORRECT_MODE,
+        RESULT_COMMUNICATION_FAILED,
+    })
+    public @interface ControlCallbackResult {}
+
+    /** Control operation is successfully handled by the framework. */
     public static final int RESULT_SUCCESS = 0;
     public static final int RESULT_TIMEOUT = 1;
+    /** Source device that the application is using is not available. */
     public static final int RESULT_SOURCE_NOT_AVAILABLE = 2;
+    /** Target device that the application is controlling is not available. */
     public static final int RESULT_TARGET_NOT_AVAILABLE = 3;
 
     @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4;
@@ -394,16 +412,15 @@
     /**
      * Gets an object that represents an HDMI-CEC logical device of type switch on the system.
      *
-     * <p>Used to send HDMI control messages to other devices like TV through HDMI bus. It is also
-     * possible to communicate with other logical devices hosted in the same system if the system is
-     * configured to host more than one type of HDMI-CEC logical devices.
+     * <p>Used to send HDMI control messages to other devices (e.g. TVs) through HDMI bus.
+     * It is also possible to communicate with other logical devices hosted in the same
+     * system if the system is configured to host more than one type of HDMI-CEC logical device.
      *
      * @return {@link HdmiSwitchClient} instance. {@code null} on failure.
-     *
-     * TODO(b/110094868): unhide for Q
      * @hide
      */
     @Nullable
+    @SystemApi
     @SuppressLint("Doclava125")
     public HdmiSwitchClient getSwitchClient() {
         return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
@@ -412,11 +429,15 @@
     /**
      * Get a snapshot of the real-time status of the remote devices.
      *
-     * @return a list of {@link HdmiDeviceInfo} of the devices connected to the current device.
+     * <p>This only applies to devices with multiple HDMI inputs.
      *
-     * TODO(b/110094868): unhide for Q
+     * @return a list of {@link HdmiDeviceInfo} of the connected CEC devices. An empty
+     * list will be returned if there is none.
+     *
      * @hide
      */
+    @SystemApi
+    @Nullable
     public List<HdmiDeviceInfo> getConnectedDevicesList() {
         try {
             return mService.getDeviceList();
@@ -426,14 +447,17 @@
     }
 
     /**
-     * Power off the target device.
+     * Power off the target device by sending CEC commands.
      *
-     * @param deviceInfo HdmiDeviceInfo of the device to be powered off
+     * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
      *
-     * TODO(b/110094868): unhide for Q
+     * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered off.
+     *
      * @hide
      */
+    @SystemApi
     public void powerOffRemoteDevice(HdmiDeviceInfo deviceInfo) {
+        Preconditions.checkNotNull(deviceInfo);
         try {
             mService.powerOffRemoteDevice(
                     deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
@@ -443,14 +467,16 @@
     }
 
     /**
-     * Power on the target device.
+     * Power on the target device by sending CEC commands.
      *
-     * @param deviceInfo HdmiDeviceInfo of the device to be powered on
+     * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
      *
-     * TODO(b/110094868): unhide for Q
+     * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered on.
+     *
      * @hide
      */
     public void powerOnRemoteDevice(HdmiDeviceInfo deviceInfo) {
+        Preconditions.checkNotNull(deviceInfo);
         try {
             mService.powerOnRemoteDevice(
                     deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
@@ -460,14 +486,17 @@
     }
 
     /**
-     * Ask the target device to be the new Active Source.
+     * Request the target device to be the new Active Source by sending CEC commands.
+     *
+     * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
      *
      * @param deviceInfo HdmiDeviceInfo of the target device
      *
-     * TODO(b/110094868): unhide for Q
      * @hide
      */
-    public void askRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) {
+    @SystemApi
+    public void requestRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) {
+        Preconditions.checkNotNull(deviceInfo);
         try {
             mService.askRemoteDeviceToBecomeActiveSource(deviceInfo.getPhysicalAddress());
         } catch (RemoteException e) {
@@ -506,8 +535,13 @@
     /**
      * Get the physical address of the device.
      *
+     * <p>Physical address needs to be automatically adjusted when devices are phyiscally or
+     * electrically added or removed from the device tree. Please see HDMI Specification Version
+     * 1.4b 8.7 Physical Address for more details on the address discovery proccess.
+     *
      * @hide
      */
+    @SystemApi
     public int getPhysicalAddress() {
         if (mPhysicalAddress != INVALID_PHYSICAL_ADDRESS) {
             return mPhysicalAddress;
@@ -521,16 +555,19 @@
     }
 
     /**
-     * Check if the target device is connected to the current device. The
-     * API also returns true if the current device is the target.
+     * Check if the target remote device is connected to the current device.
+     *
+     * <p>The API also returns true if the current device is the target.
      *
      * @param targetDevice {@link HdmiDeviceInfo} of the target device.
-     * @return true if {@code device} is directly or indirectly connected to the
+     * @return true if {@code targetDevice} is directly or indirectly
+     * connected to the current device.
      *
-     * TODO(b/110094868): unhide for Q
      * @hide
      */
-    public boolean isTargetDeviceConnected(HdmiDeviceInfo targetDevice) {
+    @SystemApi
+    public boolean isRemoteDeviceConnected(HdmiDeviceInfo targetDevice) {
+        Preconditions.checkNotNull(targetDevice);
         mPhysicalAddress = getPhysicalAddress();
         if (mPhysicalAddress == INVALID_PHYSICAL_ADDRESS) {
             return false;
diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
index 1ac2973..a036512 100644
--- a/core/java/android/hardware/hdmi/HdmiSwitchClient.java
+++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
@@ -15,24 +15,30 @@
  */
 package android.hardware.hdmi;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.hardware.hdmi.HdmiControlManager.ControlCallbackResult;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
- * HdmiSwitchClient represents HDMI-CEC logical device of type Switch in the Android system which
- * acts as switch.
+ * An {@code HdmiSwitchClient} represents a HDMI-CEC switch device.
  *
- * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH,
- * but it is used by all Android TV devices that have multiple HDMI inputs,
- * even if it is not a "pure" swicth and has another device type like TV or Player.
+ * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH, but it is
+ * used by all Android devices that have multiple HDMI inputs, even if it is not a "pure" swicth
+ * and has another device type like TV or Player.
  *
  * @hide
- * TODO(b/110094868): unhide and add @SystemApi for Q
  */
+@SystemApi
 public class HdmiSwitchClient extends HdmiClient {
 
     private static final String TAG = "HdmiSwitchClient";
@@ -41,17 +47,15 @@
         super(service);
     }
 
-    private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) {
+    private static IHdmiControlCallback getCallbackWrapper(final OnSelectListener listener) {
         return new IHdmiControlCallback.Stub() {
             @Override
             public void onComplete(int result) {
-                callback.onComplete(result);
+                listener.onSelect(result);
             }
         };
     }
 
-    /** @hide */
-    // TODO(b/110094868): unhide for Q
     @Override
     public int getDeviceType() {
         return HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH;
@@ -61,20 +65,17 @@
      * Selects a CEC logical device to be a new active source.
      *
      * @param logicalAddress logical address of the device to select
-     * @param callback callback to get the result with
-     * @throws {@link IllegalArgumentException} if the {@code callback} is null
+     * @param listener listener to get the result with
      *
      * @hide
-     * TODO(b/110094868): unhide and add @SystemApi for Q
      */
-    public void deviceSelect(int logicalAddress, @NonNull SelectCallback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("callback must not be null.");
-        }
+    public void selectDevice(int logicalAddress, @NonNull OnSelectListener listener) {
+        Preconditions.checkNotNull(listener);
         try {
-            mService.deviceSelect(logicalAddress, getCallbackWrapper(callback));
+            mService.deviceSelect(logicalAddress, getCallbackWrapper(listener));
         } catch (RemoteException e) {
             Log.e(TAG, "failed to select device: ", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -82,20 +83,83 @@
      * Selects a HDMI port to be a new route path.
      *
      * @param portId HDMI port to select
-     * @param callback callback to get the result with
-     * @throws {@link IllegalArgumentException} if the {@code callback} is null
+     * @see {@link android.media.tv.TvInputHardwareInfo#getHdmiPortId()}
+     *     to get portId of a specific TV Input.
+     * @param listener listener to get the result with
      *
      * @hide
-     * TODO(b/110094868): unhide and add @SystemApi for Q
      */
-    public void portSelect(int portId, @NonNull SelectCallback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("Callback must not be null");
-        }
+    @SystemApi
+    public void selectPort(int portId, @NonNull OnSelectListener listener) {
+        Preconditions.checkNotNull(listener);
         try {
-            mService.portSelect(portId, getCallbackWrapper(callback));
+            mService.portSelect(portId, getCallbackWrapper(listener));
         } catch (RemoteException e) {
             Log.e(TAG, "failed to select port: ", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Selects a CEC logical device to be a new active source.
+     *
+     * @param logicalAddress logical address of the device to select
+     * @param executor executor to allow the develper to specify the thread upon which the listeners
+     *     will be invoked
+     * @param listener listener to get the result with
+     *
+     * @hide
+     */
+    public void selectDevice(
+            int logicalAddress,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnSelectListener listener) {
+        Preconditions.checkNotNull(listener);
+        try {
+            mService.deviceSelect(logicalAddress,
+                    new IHdmiControlCallback.Stub() {
+                            @Override
+                            public void onComplete(int result) {
+                                Binder.withCleanCallingIdentity(
+                                        () -> executor.execute(() -> listener.onSelect(result)));
+                            }
+                    }
+            );
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to select device: ", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Selects a HDMI port to be a new route path.
+     *
+     * @param portId HDMI port to select
+     * @param executor executor to allow the develper to specify the thread upon which the listeners
+     *     will be invoked
+     * @param listener listener to get the result with
+     *
+     * @hide
+     */
+    @SystemApi
+    public void selectPort(
+            int portId,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnSelectListener listener) {
+        Preconditions.checkNotNull(listener);
+        try {
+            mService.portSelect(portId,
+                    new IHdmiControlCallback.Stub() {
+                            @Override
+                            public void onComplete(int result) {
+                                Binder.withCleanCallingIdentity(
+                                        () -> executor.execute(() -> listener.onSelect(result)));
+                            }
+                    }
+            );
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to select port: ", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -105,10 +169,9 @@
      * <p>This only applies to device with multiple HDMI inputs
      *
      * @return list of {@link HdmiDeviceInfo} for connected CEC devices. Empty list is returned if
-     * there is none.
+     *     there is none.
      *
      * @hide
-     * TODO(b/110094868): unhide and add @SystemApi for Q
      */
     public List<HdmiDeviceInfo> getDeviceList() {
         try {
@@ -120,18 +183,19 @@
     }
 
     /**
-     * Callback interface used to get the result of {@link #deviceSelect} or {@link #portSelect}.
+     * Listener interface used to get the result of {@link #deviceSelect} or {@link #portSelect}.
      *
      * @hide
-     * TODO(b/110094868): unhide and add @SystemApi for Q
      */
-    public interface SelectCallback {
+    @SystemApi
+    public interface OnSelectListener {
 
         /**
          * Called when the operation is finished.
          *
-         * @param result the result value of {@link #deviceSelect} or {@link #portSelect}.
+         * @param result callback result.
+         * @see {@link ControlCallbackResult}
          */
-        void onComplete(int result);
+        void onSelect(@ControlCallbackResult int result);
     }
 }
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 1030694..37b25c8 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -179,19 +179,24 @@
                 return;
             case DO_START_INPUT: {
                 final SomeArgs args = (SomeArgs) msg.obj;
-                final int missingMethods = msg.arg1;
-                final boolean restarting = msg.arg2 != 0;
                 final IBinder startInputToken = (IBinder) args.arg1;
                 final IInputContext inputContext = (IInputContext) args.arg2;
                 final EditorInfo info = (EditorInfo) args.arg3;
                 final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
+                SomeArgs moreArgs = (SomeArgs) args.arg5;
                 final InputConnection ic = inputContext != null
                         ? new InputConnectionWrapper(
-                                mTarget, inputContext, missingMethods, isUnbindIssued) : null;
+                                mTarget, inputContext, moreArgs.argi3, isUnbindIssued)
+                        : null;
                 info.makeCompatible(mTargetSdkVersion);
-                inputMethod.dispatchStartInputWithToken(ic, info, restarting /* restarting */,
-                        startInputToken);
+                inputMethod.dispatchStartInputWithToken(
+                        ic,
+                        info,
+                        moreArgs.argi1 == 1 /* restarting */,
+                        startInputToken,
+                        moreArgs.argi2 == 1 /* shouldPreRenderIme */);
                 args.recycle();
+                moreArgs.recycle();
                 return;
             }
             case DO_CREATE_SESSION: {
@@ -291,14 +296,17 @@
     @Override
     public void startInput(IBinder startInputToken, IInputContext inputContext,
             @InputConnectionInspector.MissingMethodFlags final int missingMethods,
-            EditorInfo attribute, boolean restarting) {
+            EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) {
         if (mIsUnbindIssued == null) {
             Log.e(TAG, "startInput must be called after bindInput.");
             mIsUnbindIssued = new AtomicBoolean();
         }
-        mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOOO(DO_START_INPUT,
-                missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute,
-                mIsUnbindIssued));
+        SomeArgs args = SomeArgs.obtain();
+        args.argi1 = restarting ? 1 : 0;
+        args.argi2 = shouldPreRenderIme ? 1 : 0;
+        args.argi3 = missingMethods;
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(
+                DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args));
     }
 
     @BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 38ddc16..5b3ad77 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -346,6 +346,13 @@
      */
     public static final int IME_VISIBLE = 0x2;
 
+    /**
+     * @hide
+     * The IME is active and ready with views but set invisible.
+     * This flag cannot be combined with {@link #IME_VISIBLE}.
+     */
+    public static final int IME_INVISIBLE = 0x4;
+
     // Min and max values for back disposition.
     private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT;
     private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_ADJUST_NOTHING;
@@ -362,10 +369,19 @@
     View mRootView;
     SoftInputWindow mWindow;
     boolean mInitialized;
-    boolean mWindowCreated;
-    boolean mWindowVisible;
-    boolean mWindowWasVisible;
+    boolean mViewsCreated;
+    // IME views visibility.
+    boolean mDecorViewVisible;
+    boolean mDecorViewWasVisible;
     boolean mInShowWindow;
+    // True if pre-rendering of IME views/window is supported.
+    boolean mCanPreRender;
+    // If IME is pre-rendered.
+    boolean mIsPreRendered;
+    // IME window visibility.
+    // Use (mDecorViewVisible && mWindowVisible) to check if IME is visible to the user.
+    boolean mWindowVisible;
+
     ViewGroup mFullscreenArea;
     FrameLayout mExtractFrame;
     FrameLayout mCandidatesFrame;
@@ -552,15 +568,18 @@
          */
         @MainThread
         @Override
-        public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
+        public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
                 @NonNull EditorInfo editorInfo, boolean restarting,
-                @NonNull IBinder startInputToken) {
+                @NonNull IBinder startInputToken, boolean shouldPreRenderIme) {
             mPrivOps.reportStartInput(startInputToken);
-            // This needs to be dispatched to interface methods rather than doStartInput().
-            // Otherwise IME developers who have overridden those interface methods will lose
-            // notifications.
-            super.dispatchStartInputWithToken(inputConnection, editorInfo, restarting,
-                    startInputToken);
+            mCanPreRender = shouldPreRenderIme;
+            if (DEBUG) Log.v(TAG, "Will Pre-render IME: " + mCanPreRender);
+
+            if (restarting) {
+                restartInput(inputConnection, editorInfo);
+            } else {
+                startInput(inputConnection, editorInfo);
+            }
         }
 
         /**
@@ -570,14 +589,27 @@
         @Override
         public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
             if (DEBUG) Log.v(TAG, "hideSoftInput()");
-            boolean wasVis = isInputViewShown();
-            mShowInputFlags = 0;
-            mShowInputRequested = false;
-            doHideWindow();
+            final boolean wasVisible = mIsPreRendered
+                    ? mDecorViewVisible && mWindowVisible : isInputViewShown();
+            if (mIsPreRendered) {
+                // TODO: notify visibility to insets consumer.
+                if (DEBUG) {
+                    Log.v(TAG, "Making IME window invisible");
+                }
+                setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
+                onPreRenderedWindowVisibilityChanged(false /* setVisible */);
+            } else {
+                mShowInputFlags = 0;
+                mShowInputRequested = false;
+                doHideWindow();
+            }
+            final boolean isVisible = mIsPreRendered
+                    ? mDecorViewVisible && mWindowVisible : isInputViewShown();
+            final boolean visibilityChanged = isVisible != wasVisible;
             if (resultReceiver != null) {
-                resultReceiver.send(wasVis != isInputViewShown()
+                resultReceiver.send(visibilityChanged
                         ? InputMethodManager.RESULT_HIDDEN
-                        : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+                        : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
                                 : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
             }
         }
@@ -589,17 +621,28 @@
         @Override
         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
             if (DEBUG) Log.v(TAG, "showSoftInput()");
-            boolean wasVis = isInputViewShown();
+            final boolean wasVisible = mIsPreRendered
+                    ? mDecorViewVisible && mWindowVisible : isInputViewShown();
             if (dispatchOnShowInputRequested(flags, false)) {
-                showWindow(true);
+                if (mIsPreRendered) {
+                    // TODO: notify visibility to insets consumer.
+                    if (DEBUG) {
+                        Log.v(TAG, "Making IME window visible");
+                    }
+                    onPreRenderedWindowVisibilityChanged(true /* setVisible */);
+                } else {
+                    showWindow(true);
+                }
             }
             // If user uses hard keyboard, IME button should always be shown.
-            setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
-
+            setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
+            final boolean isVisible = mIsPreRendered
+                    ? mDecorViewVisible && mWindowVisible : isInputViewShown();
+            final boolean visibilityChanged = isVisible != wasVisible;
             if (resultReceiver != null) {
-                resultReceiver.send(wasVis != isInputViewShown()
+                resultReceiver.send(visibilityChanged
                         ? InputMethodManager.RESULT_SHOWN
-                        : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+                        : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
                                 : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
             }
         }
@@ -972,7 +1015,7 @@
 
     void initViews() {
         mInitialized = false;
-        mWindowCreated = false;
+        mViewsCreated = false;
         mShowInputRequested = false;
         mShowInputFlags = 0;
 
@@ -1046,7 +1089,7 @@
     }
 
     private void resetStateForNewConfiguration() {
-        boolean visible = mWindowVisible;
+        boolean visible = mDecorViewVisible;
         int showFlags = mShowInputFlags;
         boolean showingInput = mShowInputRequested;
         CompletionInfo[] completions = mCurCompletions;
@@ -1129,7 +1172,7 @@
             return;
         }
         mBackDisposition = disposition;
-        setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
+        setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
     }
 
     /**
@@ -1380,7 +1423,7 @@
             mExtractFrame.setVisibility(View.GONE);
         }
         updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE);
-        if (mWindowWasVisible && mFullscreenArea.getVisibility() != vis) {
+        if (mDecorViewWasVisible && mFullscreenArea.getVisibility() != vis) {
             int animRes = mThemeAttrs.getResourceId(vis == View.VISIBLE
                     ? com.android.internal.R.styleable.InputMethodService_imeExtractEnterAnimation
                     : com.android.internal.R.styleable.InputMethodService_imeExtractExitAnimation,
@@ -1439,7 +1482,7 @@
      */
     public void updateInputViewShown() {
         boolean isShown = mShowInputRequested && onEvaluateInputViewShown();
-        if (mIsInputViewShown != isShown && mWindowVisible) {
+        if (mIsInputViewShown != isShown && mDecorViewVisible) {
             mIsInputViewShown = isShown;
             mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE);
             if (mInputView == null) {
@@ -1458,14 +1501,14 @@
     public boolean isShowInputRequested() {
         return mShowInputRequested;
     }
-    
+
     /**
      * Return whether the soft input view is <em>currently</em> shown to the
      * user.  This is the state that was last determined and
      * applied by {@link #updateInputViewShown()}.
      */
     public boolean isInputViewShown() {
-        return mIsInputViewShown && mWindowVisible;
+        return mCanPreRender ? mWindowVisible : mIsInputViewShown && mDecorViewVisible;
     }
 
     /**
@@ -1499,7 +1542,7 @@
      */
     public void setCandidatesViewShown(boolean shown) {
         updateCandidatesVisibility(shown);
-        if (!mShowInputRequested && mWindowVisible != shown) {
+        if (!mShowInputRequested && mDecorViewVisible != shown) {
             // If we are being asked to show the candidates view while the app
             // has not asked for the input view to be shown, then we need
             // to update whether the window is shown.
@@ -1804,7 +1847,8 @@
     public void showWindow(boolean showInput) {
         if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput
                 + " mShowInputRequested=" + mShowInputRequested
-                + " mWindowCreated=" + mWindowCreated
+                + " mViewsCreated=" + mViewsCreated
+                + " mDecorViewVisible=" + mDecorViewVisible
                 + " mWindowVisible=" + mWindowVisible
                 + " mInputStarted=" + mInputStarted
                 + " mShowInputFlags=" + mShowInputFlags);
@@ -1814,18 +1858,42 @@
             return;
         }
 
-        mWindowWasVisible = mWindowVisible;
+        mDecorViewWasVisible = mDecorViewVisible;
         mInShowWindow = true;
-        showWindowInner(showInput);
-        mWindowWasVisible = true;
+        boolean isPreRenderedAndInvisible = mIsPreRendered && !mWindowVisible;
+        final int previousImeWindowStatus =
+                (mDecorViewVisible ? IME_ACTIVE : 0) | (isInputViewShown()
+                        ? (isPreRenderedAndInvisible ? IME_INVISIBLE : IME_VISIBLE) : 0);
+        startViews(prepareWindow(showInput));
+        final int nextImeWindowStatus = mapToImeWindowStatus();
+        if (previousImeWindowStatus != nextImeWindowStatus) {
+            setImeWindowStatus(nextImeWindowStatus, mBackDisposition);
+        }
+
+        // compute visibility
+        onWindowShown();
+        mIsPreRendered = mCanPreRender;
+        if (mIsPreRendered) {
+            onPreRenderedWindowVisibilityChanged(true /* setVisible */);
+        } else {
+            // Pre-rendering not supported.
+            if (DEBUG) Log.d(TAG, "No pre-rendering supported");
+            mWindowVisible = true;
+        }
+
+        // request draw for the IME surface.
+        // When IME is not pre-rendered, this will actually show the IME.
+        if ((previousImeWindowStatus & IME_ACTIVE) == 0) {
+            if (DEBUG) Log.v(TAG, "showWindow: draw decorView!");
+            mWindow.show();
+        }
+        mDecorViewWasVisible = true;
         mInShowWindow = false;
     }
 
-    void showWindowInner(boolean showInput) {
+    private boolean prepareWindow(boolean showInput) {
         boolean doShowInput = false;
-        final int previousImeWindowStatus =
-                (mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0);
-        mWindowVisible = true;
+        mDecorViewVisible = true;
         if (!mShowInputRequested && mInputStarted && showInput) {
             doShowInput = true;
             mShowInputRequested = true;
@@ -1836,8 +1904,8 @@
         updateFullscreenMode();
         updateInputViewShown();
 
-        if (!mWindowCreated) {
-            mWindowCreated = true;
+        if (!mViewsCreated) {
+            mViewsCreated = true;
             initialize();
             if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView");
             View v = onCreateCandidatesView();
@@ -1846,6 +1914,10 @@
                 setCandidatesView(v);
             }
         }
+        return doShowInput;
+    }
+
+    private void startViews(boolean doShowInput) {
         if (mShowInputRequested) {
             if (!mInputViewStarted) {
                 if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
@@ -1857,29 +1929,26 @@
             mCandidatesViewStarted = true;
             onStartCandidatesView(mInputEditorInfo, false);
         }
-        
-        if (doShowInput) {
-            startExtractingText(false);
-        }
+        if (doShowInput) startExtractingText(false);
+    }
 
-        final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown());
-        if (previousImeWindowStatus != nextImeWindowStatus) {
-            setImeWindowStatus(nextImeWindowStatus, mBackDisposition);
-        }
-        if ((previousImeWindowStatus & IME_ACTIVE) == 0) {
-            if (DEBUG) Log.v(TAG, "showWindow: showing!");
+    private void onPreRenderedWindowVisibilityChanged(boolean setVisible) {
+        mWindowVisible = setVisible;
+        mShowInputFlags = setVisible ? mShowInputFlags : 0;
+        mShowInputRequested = setVisible;
+        mDecorViewVisible = setVisible;
+        if (setVisible) {
             onWindowShown();
-            mWindow.show();
         }
     }
 
-    private void finishViews() {
+    private void finishViews(boolean finishingInput) {
         if (mInputViewStarted) {
             if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
-            onFinishInputView(false);
+            onFinishInputView(finishingInput);
         } else if (mCandidatesViewStarted) {
             if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
-            onFinishCandidatesView(false);
+            onFinishCandidatesView(finishingInput);
         }
         mInputViewStarted = false;
         mCandidatesViewStarted = false;
@@ -1891,12 +1960,15 @@
     }
 
     public void hideWindow() {
-        finishViews();
-        if (mWindowVisible) {
+        if (DEBUG) Log.v(TAG, "CALL: hideWindow");
+        mIsPreRendered = false;
+        mWindowVisible = false;
+        finishViews(false /* finishingInput */);
+        if (mDecorViewVisible) {
             mWindow.hide();
-            mWindowVisible = false;
+            mDecorViewVisible = false;
             onWindowHidden();
-            mWindowWasVisible = false;
+            mDecorViewWasVisible = false;
         }
         updateFullscreenMode();
     }
@@ -1956,15 +2028,8 @@
     }
     
     void doFinishInput() {
-        if (mInputViewStarted) {
-            if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
-            onFinishInputView(true);
-        } else if (mCandidatesViewStarted) {
-            if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
-            onFinishCandidatesView(true);
-        }
-        mInputViewStarted = false;
-        mCandidatesViewStarted = false;
+        if (DEBUG) Log.v(TAG, "CALL: doFinishInput");
+        finishViews(true /* finishingInput */);
         if (mInputStarted) {
             if (DEBUG) Log.v(TAG, "CALL: onFinishInput");
             onFinishInput();
@@ -1984,7 +2049,7 @@
         initialize();
         if (DEBUG) Log.v(TAG, "CALL: onStartInput");
         onStartInput(attribute, restarting);
-        if (mWindowVisible) {
+        if (mDecorViewVisible) {
             if (mShowInputRequested) {
                 if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                 mInputViewStarted = true;
@@ -1995,6 +2060,31 @@
                 mCandidatesViewStarted = true;
                 onStartCandidatesView(mInputEditorInfo, restarting);
             }
+        } else if (mCanPreRender && mInputEditorInfo != null && mStartedInputConnection != null) {
+            // Pre-render IME views and window when real EditorInfo is available.
+            // pre-render IME window and keep it invisible.
+            if (DEBUG) Log.v(TAG, "Pre-Render IME for " + mInputEditorInfo.fieldName);
+            if (mInShowWindow) {
+                Log.w(TAG, "Re-entrance in to showWindow");
+                return;
+            }
+
+            mDecorViewWasVisible = mDecorViewVisible;
+            mInShowWindow = true;
+            startViews(prepareWindow(true /* showInput */));
+
+            // compute visibility
+            mIsPreRendered = true;
+            onPreRenderedWindowVisibilityChanged(false /* setVisible */);
+
+            // request draw for the IME surface.
+            // When IME is not pre-rendered, this will actually show the IME.
+            if (DEBUG) Log.v(TAG, "showWindow: draw decorView!");
+            mWindow.show();
+            mDecorViewWasVisible = true;
+            mInShowWindow = false;
+        } else {
+            mIsPreRendered = false;
         }
     }
     
@@ -2153,7 +2243,7 @@
             // consume the back key.
             if (doIt) requestHideSelf(0);
             return true;
-        } else if (mWindowVisible) {
+        } else if (mDecorViewVisible) {
             if (mCandidatesVisibility == View.VISIBLE) {
                 // If we are showing candidates even if no input area, then
                 // hide them.
@@ -2180,7 +2270,6 @@
         return mExtractEditText;
     }
 
-
     /**
      * Called back when a {@link KeyEvent} is forwarded from the target application.
      *
@@ -2893,8 +2982,11 @@
         inputContentInfo.setUriToken(uriToken);
     }
 
-    private static int mapToImeWindowStatus(boolean isInputViewShown) {
-        return IME_ACTIVE | (isInputViewShown ? IME_VISIBLE : 0);
+    private int mapToImeWindowStatus() {
+        return IME_ACTIVE
+                | (isInputViewShown()
+                        ? (mCanPreRender ? (mWindowVisible ? IME_VISIBLE : IME_INVISIBLE)
+                        : IME_VISIBLE) : 0);
     }
 
     /**
@@ -2904,9 +2996,10 @@
     @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
         final Printer p = new PrintWriterPrinter(fout);
         p.println("Input method service state for " + this + ":");
-        p.println("  mWindowCreated=" + mWindowCreated);
-        p.println("  mWindowVisible=" + mWindowVisible
-                + " mWindowWasVisible=" + mWindowWasVisible
+        p.println("  mViewsCreated=" + mViewsCreated);
+        p.println("  mDecorViewVisible=" + mDecorViewVisible
+                + " mDecorViewWasVisible=" + mDecorViewWasVisible
+                + " mWindowVisible=" + mWindowVisible
                 + " mInShowWindow=" + mInShowWindow);
         p.println("  Configuration=" + getResources().getConfiguration());
         p.println("  mToken=" + mToken);
@@ -2926,6 +3019,8 @@
         
         p.println("  mShowInputRequested=" + mShowInputRequested
                 + " mLastShowInputRequested=" + mLastShowInputRequested
+                + " mCanPreRender=" + mCanPreRender
+                + " mIsPreRendered=" + mIsPreRendered
                 + " mShowInputFlags=0x" + Integer.toHexString(mShowInputFlags));
         p.println("  mCandidatesVisibility=" + mCandidatesVisibility
                 + " mFullscreenApplied=" + mFullscreenApplied
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 243b0eba..5bb24ba 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1014,20 +1014,14 @@
      *                   to remove an existing always-on VPN configuration.
      * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
      *        {@code false} otherwise.
-     * @param lockdownWhitelist The list of packages that are allowed to access network directly
-     *         when VPN is in lockdown mode but is not running. Non-existent packages are ignored so
-     *         this method must be called when a package that should be whitelisted is installed or
-     *         uninstalled.
      * @return {@code true} if the package is set as always-on VPN controller;
      *         {@code false} otherwise.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
     public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
-            boolean lockdownEnabled, @Nullable List<String> lockdownWhitelist) {
+            boolean lockdownEnabled) {
         try {
-            return mService.setAlwaysOnVpnPackage(
-                    userId, vpnPackage, lockdownEnabled, lockdownWhitelist);
+            return mService.setAlwaysOnVpnPackage(userId, vpnPackage, lockdownEnabled);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1042,7 +1036,6 @@
      *         or {@code null} if none is set.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
     public String getAlwaysOnVpnPackageForUser(int userId) {
         try {
             return mService.getAlwaysOnVpnPackage(userId);
@@ -1052,36 +1045,6 @@
     }
 
     /**
-     * @return whether always-on VPN is in lockdown mode.
-     *
-     * @hide
-     **/
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
-    public boolean isVpnLockdownEnabled(int userId) {
-        try {
-            return mService.isVpnLockdownEnabled(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
-    }
-
-    /**
-     * @return the list of packages that are allowed to access network when always-on VPN is in
-     * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active.
-     *
-     * @hide
-     **/
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
-    public List<String> getVpnLockdownWhitelist(int userId) {
-        try {
-            return mService.getVpnLockdownWhitelist(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Returns details about the currently active default data network
      * for a given uid.  This is for internal use only to avoid spying
      * other apps.
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index fd7360f..e97060a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -125,11 +125,8 @@
 
     boolean updateLockdownVpn();
     boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
-    boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown,
-            in List<String> lockdownWhitelist);
+    boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown);
     String getAlwaysOnVpnPackage(int userId);
-    boolean isVpnLockdownEnabled(int userId);
-    List<String> getVpnLockdownWhitelist(int userId);
 
     int checkMobileProvisioning(int suggestedTimeOutMs);
 
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 518528d..d463b44 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -75,13 +75,25 @@
         int BUGREPORT_ERROR_USER_DENIED_CONSENT =
                 IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT;
 
+        /** The request to get user consent timed out. */
+        int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT =
+                IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT;
+
         /**
          * Called when taking bugreport resulted in an error.
          *
          * @param errorCode the error that occurred. Possible values are
          *     {@code BUGREPORT_ERROR_INVALID_INPUT},
          *     {@code BUGREPORT_ERROR_RUNTIME},
-         *     {@code BUGREPORT_ERROR_USER_DENIED_CONSENT}.
+         *     {@code BUGREPORT_ERROR_USER_DENIED_CONSENT},
+         *     {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT}.
+         *
+         * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not
+         * consent to sharing the bugreport with the calling app.
+         *
+         * <p>If {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT} is passed, then the consent timed
+         * out, but the bugreport could be available in the internal directory of dumpstate for
+         * manual retrieval.
          */
         void onError(@BugreportErrorCode int errorCode);
 
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 249b622..5dd869f 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -35,4 +35,6 @@
     void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted,
             boolean countSystem, in RemoteCallback callback);
     void getPermissionUsages(boolean countSystem, long numMillis, in RemoteCallback callback);
+    void isApplicationQualifiedForRole(String roleName, String packageName,
+            in RemoteCallback callback);
 }
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index bfcca7c..b59d0c7 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -21,6 +21,7 @@
 import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkStringNotEmpty;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
@@ -343,6 +344,28 @@
     }
 
     /**
+     * Check whether an application is qualified for a role.
+     *
+     * @param roleName name of the role to check for
+     * @param packageName package name of the application to check for
+     * @param executor Executor on which to invoke the callback
+     * @param callback Callback to receive the result
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+    public void isApplicationQualifiedForRole(@NonNull String roleName, @NonNull String packageName,
+            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+        checkStringNotEmpty(roleName);
+        checkStringNotEmpty(packageName);
+        checkNotNull(executor);
+        checkNotNull(callback);
+
+        sRemoteService.scheduleRequest(new PendingIsApplicationQualifiedForRoleRequest(
+                sRemoteService, roleName, packageName, executor, callback));
+    }
+
+    /**
      * A connection to the remote service
      */
     static final class RemoteService extends
@@ -810,4 +833,58 @@
             }
         }
     }
+
+    /**
+     * Request for {@link #isApplicationQualifiedForRole}.
+     */
+    private static final class PendingIsApplicationQualifiedForRoleRequest extends
+            AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
+
+        private final @NonNull String mRoleName;
+        private final @NonNull String mPackageName;
+        private final @NonNull Consumer<Boolean> mCallback;
+
+        private final @NonNull RemoteCallback mRemoteCallback;
+
+        private PendingIsApplicationQualifiedForRoleRequest(@NonNull RemoteService service,
+                @NonNull String roleName, @NonNull String packageName,
+                @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+            super(service);
+
+            mRoleName = roleName;
+            mPackageName = packageName;
+            mCallback = callback;
+
+            mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    boolean qualified;
+                    if (result != null) {
+                        qualified = result.getBoolean(KEY_RESULT);
+                    } else {
+                        qualified = false;
+                    }
+                    callback.accept(qualified);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                    finish();
+                }
+            }), null);
+        }
+
+        @Override
+        protected void onTimeout(RemoteService remoteService) {
+            mCallback.accept(false);
+        }
+
+        @Override
+        public void run() {
+            try {
+                getService().getServiceInterface().isApplicationQualifiedForRole(mRoleName,
+                        mPackageName, mRemoteCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error checking whether application qualifies for role", e);
+            }
+        }
+    }
 }
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 10e8c8d..9a58b97 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -20,6 +20,7 @@
 import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkStringNotEmpty;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.Manifest;
@@ -136,8 +137,19 @@
      *
      * @return descriptions of the users of permissions
      */
-    public abstract @NonNull List<RuntimePermissionUsageInfo>
-            onPermissionUsageResult(boolean countSystem, long numMillis);
+    public abstract @NonNull List<RuntimePermissionUsageInfo> onGetPermissionUsages(
+            boolean countSystem, long numMillis);
+
+    /**
+     * Check whether an application is qualified for a role.
+     *
+     * @param roleName name of the role to check for
+     * @param packageName package name of the application to check for
+     *
+     * @return whether the application is qualified for the role.
+     */
+    public abstract boolean onIsApplicationQualifiedForRole(@NonNull String roleName,
+            @NonNull String packageName);
 
     @Override
     public final IBinder onBind(Intent intent) {
@@ -240,6 +252,20 @@
                                 PermissionControllerService.this, countSystem, numMillis,
                                 callback));
             }
+
+            @Override
+            public void isApplicationQualifiedForRole(String roleName, String packageName,
+                    RemoteCallback callback) {
+                checkStringNotEmpty(roleName);
+                checkStringNotEmpty(packageName);
+                checkNotNull(callback, "callback");
+
+                enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);
+
+                mHandler.sendMessage(obtainMessage(
+                        PermissionControllerService::isApplicationQualifiedForRole,
+                        PermissionControllerService.this, roleName, packageName, callback));
+            }
         };
     }
 
@@ -296,7 +322,7 @@
     private void getPermissionUsages(boolean countSystem, long numMillis,
             @NonNull RemoteCallback callback) {
         List<RuntimePermissionUsageInfo> users =
-                onPermissionUsageResult(countSystem, numMillis);
+                onGetPermissionUsages(countSystem, numMillis);
         if (users != null && !users.isEmpty()) {
             Bundle result = new Bundle();
             result.putParcelableList(PermissionControllerManager.KEY_RESULT, users);
@@ -305,4 +331,12 @@
             callback.sendResult(null);
         }
     }
+
+    private void isApplicationQualifiedForRole(@NonNull String roleName,
+            @NonNull String packageName, @NonNull RemoteCallback callback) {
+        boolean qualified = onIsApplicationQualifiedForRole(roleName, packageName);
+        Bundle result = new Bundle();
+        result.putBoolean(PermissionControllerManager.KEY_RESULT, qualified);
+        callback.sendResult(result);
+    }
 }
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index cd823a9..148dd91 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -181,6 +181,23 @@
         String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
     }
 
+    /**
+     * Namespace for storage-related features.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface Storage {
+        String NAMESPACE = "storage";
+
+        /**
+         * If {@code 1}, enables the isolated storage feature. If {@code -1},
+         * disables the isolated storage feature. If {@code 0}, uses the default
+         * value from the build system.
+         */
+        String ISOLATED_STORAGE_ENABLED = "isolated_storage_enabled";
+    }
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0961bc3..5222fc3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -59,6 +59,7 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.SQLException;
+import android.hardware.display.ColorDisplayManager;
 import android.location.LocationManager;
 import android.media.AudioFormat;
 import android.net.ConnectivityManager;
@@ -89,7 +90,6 @@
 import android.view.inputmethod.InputMethodSystemProperty;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.ColorDisplayController;
 import com.android.internal.widget.ILockSettings;
 
 import java.io.IOException;
@@ -3239,8 +3239,8 @@
 
         private static final Validator DISPLAY_COLOR_MODE_VALIDATOR =
                 new SettingsValidators.InclusiveIntegerRangeValidator(
-                        ColorDisplayController.COLOR_MODE_NATURAL,
-                        ColorDisplayController.COLOR_MODE_AUTOMATIC);
+                        ColorDisplayManager.COLOR_MODE_NATURAL,
+                        ColorDisplayManager.COLOR_MODE_AUTOMATIC);
 
         /**
          * The amount of time in milliseconds before the device goes to sleep or begins
@@ -5800,16 +5800,6 @@
         public static final String ALWAYS_ON_VPN_LOCKDOWN = "always_on_vpn_lockdown";
 
         /**
-         * Comma separated list of packages that are allowed to access the network when VPN is in
-         * lockdown mode but not running.
-         * @see #ALWAYS_ON_VPN_LOCKDOWN
-         *
-         * @hide
-         */
-        public static final String ALWAYS_ON_VPN_LOCKDOWN_WHITELIST =
-                "always_on_vpn_lockdown_whitelist";
-
-        /**
          * Whether applications can be installed for this user via the system's
          * {@link Intent#ACTION_INSTALL_PACKAGE} mechanism.
          *
@@ -14613,6 +14603,17 @@
                 "android.settings.panel.action.INTERNET_CONNECTIVITY";
 
         /**
+         * Activity Action: Show a settings dialog containing NFC-related settings.
+         * <p>
+         * Input: Nothing.
+         * <p>
+         * Output: Nothing.
+         */
+        @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+        public static final String ACTION_NFC =
+                "android.settings.panel.action.NFC";
+
+        /**
          * Activity Action: Show a settings dialog containing all volume streams.
          * <p>
          * Input: Nothing.
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 0edcb3d..4052ed7 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -40,6 +40,7 @@
     public static final String SAFETY_HUB = "settings_safety_hub";
     public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
     public static final String AOD_IMAGEWALLPAPER_ENABLED = "settings_aod_imagewallpaper_enabled";
+    public static final String GLOBAL_ACTIONS_GRID_ENABLED = "settings_global_actions_grid_enabled";
 
     private static final Map<String, String> DEFAULT_FLAGS;
     private static final Set<String> OBSERVABLE_FLAGS;
@@ -53,13 +54,14 @@
         DEFAULT_FLAGS.put("settings_seamless_transfer", "false");
         DEFAULT_FLAGS.put("settings_slice_injection", "false");
         DEFAULT_FLAGS.put("settings_systemui_theme", "true");
-        DEFAULT_FLAGS.put("settings_wifi_dpp", "false");
-        DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "false");
-        DEFAULT_FLAGS.put("settings_wifi_sharing", "false");
+        DEFAULT_FLAGS.put("settings_wifi_dpp", "true");
+        DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "true");
+        DEFAULT_FLAGS.put("settings_wifi_sharing", "true");
         DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
         DEFAULT_FLAGS.put(SAFETY_HUB, "false");
         DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false");
         DEFAULT_FLAGS.put(AOD_IMAGEWALLPAPER_ENABLED, "false");
+        DEFAULT_FLAGS.put(GLOBAL_ACTIONS_GRID_ENABLED, "false");
 
         OBSERVABLE_FLAGS = new HashSet<>();
         OBSERVABLE_FLAGS.add(AOD_IMAGEWALLPAPER_ENABLED);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index dd6231d..2142c36 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -16,17 +16,22 @@
 
 package android.view;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TypeEvaluator;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Property;
 import android.util.SparseArray;
+import android.view.InsetsState.InternalInsetType;
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets.Type.InsetType;
-import android.view.InsetsState.InternalInsetType;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -39,6 +44,41 @@
  */
 public class InsetsController implements WindowInsetsController {
 
+    // TODO: Use animation scaling and more optimal duration.
+    private static final int ANIMATION_DURATION_MS = 400;
+    private static final int DIRECTION_NONE = 0;
+    private static final int DIRECTION_SHOW = 1;
+    private static final int DIRECTION_HIDE = 2;
+    @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
+    private @interface AnimationDirection{}
+
+    /**
+     * Translation animation evaluator.
+     */
+    private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of(
+            0,
+            (int) (startValue.top + fraction * (endValue.top - startValue.top)),
+            0,
+            (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
+
+    /**
+     * Linear animation property
+     */
+    private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> {
+        InsetsProperty() {
+            super(Insets.class, "Insets");
+        }
+
+        @Override
+        public Insets get(WindowInsetsAnimationController object) {
+            return object.getCurrentInsets();
+        }
+        @Override
+        public void set(WindowInsetsAnimationController object, Insets value) {
+            object.changeInsets(value);
+        }
+    }
+
     private final String TAG = "InsetsControllerImpl";
 
     private final InsetsState mState = new InsetsState();
@@ -58,6 +98,8 @@
 
     private final Rect mLastLegacyContentInsets = new Rect();
     private final Rect mLastLegacyStableInsets = new Rect();
+    private ObjectAnimator mAnimator;
+    private @AnimationDirection int mAnimationDirection;
 
     public InsetsController(ViewRootImpl viewRoot) {
         mViewRoot = viewRoot;
@@ -122,7 +164,10 @@
     public void onControlsChanged(InsetsSourceControl[] activeControls) {
         if (activeControls != null) {
             for (InsetsSourceControl activeControl : activeControls) {
-                mTmpControlArray.put(activeControl.getType(), activeControl);
+                if (activeControl != null) {
+                    // TODO(b/122982984): Figure out why it can be null.
+                    mTmpControlArray.put(activeControl.getType(), activeControl);
+                }
             }
         }
 
@@ -146,18 +191,40 @@
 
     @Override
     public void show(@InsetType int types) {
+        int typesReady = 0;
         final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            getSourceConsumer(internalTypes.valueAt(i)).show();
+            InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
+            if (mAnimationDirection == DIRECTION_HIDE) {
+                // Only one animator (with multiple InsetType) can run at a time.
+                // previous one should be cancelled for simplicity.
+                cancelExistingAnimation();
+            } else if (consumer.isVisible() || mAnimationDirection == DIRECTION_SHOW) {
+                // no-op: already shown or animating in.
+                // TODO: When we have more than one types: handle specific case when
+                // show animation is going on, but the current type is not becoming visible.
+                continue;
+            }
+            typesReady |= InsetsState.toPublicType(consumer.getType());
         }
+        applyAnimation(typesReady, true /* show */);
     }
 
     @Override
     public void hide(@InsetType int types) {
+        int typesReady = 0;
         final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            getSourceConsumer(internalTypes.valueAt(i)).hide();
+            InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
+            if (mAnimationDirection == DIRECTION_SHOW) {
+                cancelExistingAnimation();
+            } else if (!consumer.isVisible() || mAnimationDirection == DIRECTION_HIDE) {
+                // no-op: already hidden or animating out.
+                continue;
+            }
+            typesReady |= InsetsState.toPublicType(consumer.getType());
         }
+        applyAnimation(typesReady, false /* show */);
     }
 
     @Override
@@ -226,6 +293,79 @@
         }
     }
 
+    private void applyAnimation(@InsetType final int types, boolean show) {
+        if (types == 0) {
+            // nothing to animate.
+            return;
+        }
+        WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {
+            @Override
+            public void onReady(WindowInsetsAnimationController controller, int types) {
+                mAnimator = ObjectAnimator.ofObject(
+                        controller,
+                        new InsetsProperty(),
+                        sEvaluator,
+                        show ? controller.getHiddenStateInsets() : controller.getShownStateInsets(),
+                        show ? controller.getShownStateInsets() : controller.getHiddenStateInsets()
+                );
+                mAnimator.setDuration(ANIMATION_DURATION_MS);
+                mAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                        onAnimationFinish();
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        onAnimationFinish();
+                    }
+                });
+                mAnimator.start();
+            }
+
+            @Override
+            public void onCancelled() {}
+
+            private void onAnimationFinish() {
+                mAnimationDirection = DIRECTION_NONE;
+                if (show) {
+                    showOnAnimationEnd(types);
+                } else {
+                    hideOnAnimationEnd(types);
+                }
+            }
+        };
+        // TODO: Instead of clearing this here, properly wire up
+        // InsetsAnimationControlImpl.finish() to remove this from mAnimationControls.
+        mAnimationControls.clear();
+        controlWindowInsetsAnimation(types, listener);
+    }
+
+    private void hideOnAnimationEnd(@InsetType int types) {
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            getSourceConsumer(internalTypes.valueAt(i)).hide();
+        }
+    }
+
+    private void showOnAnimationEnd(@InsetType int types) {
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            getSourceConsumer(internalTypes.valueAt(i)).show();
+        }
+    }
+
+    /**
+     * Cancel on-going animation to show/hide {@link InsetType}.
+     */
+    @VisibleForTesting
+    public void cancelExistingAnimation() {
+        mAnimationDirection = DIRECTION_NONE;
+        if (mAnimator != null) {
+            mAnimator.cancel();
+        }
+    }
+
     void dump(String prefix, PrintWriter pw) {
         pw.println(prefix); pw.println("InsetsController:");
         mState.dump(prefix + "  ", pw);
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 145b097..7937cb6 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -17,8 +17,8 @@
 package android.view;
 
 import android.annotation.Nullable;
-import android.view.SurfaceControl.Transaction;
 import android.view.InsetsState.InternalInsetType;
+import android.view.SurfaceControl.Transaction;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -89,6 +89,11 @@
         return true;
     }
 
+    @VisibleForTesting
+    public boolean isVisible() {
+        return mVisible;
+    }
+
     private void setVisible(boolean visible) {
         if (mVisible == visible) {
             return;
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 529776e..a6af1a2 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,7 +16,10 @@
 
 package android.view;
 
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.WindowInsets.Type.SIZE;
 import static android.view.WindowInsets.Type.indexOf;
 
 import android.annotation.IntDef;
@@ -124,9 +127,10 @@
             @Nullable @InsetSide SparseIntArray typeSideMap) {
         Insets[] typeInsetsMap = new Insets[Type.SIZE];
         Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
+        boolean[] typeVisibilityMap = new boolean[SIZE];
         final Rect relativeFrame = new Rect(frame);
         final Rect relativeFrameMax = new Rect(frame);
-        if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_IME
+        if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
                 && legacyContentInsets != null && legacyStableInsets != null) {
             WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets);
             WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets);
@@ -136,22 +140,29 @@
             if (source == null) {
                 continue;
             }
+            if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
+                    && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR)) {
+                typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
+                continue;
+            }
+
             processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
-                    typeSideMap);
+                    typeSideMap, typeVisibilityMap);
 
             // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
             // target.
             if (source.getType() != TYPE_IME) {
                 processSource(source, relativeFrameMax, true /* ignoreVisibility */,
-                        typeMaxInsetsMap, null /* typeSideMap */);
+                        typeMaxInsetsMap, null /* typeSideMap */, null /* typeVisibilityMap */);
             }
         }
-        return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, isScreenRound,
+        return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
                 alwaysConsumeNavBar, cutout);
     }
 
     private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
-            Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap) {
+            Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap,
+            @Nullable boolean[] typeVisibilityMap) {
         Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
 
         int index = indexOf(toPublicType(source.getType()));
@@ -162,6 +173,10 @@
             typeInsetsMap[index] = Insets.max(existing, insets);
         }
 
+        if (typeVisibilityMap != null) {
+            typeVisibilityMap[index] = source.isVisible();
+        }
+
         if (typeSideMap != null && !Insets.NONE.equals(insets)) {
             @InsetSide int insetSide = getInsetSide(insets);
             if (insetSide != INSET_SIDE_UNKNWON) {
diff --git a/core/java/android/view/RemoteAnimationAdapter.java b/core/java/android/view/RemoteAnimationAdapter.java
index 3c9ce78..6f5a85d 100644
--- a/core/java/android/view/RemoteAnimationAdapter.java
+++ b/core/java/android/view/RemoteAnimationAdapter.java
@@ -18,7 +18,6 @@
 
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityOptions;
-import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -52,6 +51,7 @@
     private final IRemoteAnimationRunner mRunner;
     private final long mDuration;
     private final long mStatusBarTransitionDelay;
+    private final boolean mChangeNeedsSnapshot;
 
     /** @see #getCallingPid */
     private int mCallingPid;
@@ -59,21 +59,31 @@
     /**
      * @param runner The interface that gets notified when we actually need to start the animation.
      * @param duration The duration of the animation.
+     * @param changeNeedsSnapshot For change transitions, whether this should create a snapshot by
+     *                            screenshotting the task.
      * @param statusBarTransitionDelay The desired delay for all visual animations in the
      *        status bar caused by this app animation in millis.
      */
     @UnsupportedAppUsage
     public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration,
-            long statusBarTransitionDelay) {
+            long statusBarTransitionDelay, boolean changeNeedsSnapshot) {
         mRunner = runner;
         mDuration = duration;
+        mChangeNeedsSnapshot = changeNeedsSnapshot;
         mStatusBarTransitionDelay = statusBarTransitionDelay;
     }
 
+    @UnsupportedAppUsage
+    public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration,
+            long statusBarTransitionDelay) {
+        this(runner, duration, statusBarTransitionDelay, false /* changeNeedsSnapshot */);
+    }
+
     public RemoteAnimationAdapter(Parcel in) {
         mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder());
         mDuration = in.readLong();
         mStatusBarTransitionDelay = in.readLong();
+        mChangeNeedsSnapshot = in.readBoolean();
     }
 
     public IRemoteAnimationRunner getRunner() {
@@ -88,6 +98,10 @@
         return mStatusBarTransitionDelay;
     }
 
+    public boolean getChangeNeedsSnapshot() {
+        return mChangeNeedsSnapshot;
+    }
+
     /**
      * To be called by system_server to keep track which pid is running this animation.
      */
@@ -112,6 +126,7 @@
         dest.writeStrongInterface(mRunner);
         dest.writeLong(mDuration);
         dest.writeLong(mStatusBarTransitionDelay);
+        dest.writeBoolean(mChangeNeedsSnapshot);
     }
 
     public static final Creator<RemoteAnimationAdapter> CREATOR
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 991b385..cd3decf 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9067,35 +9067,43 @@
                     Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'appeared' on " + this + ": laid="
                             + isLaidOut() + ", visibleToUser=" + isVisibleToUser()
                             + ", visible=" + (getVisibility() == VISIBLE)
-                            + ": alreadyNotifiedAppeared="
-                            + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0));
-                }
-                return;
-            }
-            // All good: notify it...
-            final ViewStructure structure = session.newViewStructure(this);
-            onProvideContentCaptureStructure(structure, /* flags= */ 0);
-            session.notifyViewAppeared(structure);
-            // ...and set the flags
-            mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
-            mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
-        } else {
-            if ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) == 0
-                    || (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0) {
-                if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
-                    Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this
-                            + ": notifiedAppeared="
-                            + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)
+                            + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4
+                                    & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)
                             + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4
                                     & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0));
                 }
                 return;
             }
-            // All good: notify it...
-            session.notifyViewDisappeared(getAutofillId());
-            // ...and set the flags
+            mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
+            mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
+
+            // The code below doesn't take much for a unique view, but it's called for all views
+            // the first time the view hiearchy is laid off, which could acccumulative delay the
+            // initial layout. Hence, we're postponing it to a later stage - it might still cost a
+            // lost frame (or more), but that jank cost would only happen after the 1st layout.
+            Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
+                final ViewStructure structure = session.newViewStructure(this);
+                onProvideContentCaptureStructure(structure, /* flags= */ 0);
+                session.notifyViewAppeared(structure);
+            }, /* token= */ null);
+        } else {
+            if ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) == 0
+                    || (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0) {
+                if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
+                    Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this + ": laid="
+                            + isLaidOut() + ", visibleToUser=" + isVisibleToUser()
+                            + ", visible=" + (getVisibility() == VISIBLE)
+                            + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4
+                                    & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)
+                            + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4
+                                    & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0));
+                }
+                return;
+            }
             mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
             mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
+            Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT,
+                    () -> session.notifyViewDisappeared(getAutofillId()), /* token= */ null);
         }
     }
 
@@ -9163,7 +9171,7 @@
 
         ContentCaptureSession session = null;
         if (mParent instanceof View) {
-            session = ((View) mParent).getContentCaptureSession();
+            session = ((View) mParent).getContentCaptureSession(ccm);
         }
 
         return session != null ? session : ccm.getMainContentCaptureSession();
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index e808830..c1536ae 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -66,6 +66,7 @@
 
     private final Insets[] mTypeInsetsMap;
     private final Insets[] mTypeMaxInsetsMap;
+    private final boolean[] mTypeVisibilityMap;
 
     @Nullable private Rect mTempRect;
     private final boolean mIsRound;
@@ -106,6 +107,7 @@
     public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect,
             boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
         this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
+                createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)),
                 isRound, alwaysConsumeNavBar, displayCutout);
     }
 
@@ -122,7 +124,9 @@
      * @hide
      */
     public WindowInsets(@Nullable Insets[] typeInsetsMap,
-            @Nullable Insets[] typeMaxInsetsMap, boolean isRound,
+            @Nullable Insets[] typeMaxInsetsMap,
+            boolean[] typeVisibilityMap,
+            boolean isRound,
             boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
         mSystemWindowInsetsConsumed = typeInsetsMap == null;
         mTypeInsetsMap = mSystemWindowInsetsConsumed
@@ -134,6 +138,7 @@
                 ? new Insets[SIZE]
                 : typeMaxInsetsMap.clone();
 
+        mTypeVisibilityMap = typeVisibilityMap;
         mIsRound = isRound;
         mAlwaysConsumeNavBar = alwaysConsumeNavBar;
 
@@ -148,8 +153,8 @@
      * @param src Source to copy insets from
      */
     public WindowInsets(WindowInsets src) {
-        this(src.mTypeInsetsMap, src.mTypeMaxInsetsMap, src.mIsRound, src.mAlwaysConsumeNavBar,
-                displayCutoutCopyConstructorArgument(src));
+        this(src.mTypeInsetsMap, src.mTypeMaxInsetsMap, src.mTypeVisibilityMap, src.mIsRound,
+                src.mAlwaysConsumeNavBar, displayCutoutCopyConstructorArgument(src));
     }
 
     private static DisplayCutout displayCutoutCopyConstructorArgument(WindowInsets w) {
@@ -200,7 +205,7 @@
     /** @hide */
     @UnsupportedAppUsage
     public WindowInsets(Rect systemWindowInsets) {
-        this(createCompatTypeMap(systemWindowInsets), null, false, false, null);
+        this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null);
     }
 
     /**
@@ -225,6 +230,20 @@
         typeInsetMap[indexOf(SIDE_BARS)] = Insets.of(insets.left, 0, insets.right, insets.bottom);
     }
 
+    private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetMap) {
+        boolean[] typeVisibilityMap = new boolean[SIZE];
+        if (typeInsetMap == null) {
+            return typeVisibilityMap;
+        }
+        for (int i = FIRST; i <= LAST; i = i << 1) {
+            int index = indexOf(i);
+            if (!Insets.NONE.equals(typeInsetMap[index])) {
+                typeVisibilityMap[index] = true;
+            }
+        }
+        return typeVisibilityMap;
+    }
+
     /**
      * Used to provide a safe copy of the system window insets to pass through
      * to the existing fitSystemWindows method and other similar internals.
@@ -297,6 +316,27 @@
     }
 
     /**
+     * Returns whether a set of windows that may cause insets is currently visible on screen,
+     * regardless of whether it actually overlaps with this window.
+     *
+     * @param typeMask Bit mask of {@link InsetType}s to query visibility status.
+     * @return {@code true} if and only if all windows included in {@code typeMask} are currently
+     *         visible on screen.
+     * @hide pending unhide
+     */
+    public boolean isVisible(@InsetType int typeMask) {
+        for (int i = FIRST; i <= LAST; i = i << 1) {
+            if ((typeMask & i) == 0) {
+                continue;
+            }
+            if (!mTypeVisibilityMap[indexOf(i)]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Returns the left system window inset in pixels.
      *
      * <p>The system window inset represents the area of a full-screen window that is
@@ -392,6 +432,7 @@
     public WindowInsets consumeDisplayCutout() {
         return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
                 mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
+                mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeNavBar,
                 null /* displayCutout */);
     }
@@ -437,6 +478,7 @@
     @NonNull
     public WindowInsets consumeSystemWindowInsets() {
         return new WindowInsets(null, mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
+                mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeNavBar,
                 displayCutoutCopyConstructorArgument(this));
     }
@@ -594,7 +636,7 @@
     @NonNull
     public WindowInsets consumeStableInsets() {
         return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, null,
-                mIsRound, mAlwaysConsumeNavBar,
+                mTypeVisibilityMap, mIsRound, mAlwaysConsumeNavBar,
                 displayCutoutCopyConstructorArgument(this));
     }
 
@@ -671,6 +713,7 @@
                 mStableInsetsConsumed
                         ? null
                         : insetInsets(mTypeMaxInsetsMap, left, top, right, bottom),
+                mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeNavBar,
                 mDisplayCutoutConsumed
                         ? null
@@ -692,14 +735,15 @@
                 && mDisplayCutoutConsumed == that.mDisplayCutoutConsumed
                 && Arrays.equals(mTypeInsetsMap, that.mTypeInsetsMap)
                 && Arrays.equals(mTypeMaxInsetsMap, that.mTypeMaxInsetsMap)
+                && Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap)
                 && Objects.equals(mDisplayCutout, that.mDisplayCutout);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
-                mIsRound, mDisplayCutout, mAlwaysConsumeNavBar, mSystemWindowInsetsConsumed,
-                mStableInsetsConsumed, mDisplayCutoutConsumed);
+                Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mAlwaysConsumeNavBar,
+                mSystemWindowInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed);
     }
 
 
@@ -754,6 +798,7 @@
 
         private final Insets[] mTypeInsetsMap;
         private final Insets[] mTypeMaxInsetsMap;
+        private final boolean[] mTypeVisibilityMap;
         private boolean mSystemInsetsConsumed = true;
         private boolean mStableInsetsConsumed = true;
 
@@ -768,6 +813,7 @@
         public Builder() {
             mTypeInsetsMap = new Insets[SIZE];
             mTypeMaxInsetsMap = new Insets[SIZE];
+            mTypeVisibilityMap = new boolean[SIZE];
         }
 
         /**
@@ -778,6 +824,7 @@
         public Builder(WindowInsets insets) {
             mTypeInsetsMap = insets.mTypeInsetsMap.clone();
             mTypeMaxInsetsMap = insets.mTypeMaxInsetsMap.clone();
+            mTypeVisibilityMap = insets.mTypeVisibilityMap.clone();
             mSystemInsetsConsumed = insets.mSystemWindowInsetsConsumed;
             mStableInsetsConsumed = insets.mStableInsetsConsumed;
             mDisplayCutout = displayCutoutCopyConstructorArgument(insets);
@@ -862,6 +909,29 @@
         }
 
         /**
+         * Sets whether windows that can cause insets are currently visible on screen.
+         *
+         *
+         * @see #isVisible(int)
+         *
+         * @param typeMask The bitmask of {@link InsetType} to set the visibility for.
+         * @param visible Whether to mark the windows as visible or not.
+         *
+         * @return itself
+         * @hide pending unhide
+         */
+        @NonNull
+        public Builder setVisible(@InsetType int typeMask, boolean visible) {
+            for (int i = FIRST; i <= LAST; i = i << 1) {
+                if ((typeMask & i) == 0) {
+                    continue;
+                }
+                mTypeVisibilityMap[indexOf(i)] = visible;
+            }
+            return this;
+        }
+
+        /**
          * Sets the stable insets in pixels.
          *
          * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
@@ -916,8 +986,8 @@
         @NonNull
         public WindowInsets build() {
             return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
-                    mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mIsRound,
-                    mAlwaysConsumeNavBar, mDisplayCutout);
+                    mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
+                    mIsRound, mAlwaysConsumeNavBar, mDisplayCutout);
         }
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index d09323d..112653a 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -219,7 +219,7 @@
     @MainThread
     default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
             @NonNull EditorInfo editorInfo, boolean restarting,
-            @NonNull IBinder startInputToken) {
+            @NonNull IBinder startInputToken, boolean shouldPreRenderIme) {
         if (restarting) {
             restartInput(inputConnection, editorInfo);
         } else {
diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
index fdc34b3..4d917a1 100644
--- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
+++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
@@ -115,7 +115,7 @@
         private int mNextUserId = FIRST_NON_LOCAL_USER;
 
         private int encode(Person person) {
-            if (ConversationActions.Message.PERSON_USER_LOCAL.equals(person)) {
+            if (ConversationActions.Message.PERSON_USER_SELF.equals(person)) {
                 return USER_LOCAL;
             }
             Integer result = mMapping.get(person);
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index f7c1a26..502181f 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -109,9 +109,9 @@
          *
          * @see Builder#Builder(Person)
          */
-        public static final Person PERSON_USER_LOCAL =
+        public static final Person PERSON_USER_SELF =
                 new Person.Builder()
-                        .setKey("text-classifier-conversation-actions-local-user")
+                        .setKey("text-classifier-conversation-actions-user-self")
                         .build();
 
         /**
@@ -123,9 +123,9 @@
          *
          * @see Builder#Builder(Person)
          */
-        public static final Person PERSON_USER_REMOTE =
+        public static final Person PERSON_USER_OTHERS =
                 new Person.Builder()
-                        .setKey("text-classifier-conversation-actions-remote-user")
+                        .setKey("text-classifier-conversation-actions-user-others")
                         .build();
 
         @Nullable
@@ -235,10 +235,10 @@
             /**
              * Constructs a builder.
              *
-             * @param author the person that composed the message, use {@link #PERSON_USER_LOCAL}
+             * @param author the person that composed the message, use {@link #PERSON_USER_SELF}
              *               to represent the local user. If it is not possible to identify the
              *               remote user that the local user is conversing with, use
-             *               {@link #PERSON_USER_REMOTE} to represent a remote user.
+             *               {@link #PERSON_USER_OTHERS} to represent a remote user.
              */
             public Builder(@NonNull Person author) {
                 mAuthor = Preconditions.checkNotNull(author);
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 30137e38..803462d 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -18,6 +18,10 @@
 
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -98,6 +102,20 @@
 
     private static final boolean DEBUG = false;
 
+
+    /**
+     * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
+     * {@link AppPredictionManager} will be queried for direct share targets.
+     */
+    // TODO(b/123089490): Replace with system flag
+    private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = false;
+    // TODO(b/123088566) Share these in a better way.
+    private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
+    private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
+    public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
+    private AppPredictor mAppPredictor;
+    private AppPredictor.Callback mAppPredictorCallback;
+
     /**
      * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
      * binding to every ChooserTargetService implementation.
@@ -309,6 +327,35 @@
         mChooserShownTime = System.currentTimeMillis();
         final long systemCost = mChooserShownTime - intentReceivedTime;
         MetricsLogger.histogram(null, "system_cost_for_smart_sharing", (int) systemCost);
+
+        if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+            final IntentFilter filter = getTargetIntentFilter();
+            Bundle extras = new Bundle();
+            extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
+            AppPredictionManager appPredictionManager =
+                    getSystemService(AppPredictionManager.class);
+            mAppPredictor = appPredictionManager.createAppPredictionSession(
+                new AppPredictionContext.Builder(this)
+                    .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
+                    .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
+                    .setExtras(extras)
+                    .build());
+            mAppPredictorCallback = resultList -> {
+                final List<DisplayResolveInfo> driList =
+                        getDisplayResolveInfos(mChooserListAdapter);
+                final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
+                        new ArrayList<>();
+                for (AppTarget appTarget : resultList) {
+                    shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
+                            appTarget.getShortcutInfo(),
+                            new ComponentName(
+                                appTarget.getPackageName(), appTarget.getClassName())));
+                }
+                sendShareShortcutInfoList(shareShortcutInfos, driList);
+            };
+            mAppPredictor.registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
+        }
+
         if (DEBUG) {
             Log.d(TAG, "System Time Cost is " + systemCost);
         }
@@ -339,6 +386,10 @@
         }
         unbindRemainingServices();
         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
+        if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+            mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
+            mAppPredictor.destroy();
+        }
     }
 
     @Override
@@ -513,6 +564,7 @@
 
     void queryTargetServices(ChooserListAdapter adapter) {
         final PackageManager pm = getPackageManager();
+        ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class);
         int targetsToQuery = 0;
         for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
             final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
@@ -522,6 +574,11 @@
                 continue;
             }
             final ActivityInfo ai = dri.getResolveInfo().activityInfo;
+            if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
+                    && sm.hasShareTargets(ai.packageName)) {
+                // Share targets will be queried from ShortcutManager
+                continue;
+            }
             final Bundle md = ai.metaData;
             final String serviceName = md != null ? convertServiceName(ai.packageName,
                     md.getString(ChooserTargetService.META_DATA_NAME)) : null;
@@ -600,15 +657,10 @@
         }
     }
 
-    private void queryDirectShareTargets(ChooserListAdapter adapter) {
-        final IntentFilter filter = getTargetIntentFilter();
-        if (filter == null) {
-            return;
-        }
-
+    private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
         // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
         // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
-        final List<DisplayResolveInfo> driList = new ArrayList<>();
+        List<DisplayResolveInfo> driList = new ArrayList<>();
         int targetsToQuery = 0;
         for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
             final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
@@ -628,42 +680,59 @@
                 break;
             }
         }
+        return driList;
+    }
+
+    private void queryDirectShareTargets(ChooserListAdapter adapter) {
+        if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+            mAppPredictor.requestPredictionUpdate();
+            return;
+        }
+        final IntentFilter filter = getTargetIntentFilter();
+        if (filter == null) {
+            return;
+        }
+        final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
 
         AsyncTask.execute(() -> {
             ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
             List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
-
-            // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
-            // for direct share targets. After ShareSheet is refactored we should use the
-            // ShareShortcutInfos directly.
-            boolean resultMessageSent = false;
-            for (int i = 0; i < driList.size(); i++) {
-                List<ChooserTarget> chooserTargets = new ArrayList<>();
-                for (int j = 0; j < resultList.size(); j++) {
-                    if (driList.get(i).getResolvedComponentName().equals(
-                            resultList.get(j).getTargetComponent())) {
-                        chooserTargets.add(convertToChooserTarget(resultList.get(j)));
-                    }
-                }
-                if (chooserTargets.isEmpty()) {
-                    continue;
-                }
-
-                final Message msg = Message.obtain();
-                msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
-                msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
-                mChooserHandler.sendMessage(msg);
-                resultMessageSent = true;
-            }
-
-            if (resultMessageSent) {
-                final Message msg = Message.obtain();
-                msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
-                mChooserHandler.sendMessage(msg);
-            }
+            sendShareShortcutInfoList(resultList, driList);
         });
     }
 
+    private void sendShareShortcutInfoList(
+                List<ShortcutManager.ShareShortcutInfo> resultList,
+                List<DisplayResolveInfo> driList) {
+        // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
+        // for direct share targets. After ShareSheet is refactored we should use the
+        // ShareShortcutInfos directly.
+        boolean resultMessageSent = false;
+        for (int i = 0; i < driList.size(); i++) {
+            List<ChooserTarget> chooserTargets = new ArrayList<>();
+            for (int j = 0; j < resultList.size(); j++) {
+                if (driList.get(i).getResolvedComponentName().equals(
+                            resultList.get(j).getTargetComponent())) {
+                    chooserTargets.add(convertToChooserTarget(resultList.get(j)));
+                }
+            }
+            if (chooserTargets.isEmpty()) {
+                continue;
+            }
+            final Message msg = Message.obtain();
+            msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
+            msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
+            mChooserHandler.sendMessage(msg);
+            resultMessageSent = true;
+        }
+
+        if (resultMessageSent) {
+            final Message msg = Message.obtain();
+            msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
+            mChooserHandler.sendMessage(msg);
+        }
+    }
+
     private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut) {
         ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
         Bundle extras = new Bundle();
@@ -718,6 +787,7 @@
         // Do nothing. We'll send the voice stuff ourselves.
     }
 
+    // TODO(b/123377860) Send clicked ShortcutInfo to mAppPredictor
     void updateModelAndChooserCounts(TargetInfo info) {
         if (info != null) {
             final ResolveInfo ri = info.getResolveInfo();
diff --git a/core/java/com/android/internal/app/ColorDisplayController.java b/core/java/com/android/internal/app/ColorDisplayController.java
index b03fde7..2ac0e4d 100644
--- a/core/java/com/android/internal/app/ColorDisplayController.java
+++ b/core/java/com/android/internal/app/ColorDisplayController.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.app;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.ContentResolver;
@@ -24,18 +23,13 @@
 import android.database.ContentObserver;
 import android.hardware.display.ColorDisplayManager;
 import android.hardware.display.ColorDisplayManager.AutoMode;
+import android.hardware.display.ColorDisplayManager.ColorMode;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.SystemProperties;
 import android.provider.Settings.Secure;
-import android.provider.Settings.System;
 import android.util.Slog;
 
-import com.android.internal.R;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.time.LocalTime;
 
 /**
@@ -49,35 +43,6 @@
     private static final String TAG = "ColorDisplayController";
     private static final boolean DEBUG = false;
 
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED, COLOR_MODE_AUTOMATIC})
-    public @interface ColorMode {}
-
-    /**
-     * Color mode with natural colors.
-     *
-     * @see #setColorMode(int)
-     */
-    public static final int COLOR_MODE_NATURAL = 0;
-    /**
-     * Color mode with boosted colors.
-     *
-     * @see #setColorMode(int)
-     */
-    public static final int COLOR_MODE_BOOSTED = 1;
-    /**
-     * Color mode with saturated colors.
-     *
-     * @see #setColorMode(int)
-     */
-    public static final int COLOR_MODE_SATURATED = 2;
-    /**
-     * Color mode with automatic colors.
-     *
-     * @see #setColorMode(int)
-     */
-    public static final int COLOR_MODE_AUTOMATIC = 3;
-
     private final Context mContext;
     private final int mUserId;
     private final ColorDisplayManager mColorDisplayManager;
@@ -197,74 +162,10 @@
     }
 
     /**
-     * Get the current color mode from system properties, or return -1.
-     *
-     * See com.android.server.display.DisplayTransformManager.
-     */
-    private @ColorMode int getCurrentColorModeFromSystemProperties() {
-        final int displayColorSetting = SystemProperties.getInt("persist.sys.sf.native_mode", 0);
-        if (displayColorSetting == 0) {
-            return "1.0".equals(SystemProperties.get("persist.sys.sf.color_saturation"))
-                    ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
-        } else if (displayColorSetting == 1) {
-            return COLOR_MODE_SATURATED;
-        } else if (displayColorSetting == 2) {
-            return COLOR_MODE_AUTOMATIC;
-        } else {
-            return -1;
-        }
-    }
-
-    private boolean isColorModeAvailable(@ColorMode int colorMode) {
-        final int[] availableColorModes = mContext.getResources().getIntArray(
-                R.array.config_availableColorModes);
-        if (availableColorModes != null) {
-            for (int mode : availableColorModes) {
-                if (mode == colorMode) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
      * Get the current color mode.
      */
     public int getColorMode() {
-        if (getAccessibilityTransformActivated()) {
-            if (isColorModeAvailable(COLOR_MODE_SATURATED)) {
-                return COLOR_MODE_SATURATED;
-            } else if (isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
-                return COLOR_MODE_AUTOMATIC;
-            }
-        }
-
-        int colorMode = System.getIntForUser(mContext.getContentResolver(),
-                System.DISPLAY_COLOR_MODE, -1, mUserId);
-        if (colorMode == -1) {
-            // There might be a system property controlling color mode that we need to respect; if
-            // not, this will set a suitable default.
-            colorMode = getCurrentColorModeFromSystemProperties();
-        }
-
-        // This happens when a color mode is no longer available (e.g., after system update or B&R)
-        // or the device does not support any color mode.
-        if (!isColorModeAvailable(colorMode)) {
-            if (colorMode == COLOR_MODE_BOOSTED && isColorModeAvailable(COLOR_MODE_NATURAL)) {
-                colorMode = COLOR_MODE_NATURAL;
-            } else if (colorMode == COLOR_MODE_SATURATED
-                    && isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
-                colorMode = COLOR_MODE_AUTOMATIC;
-            } else if (colorMode == COLOR_MODE_AUTOMATIC
-                    && isColorModeAvailable(COLOR_MODE_SATURATED)) {
-                colorMode = COLOR_MODE_SATURATED;
-            } else {
-                colorMode = -1;
-            }
-        }
-
-        return colorMode;
+        return mColorDisplayManager.getColorMode();
     }
 
     /**
@@ -273,11 +174,7 @@
      * @param colorMode the color mode
      */
     public void setColorMode(@ColorMode int colorMode) {
-        if (!isColorModeAvailable(colorMode)) {
-            throw new IllegalArgumentException("Invalid colorMode: " + colorMode);
-        }
-        System.putIntForUser(mContext.getContentResolver(), System.DISPLAY_COLOR_MODE, colorMode,
-                mUserId);
+        mColorDisplayManager.setColorMode(colorMode);
     }
 
     /**
@@ -294,18 +191,6 @@
         return ColorDisplayManager.getMaximumColorTemperature(mContext);
     }
 
-    /**
-     * Returns true if any Accessibility color transforms are enabled.
-     */
-    public boolean getAccessibilityTransformActivated() {
-        final ContentResolver cr = mContext.getContentResolver();
-        return
-            Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
-                    0, mUserId) == 1
-            || Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
-                    0, mUserId) == 1;
-    }
-
     private void onSettingChanged(@NonNull String setting) {
         if (DEBUG) {
             Slog.d(TAG, "onSettingChanged: " + setting);
@@ -328,13 +213,6 @@
                 case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
                     mCallback.onColorTemperatureChanged(getColorTemperature());
                     break;
-                case System.DISPLAY_COLOR_MODE:
-                    mCallback.onDisplayColorModeChanged(getColorMode());
-                    break;
-                case Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED:
-                case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED:
-                    mCallback.onAccessibilityTransformChanged(getAccessibilityTransformActivated());
-                    break;
             }
         }
     }
@@ -377,14 +255,6 @@
                         false /* notifyForDescendants */, mContentObserver, mUserId);
                 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE),
                         false /* notifyForDescendants */, mContentObserver, mUserId);
-                cr.registerContentObserver(System.getUriFor(System.DISPLAY_COLOR_MODE),
-                        false /* notifyForDecendants */, mContentObserver, mUserId);
-                cr.registerContentObserver(
-                        Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED),
-                        false /* notifyForDecendants */, mContentObserver, mUserId);
-                cr.registerContentObserver(
-                        Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED),
-                        false /* notifyForDecendants */, mContentObserver, mUserId);
             }
         }
     }
@@ -424,19 +294,5 @@
          * @param colorTemperature the color temperature to tint the screen
          */
         default void onColorTemperatureChanged(int colorTemperature) {}
-
-        /**
-         * Callback invoked when the color mode changes.
-         *
-         * @param displayColorMode the color mode
-         */
-        default void onDisplayColorModeChanged(int displayColorMode) {}
-
-        /**
-         * Callback invoked when Accessibility color transforms change.
-         *
-         * @param state the state Accessibility color transforms (true of active)
-         */
-        default void onAccessibilityTransformChanged(boolean state) {}
     }
 }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 7600dc9..8978496 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -100,6 +100,7 @@
      * @param backDisposition disposition flags
      * @see android.inputmethodservice.InputMethodService#IME_ACTIVE
      * @see android.inputmethodservice.InputMethodService#IME_VISIBLE
+     * @see android.inputmethodservice.InputMethodService#IME_INVISIBLE
      * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT
      * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_ADJUST_NOTHING
      */
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 97d5a65..2ee902a 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -40,7 +40,7 @@
     void unbindInput();
 
     void startInput(in IBinder startInputToken, in IInputContext inputContext, int missingMethods,
-            in EditorInfo attribute, boolean restarting);
+            in EditorInfo attribute, boolean restarting, boolean preRenderImeViews);
 
     void createSession(in InputChannel channel, IInputSessionCallback callback);
 
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 7679c5b..dd7633a 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -28,6 +28,7 @@
 
 #include "SkBlurDrawLooper.h"
 #include "SkColorFilter.h"
+#include "SkFont.h"
 #include "SkFontTypes.h"
 #include "SkMaskFilter.h"
 #include "SkPath.h"
@@ -69,9 +70,21 @@
 static jclass   gFontMetricsInt_class;
 static JMetricsID gFontMetricsInt_fieldID;
 
-static void defaultSettingsForAndroid(Paint* paint) {
-    // GlyphID encoding is required because we are using Harfbuzz shaping
-    paint->setTextEncoding(kGlyphID_SkTextEncoding);
+static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count,
+                           const SkPoint pos[], SkPath* dst) {
+    struct Rec {
+        SkPath* fDst;
+        const SkPoint* fPos;
+    } rec = { dst, pos };
+    font.getPaths(glyphs, count, [](const SkPath* src, const SkMatrix& mx, void* ctx) {
+        Rec* rec = (Rec*)ctx;
+        if (src) {
+            SkMatrix tmp(mx);
+            tmp.postTranslate(rec->fPos->fX, rec->fPos->fY);
+            rec->fDst->addPath(*src, tmp);
+        }
+        rec->fPos += 1;
+    }, &rec);
 }
 
 namespace PaintGlue {
@@ -88,18 +101,7 @@
     }
 
     static jlong init(JNIEnv* env, jobject) {
-        static_assert(1 <<  0 == SkPaint::kAntiAlias_Flag,             "paint_flags_mismatch");
-        static_assert(1 <<  2 == SkPaint::kDither_Flag,                "paint_flags_mismatch");
-        static_assert(1 <<  3 == SkPaint::kUnderlineText_ReserveFlag,  "paint_flags_mismatch");
-        static_assert(1 <<  4 == SkPaint::kStrikeThruText_ReserveFlag, "paint_flags_mismatch");
-        static_assert(1 <<  5 == SkPaint::kFakeBoldText_Flag,          "paint_flags_mismatch");
-        static_assert(1 <<  6 == SkPaint::kLinearText_Flag,            "paint_flags_mismatch");
-        static_assert(1 <<  7 == SkPaint::kSubpixelText_Flag,          "paint_flags_mismatch");
-        static_assert(1 << 10 == SkPaint::kEmbeddedBitmapText_Flag,    "paint_flags_mismatch");
-
-        Paint* obj = new Paint();
-        defaultSettingsForAndroid(obj);
-        return reinterpret_cast<jlong>(obj);
+        return reinterpret_cast<jlong>(new Paint);
     }
 
     static jlong initWithPaint(JNIEnv* env, jobject clazz, jlong paintHandle) {
@@ -288,10 +290,11 @@
                 pos[i].fX = x + layout.getX(i);
                 pos[i].fY = y + layout.getY(i);
             }
+            const SkFont& font = paint->getSkFont();
             if (start == 0) {
-                paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, path);
+                getPosTextPath(font, glyphs, end, pos, path);
             } else {
-                paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, &tmpPath);
+                getPosTextPath(font, glyphs + start, end - start, pos + start, &tmpPath);
                 path->addPath(tmpPath);
             }
         }
@@ -321,7 +324,6 @@
         x += MinikinUtils::xOffsetForTextAlign(paint, layout);
         Paint::Align align = paint->getTextAlign();
         paint->setTextAlign(Paint::kLeft_Align);
-        paint->setTextEncoding(kGlyphID_SkTextEncoding);
         GetTextFunctor f(layout, path, x, y, paint, glyphs, pos);
         MinikinUtils::forFontRun(layout, paint, f);
         paint->setTextAlign(align);
@@ -584,20 +586,21 @@
         const int kElegantDescent = -500;
         const int kElegantLeading = 0;
         Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+        SkFont* font = &paint->getSkFont();
         const Typeface* typeface = paint->getAndroidTypeface();
         typeface = Typeface::resolveDefault(typeface);
         minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
-        float saveSkewX = paint->getTextSkewX();
-        bool savefakeBold = paint->isFakeBoldText();
-        MinikinFontSkia::populateSkPaint(paint, baseFont.font->typeface().get(), baseFont.fakery);
-        SkScalar spacing = paint->getFontMetrics(metrics);
+        float saveSkewX = font->getSkewX();
+        bool savefakeBold = font->isEmbolden();
+        MinikinFontSkia::populateSkFont(font, baseFont.font->typeface().get(), baseFont.fakery);
+        SkScalar spacing = font->getMetrics(metrics);
         // The populateSkPaint call may have changed fake bold / text skew
         // because we want to measure with those effects applied, so now
         // restore the original settings.
-        paint->setTextSkewX(saveSkewX);
-        paint->setFakeBoldText(savefakeBold);
+        font->setSkewX(saveSkewX);
+        font->setEmbolden(savefakeBold);
         if (paint->getFamilyVariant() == minikin::FamilyVariant::ELEGANT) {
-            SkScalar size = paint->getTextSize();
+            SkScalar size = font->getSize();
             metrics->fTop = -size * kElegantTop / 2048;
             metrics->fBottom = -size * kElegantBottom / 2048;
             metrics->fAscent = -size * kElegantAscent / 2048;
@@ -646,9 +649,7 @@
     // ------------------ @CriticalNative ---------------------------
 
     static void reset(jlong objHandle) {
-        Paint* obj = reinterpret_cast<Paint*>(objHandle);
-        obj->reset();
-        defaultSettingsForAndroid(obj);
+        reinterpret_cast<Paint*>(objHandle)->reset();
     }
 
     static void assign(jlong dstPaintHandle, jlong srcPaintHandle) {
@@ -657,31 +658,13 @@
         *dst = *src;
     }
 
-    // Equivalent to the Java Paint's FILTER_BITMAP_FLAG.
-    static const uint32_t sFilterBitmapFlag = 0x02;
-
     static jint getFlags(jlong paintHandle) {
-        Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle);
-        uint32_t result = nativePaint->getFlags();
-        result &= ~sFilterBitmapFlag; // Filtering no longer stored in this bit. Mask away.
-        if (nativePaint->getFilterQuality() != kNone_SkFilterQuality) {
-            result |= sFilterBitmapFlag;
-        }
-        return static_cast<jint>(result);
+        uint32_t flags = reinterpret_cast<Paint*>(paintHandle)->getJavaFlags();
+        return static_cast<jint>(flags);
     }
 
     static void setFlags(jlong paintHandle, jint flags) {
-        Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle);
-        // Instead of modifying 0x02, change the filter level.
-        nativePaint->setFilterQuality(flags & sFilterBitmapFlag
-                ? kLow_SkFilterQuality
-                : kNone_SkFilterQuality);
-        // Don't pass through filter flag, which is no longer stored in paint's flags.
-        flags &= ~sFilterBitmapFlag;
-        // Use the existing value for 0x02.
-        const uint32_t existing0x02Flag = nativePaint->getFlags() & sFilterBitmapFlag;
-        flags |= existing0x02Flag;
-        nativePaint->setFlags(flags);
+        reinterpret_cast<Paint*>(paintHandle)->setJavaFlags(flags);
     }
 
     static jint getHinting(jlong paintHandle) {
@@ -699,37 +682,23 @@
     }
 
     static void setLinearText(jlong paintHandle, jboolean linearText) {
-        reinterpret_cast<Paint*>(paintHandle)->setLinearText(linearText);
+        reinterpret_cast<Paint*>(paintHandle)->getSkFont().setLinearMetrics(linearText);
     }
 
     static void setSubpixelText(jlong paintHandle, jboolean subpixelText) {
-        reinterpret_cast<Paint*>(paintHandle)->setSubpixelText(subpixelText);
+        reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSubpixel(subpixelText);
     }
 
     static void setUnderlineText(jlong paintHandle, jboolean underlineText) {
-        Paint* paint = reinterpret_cast<Paint*>(paintHandle);
-        uint32_t flags = paint->getFlags();
-        if (underlineText) {
-            flags |= Paint::kUnderlineText_ReserveFlag;
-        } else {
-            flags &= ~Paint::kUnderlineText_ReserveFlag;
-        }
-        paint->setFlags(flags);
+        reinterpret_cast<Paint*>(paintHandle)->setUnderline(underlineText);
     }
 
     static void setStrikeThruText(jlong paintHandle, jboolean strikeThruText) {
-        Paint* paint = reinterpret_cast<Paint*>(paintHandle);
-        uint32_t flags = paint->getFlags();
-        if (strikeThruText) {
-            flags |= Paint::kStrikeThruText_ReserveFlag;
-        } else {
-            flags &= ~Paint::kStrikeThruText_ReserveFlag;
-        }
-        paint->setFlags(flags);
+        reinterpret_cast<Paint*>(paintHandle)->setStrikeThru(strikeThruText);
     }
 
     static void setFakeBoldText(jlong paintHandle, jboolean fakeBoldText) {
-        reinterpret_cast<Paint*>(paintHandle)->setFakeBoldText(fakeBoldText);
+        reinterpret_cast<Paint*>(paintHandle)->getSkFont().setEmbolden(fakeBoldText);
     }
 
     static void setFilterBitmap(jlong paintHandle, jboolean filterBitmap) {
@@ -907,27 +876,29 @@
     }
 
     static jfloat getTextSize(jlong paintHandle) {
-        return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSize());
+        return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize());
     }
 
     static void setTextSize(jlong paintHandle, jfloat textSize) {
-        reinterpret_cast<Paint*>(paintHandle)->setTextSize(textSize);
+        if (textSize >= 0) {
+            reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSize(textSize);
+        }
     }
 
     static jfloat getTextScaleX(jlong paintHandle) {
-        return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextScaleX());
+        return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getScaleX());
     }
 
     static void setTextScaleX(jlong paintHandle, jfloat scaleX) {
-        reinterpret_cast<Paint*>(paintHandle)->setTextScaleX(scaleX);
+        reinterpret_cast<Paint*>(paintHandle)->getSkFont().setScaleX(scaleX);
     }
 
     static jfloat getTextSkewX(jlong paintHandle) {
-        return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSkewX());
+        return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSkewX());
     }
 
     static void setTextSkewX(jlong paintHandle, jfloat skewX) {
-        reinterpret_cast<Paint*>(paintHandle)->setTextSkewX(skewX);
+        reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSkewX(skewX);
     }
 
     static jfloat getLetterSpacing(jlong paintHandle) {
@@ -979,7 +950,7 @@
         if (metrics.hasUnderlinePosition(&position)) {
             return SkScalarToFloat(position);
         } else {
-            const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize();
+            const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize();
             return SkScalarToFloat(Paint::kStdUnderline_Top * textSize);
         }
     }
@@ -991,18 +962,18 @@
         if (metrics.hasUnderlineThickness(&thickness)) {
             return SkScalarToFloat(thickness);
         } else {
-            const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize();
+            const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize();
             return SkScalarToFloat(Paint::kStdUnderline_Thickness * textSize);
         }
     }
 
     static jfloat getStrikeThruPosition(jlong paintHandle) {
-        const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize();
+        const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize();
         return SkScalarToFloat(Paint::kStdStrikeThru_Top * textSize);
     }
 
     static jfloat getStrikeThruThickness(jlong paintHandle) {
-        const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize();
+        const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize();
         return SkScalarToFloat(Paint::kStdStrikeThru_Thickness * textSize);
     }
 
diff --git a/core/jni/android/graphics/PaintFilter.cpp b/core/jni/android/graphics/PaintFilter.cpp
index 182b22b..4fe9140 100644
--- a/core/jni/android/graphics/PaintFilter.cpp
+++ b/core/jni/android/graphics/PaintFilter.cpp
@@ -21,6 +21,7 @@
 
 #include "core_jni_helpers.h"
 
+#include "hwui/Paint.h"
 #include "hwui/PaintFilter.h"
 #include "SkPaint.h"
 
@@ -29,11 +30,15 @@
 class PaintFlagsFilter : public PaintFilter {
 public:
     PaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags) {
-        fClearFlags = static_cast<uint16_t>(clearFlags & SkPaint::kAllFlags);
-        fSetFlags = static_cast<uint16_t>(setFlags & SkPaint::kAllFlags);
+        fClearFlags = static_cast<uint16_t>(clearFlags);
+        fSetFlags = static_cast<uint16_t>(setFlags);
     }
     void filter(SkPaint* paint) override {
-        paint->setFlags((paint->getFlags() & ~fClearFlags) | fSetFlags);
+        uint32_t flags = Paint::GetSkPaintJavaFlags(*paint);
+        Paint::SetSkPaintJavaFlags(paint, (flags & ~fClearFlags) | fSetFlags);
+    }
+    void filterFullPaint(Paint* paint) override {
+        paint->setJavaFlags((paint->getJavaFlags() & ~fClearFlags) | fSetFlags);
     }
 
 private:
@@ -41,33 +46,6 @@
     uint16_t fSetFlags;
 };
 
-// Custom version of PaintFlagsDrawFilter that also calls setFilterQuality.
-class CompatPaintFlagsFilter : public PaintFlagsFilter {
-public:
-    CompatPaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags, SkFilterQuality desiredQuality)
-    : PaintFlagsFilter(clearFlags, setFlags)
-    , fDesiredQuality(desiredQuality) {
-    }
-
-    virtual void filter(SkPaint* paint) {
-        PaintFlagsFilter::filter(paint);
-        paint->setFilterQuality(fDesiredQuality);
-    }
-
-private:
-    const SkFilterQuality fDesiredQuality;
-};
-
-// Returns whether flags contains FILTER_BITMAP_FLAG. If flags does, remove it.
-static inline bool hadFiltering(jint& flags) {
-    // Equivalent to the Java Paint's FILTER_BITMAP_FLAG.
-    static const uint32_t sFilterBitmapFlag = 0x02;
-
-    const bool result = (flags & sFilterBitmapFlag) != 0;
-    flags &= ~sFilterBitmapFlag;
-    return result;
-}
-
 class PaintFilterGlue {
 public:
 
@@ -78,29 +56,11 @@
 
     static jlong CreatePaintFlagsFilter(JNIEnv* env, jobject clazz,
                                         jint clearFlags, jint setFlags) {
+        PaintFilter* filter = nullptr;
         if (clearFlags | setFlags) {
-            // Mask both groups of flags to remove FILTER_BITMAP_FLAG, which no
-            // longer has a Skia equivalent flag (instead it corresponds to
-            // calling setFilterQuality), and keep track of which group(s), if
-            // any, had the flag set.
-            const bool turnFilteringOn = hadFiltering(setFlags);
-            const bool turnFilteringOff = hadFiltering(clearFlags);
-
-            PaintFilter* filter;
-            if (turnFilteringOn) {
-                // Turning filtering on overrides turning it off.
-                filter = new CompatPaintFlagsFilter(clearFlags, setFlags,
-                        kLow_SkFilterQuality);
-            } else if (turnFilteringOff) {
-                filter = new CompatPaintFlagsFilter(clearFlags, setFlags,
-                        kNone_SkFilterQuality);
-            } else {
-                filter = new PaintFlagsFilter(clearFlags, setFlags);
-            }
-            return reinterpret_cast<jlong>(filter);
-        } else {
-            return NULL;
+            filter = new PaintFlagsFilter(clearFlags, setFlags);
         }
+        return reinterpret_cast<jlong>(filter);
     }
 };
 
diff --git a/core/jni/android_media_MediaMetricsJNI.cpp b/core/jni/android_media_MediaMetricsJNI.cpp
deleted file mode 100644
index 38f7a7e..0000000
--- a/core/jni/android_media_MediaMetricsJNI.cpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2017, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <android_runtime/AndroidRuntime.h>
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-
-#include "android_media_MediaMetricsJNI.h"
-#include <media/MediaAnalyticsItem.h>
-
-
-namespace android {
-
-// place the attributes into a java PersistableBundle object
-jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle) {
-
-    jclass clazzBundle = env->FindClass("android/os/PersistableBundle");
-    if (clazzBundle==NULL) {
-        ALOGD("can't find android/os/PersistableBundle");
-        return NULL;
-    }
-    // sometimes the caller provides one for us to fill
-    if (mybundle == NULL) {
-        // create the bundle
-        jmethodID constructID = env->GetMethodID(clazzBundle, "<init>", "()V");
-        mybundle = env->NewObject(clazzBundle, constructID);
-        if (mybundle == NULL) {
-            return NULL;
-        }
-    }
-
-    // grab methods that we can invoke
-    jmethodID setIntID = env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V");
-    jmethodID setLongID = env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V");
-    jmethodID setDoubleID = env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V");
-    jmethodID setStringID = env->GetMethodID(clazzBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V");
-
-    // env, class, method, {parms}
-    //env->CallVoidMethod(env, mybundle, setIntID, jstr, jint);
-
-    // iterate through my attributes
-    // -- get name, get type, get value
-    // -- insert appropriately into the bundle
-    for (size_t i = 0 ; i < item->mPropCount; i++ ) {
-            MediaAnalyticsItem::Prop *prop = &item->mProps[i];
-            // build the key parameter from prop->mName
-            jstring keyName = env->NewStringUTF(prop->mName);
-            // invoke the appropriate method to insert
-            switch (prop->mType) {
-                case MediaAnalyticsItem::kTypeInt32:
-                    env->CallVoidMethod(mybundle, setIntID,
-                                        keyName, (jint) prop->u.int32Value);
-                    break;
-                case MediaAnalyticsItem::kTypeInt64:
-                    env->CallVoidMethod(mybundle, setLongID,
-                                        keyName, (jlong) prop->u.int64Value);
-                    break;
-                case MediaAnalyticsItem::kTypeDouble:
-                    env->CallVoidMethod(mybundle, setDoubleID,
-                                        keyName, (jdouble) prop->u.doubleValue);
-                    break;
-                case MediaAnalyticsItem::kTypeCString:
-                    env->CallVoidMethod(mybundle, setStringID, keyName,
-                                        env->NewStringUTF(prop->u.CStringValue));
-                    break;
-                default:
-                        ALOGE("to_String bad item type: %d for %s",
-                              prop->mType, prop->mName);
-                        break;
-            }
-    }
-
-    return mybundle;
-}
-
-};  // namespace android
-
diff --git a/core/jni/android_media_MediaMetricsJNI.cpp b/core/jni/android_media_MediaMetricsJNI.cpp
new file mode 120000
index 0000000..3204317c
--- /dev/null
+++ b/core/jni/android_media_MediaMetricsJNI.cpp
@@ -0,0 +1 @@
+../../media/jni/android_media_MediaMetricsJNI.cpp
\ No newline at end of file
diff --git a/core/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h
deleted file mode 100644
index b3cb4d2..0000000
--- a/core/jni/android_media_MediaMetricsJNI.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2017, 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.
- */
-
-#ifndef _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
-#define _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
-
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-#include <media/MediaAnalyticsItem.h>
-
-namespace android {
-
-class MediaMetricsJNI {
-public:
-    static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle);
-};
-
-};  // namespace android
-
-#endif //  _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
diff --git a/core/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h
new file mode 120000
index 0000000..c7a685b
--- /dev/null
+++ b/core/jni/android_media_MediaMetricsJNI.h
@@ -0,0 +1 @@
+../../media/jni/android_media_MediaMetricsJNI.h
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6f7312f..4a54bd7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2119,7 +2119,7 @@
     <!-- ================================== -->
     <eat-comment />
 
-    <!-- @SystemApi Allows an application to write to internal media storage
+    <!-- @SystemApi @TestApi Allows an application to write to internal media storage
          @hide  -->
     <permission android:name="android.permission.WRITE_MEDIA_STORAGE"
         android:protectionLevel="signature|privileged" />
@@ -3528,12 +3528,6 @@
         android:protectionLevel="signature|privileged" />
     <uses-permission android:name="android.permission.CONTROL_VPN" />
 
-    <!-- Allows an application to access and modify always-on VPN configuration.
-         <p>Not for use by third-party or privileged applications.
-         @hide -->
-    <permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN"
-        android:protectionLevel="signature" />
-
     <!-- Allows an application to capture audio output.
          <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 0fc3bd2..8e8b07a 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -49,6 +49,7 @@
 LOCAL_JAVA_LIBRARIES := \
     android.test.runner \
     telephony-common \
+    testables \
     org.apache.http.legacy \
     android.test.base \
     android.test.mock \
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 9b79e85..a15dbc8 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -574,7 +574,6 @@
                  Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
                  Settings.Secure.ALWAYS_ON_VPN_APP,
                  Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
-                 Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST,
                  Settings.Secure.ANDROID_ID,
                  Settings.Secure.ANR_SHOW_BACKGROUND,
                  Settings.Secure.ASSISTANT,
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index d447451..8f21096 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -16,15 +16,25 @@
 
 package android.view;
 
+import static android.view.InsetsState.TYPE_IME;
+import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.TYPE_TOP_BAR;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Mockito.mock;
-
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.view.WindowInsets.Type;
+import android.view.WindowManager.BadTokenException;
+import android.view.WindowManager.LayoutParams;
+import android.widget.TextView;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -37,8 +47,7 @@
 @RunWith(AndroidJUnit4.class)
 public class InsetsControllerTest {
 
-    private InsetsController mController = new InsetsController(mock(ViewRootImpl.class));
-
+    private InsetsController mController;
     private SurfaceSession mSession = new SurfaceSession();
     private SurfaceControl mLeash;
 
@@ -47,6 +56,24 @@
         mLeash = new SurfaceControl.Builder(mSession)
                 .setName("testSurface")
                 .build();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            Context context = InstrumentationRegistry.getTargetContext();
+            // cannot mock ViewRootImpl since it's final.
+            ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
+            try {
+                viewRootImpl.setView(new TextView(context), new LayoutParams(), null);
+            } catch (BadTokenException e) {
+                // activity isn't running, we will ignore BadTokenException.
+            }
+            mController = new InsetsController(viewRootImpl);
+            final Rect rect = new Rect(5, 5, 5, 5);
+            mController.calculateInsets(
+                    false,
+                    false,
+                    new DisplayCutout(
+                            Insets.of(10, 10, 10, 10), rect, rect, rect, rect),
+                    rect, rect);
+        });
     }
 
     @Test
@@ -64,4 +91,39 @@
         mController.onControlsChanged(new InsetsSourceControl[0]);
         assertNull(mController.getSourceConsumer(TYPE_TOP_BAR).getControl());
     }
+
+    @Test
+    public void testAnimationEndState() {
+        final InsetsSourceControl navBar = new InsetsSourceControl(TYPE_NAVIGATION_BAR, mLeash);
+        final InsetsSourceControl topBar = new InsetsSourceControl(TYPE_TOP_BAR, mLeash);
+        final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash);
+
+        InsetsSourceControl[] controls = new InsetsSourceControl[3];
+        controls[0] = navBar;
+        controls[1] = topBar;
+        controls[2] = ime;
+        mController.onControlsChanged(controls);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mController.show(Type.all());
+            // quickly jump to final state by cancelling it.
+            mController.cancelExistingAnimation();
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(topBar.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+
+            mController.hide(Type.all());
+            mController.cancelExistingAnimation();
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+
+            mController.show(Type.ime());
+            mController.cancelExistingAnimation();
+            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+
+            mController.hide(Type.ime());
+            mController.cancelExistingAnimation();
+            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+        });
+    }
 }
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index d57fa8f..6a83c29b 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -20,6 +20,7 @@
 import static android.view.WindowInsets.Type.sideBars;
 import static android.view.WindowInsets.Type.topBar;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.graphics.Insets;
@@ -73,4 +74,29 @@
         assertEquals(Insets.of(0, 50, 0, 0), insets.getInsets(topBar()));
         assertEquals(Insets.of(0, 0, 30, 10), insets.getInsets(sideBars()));
     }
+
+    // TODO: Move this to CTS once API made public
+    @Test
+    public void visibility() {
+        Builder b = new WindowInsets.Builder();
+        b.setInsets(sideBars(), Insets.of(0, 0, 0, 100));
+        b.setInsets(ime(), Insets.of(0, 0, 0, 300));
+        b.setVisible(sideBars(), true);
+        b.setVisible(ime(), true);
+        WindowInsets insets = b.build();
+        assertTrue(insets.isVisible(sideBars()));
+        assertTrue(insets.isVisible(sideBars() | ime()));
+        assertFalse(insets.isVisible(sideBars() | topBar()));
+    }
+
+    // TODO: Move this to CTS once API made public
+    @Test
+    public void consume_doesntChangeVisibility() {
+        Builder b = new WindowInsets.Builder();
+        b.setInsets(ime(), Insets.of(0, 0, 0, 300));
+        b.setVisible(ime(), true);
+        WindowInsets insets = b.build();
+        insets = insets.consumeSystemWindowInsets();
+        assertTrue(insets.isVisible(ime()));
+    }
 }
diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
index 780e15a..5022e30 100644
--- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
@@ -16,8 +16,8 @@
 
 package android.view.textclassifier;
 
-import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_LOCAL;
-import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_REMOTE;
+import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_OTHERS;
+import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_SELF;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -58,7 +58,7 @@
     @Test
     public void testToNativeMessages_noTextMessages() {
         ConversationActions.Message messageWithoutText =
-                new ConversationActions.Message.Builder(PERSON_USER_REMOTE).build();
+                new ConversationActions.Message.Builder(PERSON_USER_OTHERS).build();
 
         ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
                 ActionsSuggestionsHelper.toNativeMessages(
@@ -81,7 +81,7 @@
                         .setText("second")
                         .build();
         ConversationActions.Message thirdMessage =
-                new ConversationActions.Message.Builder(PERSON_USER_LOCAL)
+                new ConversationActions.Message.Builder(PERSON_USER_SELF)
                         .setText("third")
                         .build();
         ConversationActions.Message fourthMessage =
@@ -104,16 +104,16 @@
     @Test
     public void testToNativeMessages_referenceTime() {
         ConversationActions.Message firstMessage =
-                new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
+                new ConversationActions.Message.Builder(PERSON_USER_OTHERS)
                         .setText("first")
                         .setReferenceTime(createZonedDateTimeFromMsUtc(1000))
                         .build();
         ConversationActions.Message secondMessage =
-                new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
+                new ConversationActions.Message.Builder(PERSON_USER_OTHERS)
                         .setText("second")
                         .build();
         ConversationActions.Message thirdMessage =
-                new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
+                new ConversationActions.Message.Builder(PERSON_USER_OTHERS)
                         .setText("third")
                         .setReferenceTime(createZonedDateTimeFromMsUtc(2000))
                         .build();
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
index 4d78e40..5e58f82 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -378,7 +378,7 @@
         if (isTextClassifierDisabled()) return;
         ConversationActions.Message message =
                 new ConversationActions.Message.Builder(
-                        ConversationActions.Message.PERSON_USER_REMOTE)
+                        ConversationActions.Message.PERSON_USER_OTHERS)
                         .setText("Where are you?")
                         .build();
         TextClassifier.EntityConfig typeConfig =
@@ -407,7 +407,7 @@
         if (isTextClassifierDisabled()) return;
         ConversationActions.Message message =
                 new ConversationActions.Message.Builder(
-                        ConversationActions.Message.PERSON_USER_REMOTE)
+                        ConversationActions.Message.PERSON_USER_OTHERS)
                         .setText("Where are you?")
                         .build();
         TextClassifier.EntityConfig typeConfig =
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 3c35d9b..20303eb 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -562,7 +562,7 @@
   if (package != nullptr) {
     ToResourceName(last_resolution.type_string_ref,
                    last_resolution.entry_string_ref,
-                   package,
+                   package->GetPackageName(),
                    &resource_name);
     resource_name_string = ToFormattedResourceString(&resource_name);
   }
@@ -607,15 +607,25 @@
     return false;
   }
 
-  const LoadedPackage* package =
-      apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid));
-  if (package == nullptr) {
+  const uint8_t package_idx = package_ids_[get_package_id(resid)];
+  if (package_idx == 0xff) {
+    LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.",
+                                     get_package_id(resid), resid);
     return false;
   }
 
+  const PackageGroup& package_group = package_groups_[package_idx];
+  auto cookie_iter = std::find(package_group.cookies_.begin(),
+                               package_group.cookies_.end(), cookie);
+  if (cookie_iter == package_group.cookies_.end()) {
+    return false;
+  }
+
+  long package_pos = std::distance(package_group.cookies_.begin(), cookie_iter);
+  const LoadedPackage* package = package_group.packages_[package_pos].loaded_package_;
   return ToResourceName(entry.type_string_ref,
                         entry.entry_string_ref,
-                        package,
+                        package->GetPackageName(),
                         out_name);
 }
 
diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp
index 645984d..c63dff8 100644
--- a/libs/androidfw/ResourceUtils.cpp
+++ b/libs/androidfw/ResourceUtils.cpp
@@ -48,12 +48,12 @@
          !(has_type_separator && out_type->empty());
 }
 
-bool ToResourceName(StringPoolRef& type_string_ref,
-                    StringPoolRef& entry_string_ref,
-                    const LoadedPackage* package,
+bool ToResourceName(const StringPoolRef& type_string_ref,
+                    const StringPoolRef& entry_string_ref,
+                    const StringPiece& package_name,
                     AssetManager2::ResourceName* out_name) {
-  out_name->package = package->GetPackageName().data();
-  out_name->package_len = package->GetPackageName().size();
+  out_name->package = package_name.data();
+  out_name->package_len = package_name.size();
 
   out_name->type = type_string_ref.string8(&out_name->type_len);
   out_name->type16 = nullptr;
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index eb6eb8e..e649940 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -28,12 +28,11 @@
 bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
                          StringPiece* out_entry);
 
-// Convert a type_string_ref, entry_string_ref, and package
-// to AssetManager2::ResourceName. Useful for getting
-// resource name without re-running AssetManager2::FindEntry searches.
-bool ToResourceName(StringPoolRef& type_string_ref,
-                    StringPoolRef& entry_string_ref,
-                    const LoadedPackage* package,
+// Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName.
+// Useful for getting resource name without re-running AssetManager2::FindEntry searches.
+bool ToResourceName(const StringPoolRef& type_string_ref,
+                    const StringPoolRef& entry_string_ref,
+                    const StringPiece& package_name,
                     AssetManager2::ResourceName* out_name);
 
 // Formats a ResourceName to "package:type/entry_name".
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 105dcd2..447fdf5 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -210,6 +210,16 @@
   EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data);
 }
 
+TEST_F(AssetManager2Test, GetSharedLibraryResourceName) {
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({lib_one_assets_.get()});
+
+  AssetManager2::ResourceName name;
+  ASSERT_TRUE(assetmanager.GetResourceName(lib_one::R::string::foo, &name));
+  std::string formatted_name = ToFormattedResourceString(&name);
+  ASSERT_EQ(formatted_name, "com.android.lib_one:string/foo");
+}
+
 TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({basic_assets_.get()});
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index cc62fdc..54a91f4 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -682,12 +682,11 @@
                             float y, float boundsLeft, float boundsTop, float boundsRight,
                             float boundsBottom, float totalAdvance) {
     if (count <= 0 || paint.nothingToDraw()) return;
-    SkPaint paintCopy(paint);
+    Paint paintCopy(paint);
     if (mPaintFilter) {
-        mPaintFilter->filter(&paintCopy);
+        mPaintFilter->filterFullPaint(&paintCopy);
     }
-    SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy);
-    SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding);
+    const SkFont& font = paintCopy.getSkFont();
     // Stroke with a hairline is drawn on HW with a fill style for compatibility with Android O and
     // older.
     if (!mCanvasOwned && sApiLevel <= 27 && paintCopy.getStrokeWidth() <= 0 &&
@@ -710,12 +709,11 @@
 void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
                                   const Paint& paint, const SkPath& path, size_t start,
                                   size_t end) {
-    SkPaint paintCopy(paint);
+    Paint paintCopy(paint);
     if (mPaintFilter) {
-        mPaintFilter->filter(&paintCopy);
+        mPaintFilter->filterFullPaint(&paintCopy);
     }
-    SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy);
-    SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding);
+    const SkFont& font = paintCopy.getSkFont();
 
     const int N = end - start;
     SkTextBlobBuilder builder;
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 277148e..5231486 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -39,34 +39,28 @@
 }
 
 void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) {
-    uint32_t flags;
-    PaintFilter* paintFilter = getPaintFilter();
-    if (paintFilter) {
-        SkPaint paintCopy(paint);
-        paintFilter->filter(&paintCopy);
-        flags = paintCopy.getFlags();
-    } else {
-        flags = paint.getFlags();
-    }
-    if (flags & (SkPaint::kUnderlineText_ReserveFlag | SkPaint::kStrikeThruText_ReserveFlag)) {
+    // paint has already been filtered by our caller, so we can ignore any filter
+    const bool strikeThru = paint.isStrikeThru();
+    const bool underline = paint.isUnderline();
+    if (strikeThru || underline) {
         const SkScalar left = x;
         const SkScalar right = x + length;
-        if (flags & SkPaint::kUnderlineText_ReserveFlag) {
+        const float textSize = paint.getSkFont().getSize();
+        if (underline) {
             SkFontMetrics metrics;
-            paint.getFontMetrics(&metrics);
+            paint.getSkFont().getMetrics(&metrics);
             SkScalar position;
             if (!metrics.hasUnderlinePosition(&position)) {
-                position = paint.getTextSize() * Paint::kStdUnderline_Top;
+                position = textSize * Paint::kStdUnderline_Top;
             }
             SkScalar thickness;
             if (!metrics.hasUnderlineThickness(&thickness)) {
-                thickness = paint.getTextSize() * Paint::kStdUnderline_Thickness;
+                thickness = textSize * Paint::kStdUnderline_Thickness;
             }
             const SkScalar top = y + position;
             drawStroke(left, right, top, thickness, paint, this);
         }
-        if (flags & SkPaint::kStrikeThruText_ReserveFlag) {
-            const float textSize = paint.getTextSize();
+        if (strikeThru) {
             const float position = textSize * Paint::kStdStrikeThru_Top;
             const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness;
             const SkScalar top = y + position;
@@ -75,19 +69,19 @@
     }
 }
 
-static void simplifyPaint(int color, SkPaint* paint) {
+static void simplifyPaint(int color, Paint* paint) {
     paint->setColor(color);
     paint->setShader(nullptr);
     paint->setColorFilter(nullptr);
     paint->setLooper(nullptr);
-    paint->setStrokeWidth(4 + 0.04 * paint->getTextSize());
+    paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
     paint->setStrokeJoin(SkPaint::kRound_Join);
     paint->setLooper(nullptr);
 }
 
 class DrawTextFunctor {
 public:
-    DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const SkPaint& paint, float x,
+    DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
                     float y, minikin::MinikinRect& bounds, float totalAdvance)
             : layout(layout)
             , canvas(canvas)
@@ -123,14 +117,14 @@
             bool darken = channelSum < (128 * 3);
 
             // outline
-            SkPaint outlinePaint(paint);
+            Paint outlinePaint(paint);
             simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
             outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
             canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, bounds.mLeft, bounds.mTop,
                                bounds.mRight, bounds.mBottom, totalAdvance);
 
             // inner
-            SkPaint innerPaint(paint);
+            Paint innerPaint(paint);
             simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
             innerPaint.setStyle(SkPaint::kFill_Style);
             canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, bounds.mLeft, bounds.mTop,
@@ -145,7 +139,7 @@
 private:
     const minikin::Layout& layout;
     Canvas* canvas;
-    const SkPaint& paint;
+    const Paint& paint;
     float x;
     float y;
     minikin::MinikinRect& bounds;
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 84292c8..375f5bc 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -17,8 +17,9 @@
 #include "MinikinSkia.h"
 
 #include <SkFontDescriptor.h>
+#include <SkFont.h>
+#include <SkFontMetrics.h>
 #include <SkFontMgr.h>
-#include <SkPaint.h>
 #include <SkTypeface.h>
 #include <log/log.h>
 
@@ -40,25 +41,24 @@
         , mAxes(axes)
         , mFilePath(filePath) {}
 
-static void MinikinFontSkia_SetSkiaPaint(const minikin::MinikinFont* font, SkPaint* skPaint,
-                                         const minikin::MinikinPaint& paint,
-                                         const minikin::FontFakery& fakery) {
-    skPaint->setTextEncoding(kGlyphID_SkTextEncoding);
-    skPaint->setTextSize(paint.size);
-    skPaint->setTextScaleX(paint.scaleX);
-    skPaint->setTextSkewX(paint.skewX);
-    MinikinFontSkia::unpackPaintFlags(skPaint, paint.paintFlags);
+static void MinikinFontSkia_SetSkiaFont(const minikin::MinikinFont* font, SkFont* skFont,
+                                        const minikin::MinikinPaint& paint,
+                                        const minikin::FontFakery& fakery) {
+    skFont->setSize(paint.size);
+    skFont->setScaleX(paint.scaleX);
+    skFont->setSkewX(paint.skewX);
+    MinikinFontSkia::unpackFontFlags(skFont, paint.fontFlags);
     // Apply font fakery on top of user-supplied flags.
-    MinikinFontSkia::populateSkPaint(skPaint, font, fakery);
+    MinikinFontSkia::populateSkFont(skFont, font, fakery);
 }
 
 float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, const minikin::MinikinPaint& paint,
                                             const minikin::FontFakery& fakery) const {
-    SkPaint skPaint;
+    SkFont skFont;
     uint16_t glyph16 = glyph_id;
     SkScalar skWidth;
-    MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery);
-    skPaint.getTextWidths(&glyph16, sizeof(glyph16), &skWidth, NULL);
+    MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery);
+    skFont.getWidths(&glyph16, 1, &skWidth);
 #ifdef VERBOSE
     ALOGD("width for typeface %d glyph %d = %f", mTypeface->uniqueID(), glyph_id, skWidth);
 #endif
@@ -68,11 +68,11 @@
 void MinikinFontSkia::GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id,
                                 const minikin::MinikinPaint& paint,
                                 const minikin::FontFakery& fakery) const {
-    SkPaint skPaint;
+    SkFont skFont;
     uint16_t glyph16 = glyph_id;
     SkRect skBounds;
-    MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery);
-    skPaint.getTextWidths(&glyph16, sizeof(glyph16), NULL, &skBounds);
+    MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery);
+    skFont.getWidths(&glyph16, 1, nullptr, &skBounds);
     bounds->mLeft = skBounds.fLeft;
     bounds->mTop = skBounds.fTop;
     bounds->mRight = skBounds.fRight;
@@ -82,10 +82,10 @@
 void MinikinFontSkia::GetFontExtent(minikin::MinikinExtent* extent,
                                     const minikin::MinikinPaint& paint,
                                     const minikin::FontFakery& fakery) const {
-    SkPaint skPaint;
-    MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery);
+    SkFont skFont;
+    MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery);
     SkFontMetrics metrics;
-    skPaint.getFontMetrics(&metrics);
+    skFont.getMetrics(&metrics);
     extent->ascent = metrics.fAscent;
     extent->descent = metrics.fDescent;
 }
@@ -137,28 +137,36 @@
                                              ttcIndex, variations);
 }
 
-uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) {
-    uint32_t flags = paint->getFlags();
-    unsigned hinting = static_cast<unsigned>(paint->getHinting());
-    // select only flags that might affect text layout
-    flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag |
-              SkPaint::kSubpixelText_Flag | SkPaint::kEmbeddedBitmapText_Flag |
-              SkPaint::kAutoHinting_Flag);
-    flags |= (hinting << 16);
+// hinting<<16 | edging<<8 | bools:5bits
+uint32_t MinikinFontSkia::packFontFlags(const SkFont& font) {
+    uint32_t flags = (unsigned)font.getHinting() << 16;
+    flags |= (unsigned)font.getEdging() << 8;
+    flags |= font.isEmbolden()          << minikin::Embolden_Shift;
+    flags |= font.isLinearMetrics()     << minikin::LinearMetrics_Shift;
+    flags |= font.isSubpixel()          << minikin::Subpixel_Shift;
+    flags |= font.isEmbeddedBitmaps()   << minikin::EmbeddedBitmaps_Shift;
+    flags |= font.isForceAutoHinting()  << minikin::ForceAutoHinting_Shift;
     return flags;
 }
 
-void MinikinFontSkia::unpackPaintFlags(SkPaint* paint, uint32_t paintFlags) {
-    paint->setFlags(paintFlags & SkPaint::kAllFlags);
-    paint->setHinting(static_cast<SkFontHinting>(paintFlags >> 16));
+void MinikinFontSkia::unpackFontFlags(SkFont* font, uint32_t flags) {
+    // We store hinting in the top 16 bits (only need 2 of them)
+    font->setHinting((SkFontHinting)(flags >> 16));
+    // We store edging in bits 8:15 (only need 2 of them)
+    font->setEdging((SkFont::Edging)((flags >> 8) & 0xFF));
+    font->setEmbolden(        (flags & minikin::Embolden_Flag) != 0);
+    font->setLinearMetrics(   (flags & minikin::LinearMetrics_Flag) != 0);
+    font->setSubpixel(        (flags & minikin::Subpixel_Flag) != 0);
+    font->setEmbeddedBitmaps( (flags & minikin::EmbeddedBitmaps_Flag) != 0);
+    font->setForceAutoHinting((flags & minikin::ForceAutoHinting_Flag) != 0);
 }
 
-void MinikinFontSkia::populateSkPaint(SkPaint* paint, const MinikinFont* font,
-                                      minikin::FontFakery fakery) {
-    paint->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface());
-    paint->setFakeBoldText(paint->isFakeBoldText() || fakery.isFakeBold());
+void MinikinFontSkia::populateSkFont(SkFont* skFont, const MinikinFont* font,
+                                     minikin::FontFakery fakery) {
+    skFont->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface());
+    skFont->setEmbolden(skFont->isEmbolden() || fakery.isFakeBold());
     if (fakery.isFakeItalic()) {
-        paint->setTextSkewX(paint->getTextSkewX() - 0.25f);
+        skFont->setSkewX(skFont->getSkewX() - 0.25f);
     }
 }
 }
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index 55576b7..ad46b23 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -21,7 +21,7 @@
 #include <cutils/compiler.h>
 #include <minikin/MinikinFont.h>
 
-class SkPaint;
+class SkFont;
 class SkTypeface;
 
 namespace android {
@@ -54,12 +54,12 @@
     std::shared_ptr<minikin::MinikinFont> createFontWithVariation(
             const std::vector<minikin::FontVariation>&) const;
 
-    static uint32_t packPaintFlags(const SkPaint* paint);
-    static void unpackPaintFlags(SkPaint* paint, uint32_t paintFlags);
+    static uint32_t packFontFlags(const SkFont&);
+    static void unpackFontFlags(SkFont*, uint32_t fontFlags);
 
     // set typeface and fake bold/italic parameters
-    static void populateSkPaint(SkPaint* paint, const minikin::MinikinFont* font,
-                                minikin::FontFakery fakery);
+    static void populateSkFont(SkFont*, const minikin::MinikinFont* font,
+                               minikin::FontFakery fakery);
 
 private:
     sk_sp<SkTypeface> mTypeface;
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index ba240fe..733f8e4 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -30,16 +30,17 @@
 minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint,
                                                         const Typeface* typeface) {
     const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
+    const SkFont& font = paint->getSkFont();
 
     minikin::MinikinPaint minikinPaint(resolvedFace->fFontCollection);
     /* Prepare minikin Paint */
     minikinPaint.size =
-            paint->isLinearText() ? paint->getTextSize() : static_cast<int>(paint->getTextSize());
-    minikinPaint.scaleX = paint->getTextScaleX();
-    minikinPaint.skewX = paint->getTextSkewX();
+            font.isLinearMetrics() ? font.getSize() : static_cast<int>(font.getSize());
+    minikinPaint.scaleX = font.getScaleX();
+    minikinPaint.skewX = font.getSkewX();
     minikinPaint.letterSpacing = paint->getLetterSpacing();
     minikinPaint.wordSpacing = paint->getWordSpacing();
-    minikinPaint.paintFlags = MinikinFontSkia::packPaintFlags(paint);
+    minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font);
     minikinPaint.localeListId = paint->getMinikinLocaleListId();
     minikinPaint.familyVariant = paint->getFamilyVariant();
     minikinPaint.fontStyle = resolvedFace->fStyle;
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index d27d544..cbf4095 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -63,27 +63,29 @@
     // f is a functor of type void f(size_t start, size_t end);
     template <typename F>
     ANDROID_API static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) {
-        float saveSkewX = paint->getTextSkewX();
-        bool savefakeBold = paint->isFakeBoldText();
+        float saveSkewX = paint->getSkFont().getSkewX();
+        bool savefakeBold = paint->getSkFont().isEmbolden();
         const minikin::MinikinFont* curFont = nullptr;
         size_t start = 0;
         size_t nGlyphs = layout.nGlyphs();
         for (size_t i = 0; i < nGlyphs; i++) {
             const minikin::MinikinFont* nextFont = layout.getFont(i);
             if (i > 0 && nextFont != curFont) {
-                MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start));
+                SkFont* skfont = &paint->getSkFont();
+                MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
                 f(start, i);
-                paint->setTextSkewX(saveSkewX);
-                paint->setFakeBoldText(savefakeBold);
+                skfont->setSkewX(saveSkewX);
+                skfont->setEmbolden(savefakeBold);
                 start = i;
             }
             curFont = nextFont;
         }
         if (nGlyphs > start) {
-            MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start));
+            SkFont* skfont = &paint->getSkFont();
+            MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
             f(start, nGlyphs);
-            paint->setTextSkewX(saveSkewX);
-            paint->setFakeBoldText(savefakeBold);
+            skfont->setSkewX(saveSkewX);
+            skfont->setEmbolden(savefakeBold);
         }
     }
 };
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 92ffda9..601b3c2 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -21,6 +21,7 @@
 
 #include <cutils/compiler.h>
 
+#include <SkFont.h>
 #include <SkPaint.h>
 #include <string>
 
@@ -46,7 +47,6 @@
 
     Paint();
     Paint(const Paint& paint);
-    Paint(const SkPaint& paint);  // NOLINT(google-explicit-constructor)
     ~Paint();
 
     Paint& operator=(const Paint& other);
@@ -54,6 +54,17 @@
     friend bool operator==(const Paint& a, const Paint& b);
     friend bool operator!=(const Paint& a, const Paint& b) { return !(a == b); }
 
+    SkFont& getSkFont() { return mFont; }
+    const SkFont& getSkFont() const { return mFont; }
+
+    // These shadow the methods on SkPaint, but we need to so we can keep related
+    // attributes in-sync.
+
+    void reset();
+    void setAntiAlias(bool);
+
+    // End method shadowing
+
     void setLetterSpacing(float letterSpacing) { mLetterSpacing = letterSpacing; }
 
     float getLetterSpacing() const { return mLetterSpacing; }
@@ -94,7 +105,31 @@
     Align getTextAlign() const { return mAlign; }
     void setTextAlign(Align align) { mAlign = align; }
 
+    bool isStrikeThru() const { return mStrikeThru; }
+    void setStrikeThru(bool st) { mStrikeThru = st; }
+
+    bool isUnderline() const { return mUnderline; }
+    void setUnderline(bool u) { mUnderline = u; }
+
+    bool isDevKern() const { return mDevKern; }
+    void setDevKern(bool d) { mDevKern = d; }
+
+    // The Java flags (Paint.java) no longer fit into the native apis directly.
+    // These methods handle converting to and from them and the native representations
+    // in android::Paint.
+
+    uint32_t getJavaFlags() const;
+    void setJavaFlags(uint32_t);
+
+    // Helpers that return or apply legacy java flags to SkPaint, ignoring all flags
+    // that are meant for SkFont or Paint (e.g. underline, strikethru)
+    // The only respected flags are : [ antialias, dither, filterBitmap ]
+    static uint32_t GetSkPaintJavaFlags(const SkPaint&);
+    static void SetSkPaintJavaFlags(SkPaint*, uint32_t flags);
+ 
 private:
+    SkFont mFont;
+
     float mLetterSpacing = 0;
     float mWordSpacing = 0;
     std::string mFontFeatureSettings;
@@ -107,6 +142,9 @@
     // nullptr is valid: it means the default typeface.
     const Typeface* mTypeface = nullptr;
     Align mAlign = kLeft_Align;
+    bool mStrikeThru = false;
+    bool mUnderline = false;
+    bool mDevKern = false;
 };
 
 }  // namespace android
diff --git a/libs/hwui/hwui/PaintFilter.h b/libs/hwui/hwui/PaintFilter.h
index bf5627e..0e7b619 100644
--- a/libs/hwui/hwui/PaintFilter.h
+++ b/libs/hwui/hwui/PaintFilter.h
@@ -12,6 +12,7 @@
      *  The implementation may modify the paint as they wish.
      */
     virtual void filter(SkPaint*) = 0;
+    virtual void filterFullPaint(Paint*) = 0;
 };
 
 } // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index bdbf5ca..d2903f0 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -24,10 +24,16 @@
         , mWordSpacing(0)
         , mFontFeatureSettings()
         , mMinikinLocaleListId(0)
-        , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {}
+        , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {
+    // SkPaint::antialiasing defaults to false, but
+    // SkFont::edging defaults to kAntiAlias. To keep them
+    // insync, we manually set the font to kAilas.
+    mFont.setEdging(SkFont::Edging::kAlias);
+}
 
 Paint::Paint(const Paint& paint)
         : SkPaint(paint)
+        , mFont(paint.mFont)
         , mLetterSpacing(paint.mLetterSpacing)
         , mWordSpacing(paint.mWordSpacing)
         , mFontFeatureSettings(paint.mFontFeatureSettings)
@@ -35,20 +41,17 @@
         , mFamilyVariant(paint.mFamilyVariant)
         , mHyphenEdit(paint.mHyphenEdit)
         , mTypeface(paint.mTypeface)
-        , mAlign(paint.mAlign) {}
+        , mAlign(paint.mAlign)
+        , mStrikeThru(paint.mStrikeThru)
+        , mUnderline(paint.mUnderline)
+        , mDevKern(paint.mDevKern) {}
 
-Paint::Paint(const SkPaint& paint)
-        : SkPaint(paint)
-        , mLetterSpacing(0)
-        , mWordSpacing(0)
-        , mFontFeatureSettings()
-        , mMinikinLocaleListId(0)
-        , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {}
 
 Paint::~Paint() {}
 
 Paint& Paint::operator=(const Paint& other) {
     SkPaint::operator=(other);
+    mFont = other.mFont;
     mLetterSpacing = other.mLetterSpacing;
     mWordSpacing = other.mWordSpacing;
     mFontFeatureSettings = other.mFontFeatureSettings;
@@ -57,15 +60,136 @@
     mHyphenEdit = other.mHyphenEdit;
     mTypeface = other.mTypeface;
     mAlign = other.mAlign;
+    mStrikeThru = other.mStrikeThru;
+    mUnderline = other.mUnderline;
+    mDevKern = other.mDevKern;
     return *this;
 }
 
 bool operator==(const Paint& a, const Paint& b) {
     return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
+           a.mFont == b.mFont &&
            a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing &&
            a.mFontFeatureSettings == b.mFontFeatureSettings &&
            a.mMinikinLocaleListId == b.mMinikinLocaleListId &&
            a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
-           a.mTypeface == b.mTypeface && a.mAlign == b.mAlign;
+           a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
+           a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline &&
+           a.mDevKern == b.mDevKern;
 }
+
+void Paint::reset() {
+    SkPaint::reset();
+
+    mFont = SkFont();
+    mFont.setEdging(SkFont::Edging::kAlias);
+
+    mStrikeThru = false;
+    mUnderline = false;
+    mDevKern = false;
+}
+
+void Paint::setAntiAlias(bool aa) {
+    // Java does not support/understand subpixel(lcd) antialiasing
+    SkASSERT(mFont.getEdging() != SkFont::Edging::kSubpixelAntiAlias);
+    // JavaPaint antialiasing affects both the SkPaint and SkFont settings.
+    SkPaint::setAntiAlias(aa);
+    mFont.setEdging(aa ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias);
+}
+
+////////////////// Java flags compatibility //////////////////
+
+/*	Flags are tricky. Java has its own idea of the "paint" flags, but they don't really
+    match up with skia anymore, so we have to do some shuffling in get/set flags()
+
+	3 flags apply to SkPaint (antialias, dither, filter -> enum)
+    5 flags (merged with antialias) are for SkFont
+    2 flags are for minikin::Paint (underline and strikethru)
+*/
+
+// flags relating to SkPaint
+static const uint32_t sAntiAliasFlag    = 0x01;   // affects paint and font-edging
+static const uint32_t sFilterBitmapFlag = 0x02;   // maps to enum
+static const uint32_t sDitherFlag       = 0x04;
+// flags relating to SkFont
+static const uint32_t sFakeBoldFlag     = 0x020;
+static const uint32_t sLinearMetrics    = 0x040;
+static const uint32_t sSubpixelMetrics  = 0x080;
+static const uint32_t sEmbeddedBitmaps  = 0x400;
+static const uint32_t sForceAutoHinting = 0x800;
+// flags related to minikin::Paint
+static const uint32_t sUnderlineFlag    = 0x08;
+static const uint32_t sStrikeThruFlag   = 0x10;
+// flags no longer supported on native side (but mirrored for compatibility)
+static const uint32_t sDevKernFlag      = 0x100;
+
+static uint32_t paintToLegacyFlags(const SkPaint& paint) {
+    uint32_t flags = 0;
+    flags |= -(int)paint.isAntiAlias() & sAntiAliasFlag;
+    flags |= -(int)paint.isDither()    & sDitherFlag;
+    if (paint.getFilterQuality() != kNone_SkFilterQuality) {
+        flags |= sFilterBitmapFlag;
+    }
+    return flags;
+}
+
+static uint32_t fontToLegacyFlags(const SkFont& font) {
+    uint32_t flags = 0;
+    flags |= -(int)font.isEmbolden()         & sFakeBoldFlag;
+    flags |= -(int)font.isLinearMetrics()    & sLinearMetrics;
+    flags |= -(int)font.isSubpixel()         & sSubpixelMetrics;
+    flags |= -(int)font.isEmbeddedBitmaps()  & sEmbeddedBitmaps;
+    flags |= -(int)font.isForceAutoHinting() & sForceAutoHinting;
+    return flags;
+}
+
+static void applyLegacyFlagsToPaint(uint32_t flags, SkPaint* paint) {
+    paint->setAntiAlias((flags & sAntiAliasFlag) != 0);
+    paint->setDither   ((flags & sDitherFlag) != 0);
+
+    if (flags & sFilterBitmapFlag) {
+        paint->setFilterQuality(kLow_SkFilterQuality);
+    } else {
+        paint->setFilterQuality(kNone_SkFilterQuality);
+    }
+}
+
+static void applyLegacyFlagsToFont(uint32_t flags, SkFont* font) {
+    font->setEmbolden        ((flags & sFakeBoldFlag) != 0);
+    font->setLinearMetrics   ((flags & sLinearMetrics) != 0);
+    font->setSubpixel        ((flags & sSubpixelMetrics) != 0);
+    font->setEmbeddedBitmaps ((flags & sEmbeddedBitmaps) != 0);
+    font->setForceAutoHinting((flags & sForceAutoHinting) != 0);
+
+    if (flags & sAntiAliasFlag) {
+        font->setEdging(SkFont::Edging::kAntiAlias);
+    } else {
+        font->setEdging(SkFont::Edging::kAlias);
+    }
+}
+
+uint32_t Paint::GetSkPaintJavaFlags(const SkPaint& paint) {
+    return paintToLegacyFlags(paint);
+}
+
+void Paint::SetSkPaintJavaFlags(SkPaint* paint, uint32_t flags) {
+    applyLegacyFlagsToPaint(flags, paint);
+}
+
+uint32_t Paint::getJavaFlags() const {
+    uint32_t flags = paintToLegacyFlags(*this) | fontToLegacyFlags(mFont);
+    flags |= -(int)mStrikeThru & sStrikeThruFlag;
+    flags |= -(int)mUnderline  & sUnderlineFlag;
+    flags |= -(int)mDevKern    & sDevKernFlag;
+    return flags;
+}
+
+void Paint::setJavaFlags(uint32_t flags) {
+    applyLegacyFlagsToPaint(flags, this);
+    applyLegacyFlagsToFont(flags, &mFont);
+    mStrikeThru = (flags & sStrikeThruFlag) != 0;
+    mUnderline  = (flags & sUnderlineFlag) != 0;
+    mDevKern    = (flags & sDevKernFlag) != 0;
+}
+
 }  // namespace android
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 60c8057..a1b2b18 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -138,6 +138,7 @@
     info.width = fboSize.width();
     info.height = fboSize.height();
     mat4.asColMajorf(&info.transform[0]);
+    info.color_space_ptr = canvas->imageInfo().colorSpace();
 
     // ensure that the framebuffer that the webview will render into is bound before we clear
     // the stencil and/or draw the functor.
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 1661905..8508274 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -31,8 +31,8 @@
 
 // Cache size limits.
 static const size_t maxKeySize = 1024;
-static const size_t maxValueSize = 64 * 1024;
-static const size_t maxTotalSize = 512 * 1024;
+static const size_t maxValueSize = 512 * 1024;
+static const size_t maxTotalSize = 1024 * 1024;
 
 ShaderCache::ShaderCache() {
     // There is an "incomplete FileBlobCache type" compilation error, if ctor is moved to header.
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index c3563db..706325f 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -132,6 +132,7 @@
         info.width = mFBInfo.width();
         info.height = mFBInfo.height();
         mat4.asColMajorf(&info.transform[0]);
+        info.color_space_ptr = canvas->imageInfo().colorSpace();
 
         glViewport(0, 0, info.width, info.height);
 
@@ -179,8 +180,8 @@
     canvas->resetMatrix();
 
     auto functorImage = SkImage::MakeFromAHardwareBuffer(
-            reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType, nullptr,
-            kBottomLeft_GrSurfaceOrigin);
+            reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType,
+            canvas->imageInfo().refColorSpace(), kBottomLeft_GrSurfaceOrigin);
     canvas->drawImage(functorImage, 0, 0, &paint);
     canvas->restore();
 }
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index 9e1bb8e..501b8df 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_HWUI_DRAW_GL_INFO_H
 #define ANDROID_HWUI_DRAW_GL_INFO_H
 
+#include <SkColorSpace.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -41,6 +43,9 @@
     // Input: current transform matrix, in OpenGL format
     float transform[16];
 
+    // Input: Color space.
+    const SkColorSpace* color_space_ptr;
+
     // Output: dirty region to redraw
     float dirtyLeft;
     float dirtyTop;
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 16a27598..a9f651d 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -78,24 +78,21 @@
     return layerUpdater;
 }
 
-void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x,
+void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x,
                                  float y) {
     auto utf16 = asciiToUtf16(text);
     uint32_t length = strlen(text);
-    Paint glyphPaint(paint);
-    glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding);
+
     canvas->drawText(utf16.get(), length,  // text buffer
                      0, length,            // draw range
                      0, length,            // context range
-                     x, y, minikin::Bidi::LTR, glyphPaint, nullptr, nullptr /* measured text */);
+                     x, y, minikin::Bidi::LTR, paint, nullptr, nullptr /* measured text */);
 }
 
-void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint,
+void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint,
                                  const SkPath& path) {
     auto utf16 = asciiToUtf16(text);
-    Paint glyphPaint(paint);
-    glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding);
-    canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint,
+    canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, paint,
                            nullptr);
 }
 
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 6a1ca5a..e7124df 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -278,10 +278,10 @@
 
     static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
 
-    static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x,
+    static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x,
                                  float y);
 
-    static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint,
+    static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint,
                                  const SkPath& path);
 
     static std::unique_ptr<uint16_t[]> asciiToUtf16(const char* str);
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
index f0a5e9d..0795d13 100644
--- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -51,7 +51,7 @@
         paint.setAntiAlias(true);
         paint.setColor(Color::Black);
         for (int i = 0; i < 5; i++) {
-            paint.setTextSize(10 + (frameNr % 20) + i * 20);
+            paint.getSkFont().setSize(10 + (frameNr % 20) + i * 20);
             TestUtils::drawUtf8ToCanvas(canvas.get(), text, paint, 0, 100 * (i + 2));
         }
 
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index 58c9980..ecaaf48 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -16,7 +16,7 @@
 
 #include "TestSceneBase.h"
 #include "tests/common/TestListViewSceneBase.h"
-
+#include "hwui/Paint.h"
 #include <SkGradientShader.h>
 
 class ListOfFadedTextAnimation;
@@ -33,8 +33,8 @@
         canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
         int length = dp(100);
         canvas.saveLayer(0, 0, length, itemHeight, nullptr, SaveFlags::HasAlphaLayer);
-        SkPaint textPaint;
-        textPaint.setTextSize(dp(20));
+        Paint textPaint;
+        textPaint.getSkFont().setSize(dp(20));
         textPaint.setAntiAlias(true);
         TestUtils::drawUtf8ToCanvas(&canvas, "not that long long text", textPaint, dp(10), dp(30));
 
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index 4111bd2..feb881f 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -16,6 +16,7 @@
 
 #include "TestSceneBase.h"
 #include "tests/common/TestListViewSceneBase.h"
+#include "hwui/Paint.h"
 #include <SkFont.h>
 #include <cstdio>
 
@@ -83,14 +84,14 @@
         roundRectPaint.setColor(Color::White);
         canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint);
 
-        SkPaint textPaint;
+        Paint textPaint;
         textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500);
-        textPaint.setTextSize(dp(20));
+        textPaint.getSkFont().setSize(dp(20));
         textPaint.setAntiAlias(true);
         char buf[256];
         snprintf(buf, sizeof(buf), "This card is #%d", cardId);
         TestUtils::drawUtf8ToCanvas(&canvas, buf, textPaint, itemHeight, dp(25));
-        textPaint.setTextSize(dp(15));
+        textPaint.getSkFont().setSize(dp(15));
         TestUtils::drawUtf8ToCanvas(&canvas, "This is some more text on the card", textPaint,
                                     itemHeight, dp(45));
 
diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
index aa537b4..f6cff1c 100644
--- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
@@ -17,6 +17,7 @@
 #include "TestSceneBase.h"
 #include "renderthread/RenderProxy.h"
 #include "utils/Color.h"
+#include "hwui/Paint.h"
 
 class MagnifierAnimation;
 
@@ -37,9 +38,9 @@
         canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
         card = TestUtils::createNode(
                 0, 0, width, height, [&](RenderProperties& props, Canvas& canvas) {
-                    SkPaint paint;
+                    Paint paint;
                     paint.setAntiAlias(true);
-                    paint.setTextSize(50);
+                    paint.getSkFont().setSize(50);
 
                     paint.setColor(Color::Black);
                     TestUtils::drawUtf8ToCanvas(&canvas, "Test string", paint, 10, 400);
diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
index 3befce4..8630be8 100644
--- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
@@ -41,9 +41,9 @@
         int top = bounds.fTop;
 
         mBluePaint.setColor(SkColorSetARGB(255, 0, 0, 255));
-        mBluePaint.setTextSize(padding);
+        mBluePaint.getSkFont().setSize(padding);
         mGreenPaint.setColor(SkColorSetARGB(255, 0, 255, 0));
-        mGreenPaint.setTextSize(padding);
+        mGreenPaint.getSkFont().setSize(padding);
 
         // interleave drawText and drawRect with saveLayer ops
         for (int i = 0; i < regions; i++, top += smallRectHeight) {
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
index a16b1784..d309036 100644
--- a/libs/hwui/tests/common/scenes/TextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "TestSceneBase.h"
+#include "hwui/Paint.h"
 
 class TextAnimation;
 
@@ -28,9 +29,9 @@
         canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
         card = TestUtils::createNode(0, 0, width, height, [](RenderProperties& props,
                                                              Canvas& canvas) {
-            SkPaint paint;
+            Paint paint;
             paint.setAntiAlias(true);
-            paint.setTextSize(50);
+            paint.getSkFont().setSize(50);
 
             paint.setColor(Color::Black);
             for (int i = 0; i < 10; i++) {
diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp
index 286f5f1..229c7f3 100644
--- a/libs/hwui/tests/common/scenes/TvApp.cpp
+++ b/libs/hwui/tests/common/scenes/TvApp.cpp
@@ -17,6 +17,7 @@
 #include "SkBlendMode.h"
 #include "TestSceneBase.h"
 #include "tests/common/BitmapAllocationTestUtils.h"
+#include "hwui/Paint.h"
 
 class TvApp;
 class TvAppNoRoundedCorner;
@@ -116,13 +117,13 @@
                                      [text, text2](RenderProperties& props, Canvas& canvas) {
                                          canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver);
 
-                                         SkPaint paint;
+                                         Paint paint;
                                          paint.setAntiAlias(true);
-                                         paint.setTextSize(24);
+                                         paint.getSkFont().setSize(24);
 
                                          paint.setColor(Color::Black);
                                          TestUtils::drawUtf8ToCanvas(&canvas, text, paint, 10, 30);
-                                         paint.setTextSize(20);
+                                         paint.getSkFont().setSize(20);
                                          TestUtils::drawUtf8ToCanvas(&canvas, text2, paint, 10, 54);
 
                                      });
diff --git a/media/Android.bp b/media/Android.bp
index 88ed9c6a..0675a36 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -132,7 +132,6 @@
         "apex/java/android/media/Session2Command.java",
         "apex/java/android/media/Session2CommandGroup.java",
         "apex/java/android/media/Session2Link.java",
-        "apex/java/android/media/Session2Token.java",
     ],
 }
 
diff --git a/media/apex/java/android/media/MediaConstants.java b/media/apex/java/android/media/MediaConstants.java
index 65b6f55..45ea826 100644
--- a/media/apex/java/android/media/MediaConstants.java
+++ b/media/apex/java/android/media/MediaConstants.java
@@ -24,7 +24,8 @@
     static final String KEY_PACKAGE_NAME = "android.media.key.PACKAGE_NAME";
 
     // Bundle key for Parcelable
-    static final String KEY_SESSION2LINK = "android.media.key.SESSION2LINK";
+    static final String KEY_SESSION2_TOKEN = "android.media.key.SESSION2_TOKEN";
+    static final String KEY_SESSION2_LINK = "android.media.key.SESSION2_LINK";
     static final String KEY_ALLOWED_COMMANDS = "android.media.key.ALLOWED_COMMANDS";
     static final String KEY_PLAYBACK_ACTIVE = "android.media.key.PLAYBACK_ACTIVE";
 
diff --git a/media/apex/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java
index 887b447..41721f3 100644
--- a/media/apex/java/android/media/MediaController2.java
+++ b/media/apex/java/android/media/MediaController2.java
@@ -20,9 +20,11 @@
 import static android.media.MediaConstants.KEY_PACKAGE_NAME;
 import static android.media.MediaConstants.KEY_PID;
 import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE;
-import static android.media.MediaConstants.KEY_SESSION2LINK;
+import static android.media.MediaConstants.KEY_SESSION2_LINK;
+import static android.media.MediaConstants.KEY_SESSION2_TOKEN;
 import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR;
 import static android.media.Session2Command.RESULT_INFO_SKIPPED;
+import static android.media.Session2Token.SESSION_SERVICE_INTERFACE;
 import static android.media.Session2Token.TYPE_SESSION;
 
 import android.annotation.NonNull;
@@ -260,7 +262,8 @@
 
     // Called by Controller2Link.onConnected
     void onConnected(int seq, Bundle connectionResult) {
-        Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2LINK);
+        Session2Token token = connectionResult.getParcelable(KEY_SESSION2_TOKEN);
+        Session2Link sessionBinder = token.getExtras().getParcelable(KEY_SESSION2_LINK);
         Session2CommandGroup allowedCommands =
                 connectionResult.getParcelable(KEY_ALLOWED_COMMANDS);
         boolean playbackActive = connectionResult.getBoolean(KEY_PLAYBACK_ACTIVE);
@@ -281,8 +284,7 @@
             // Implementation for the local binder is no-op,
             // so can be used without worrying about deadlock.
             sessionBinder.linkToDeath(mDeathRecipient, 0);
-            mConnectedToken = new Session2Token(mSessionToken.getUid(), TYPE_SESSION,
-                    mSessionToken.getPackageName(), sessionBinder);
+            mConnectedToken = token;
         }
         mCallbackExecutor.execute(() -> {
             mCallback.onConnected(MediaController2.this, allowedCommands);
@@ -353,7 +355,7 @@
     }
 
     private boolean requestConnectToSession() {
-        Session2Link sessionBinder = mSessionToken.getSessionLink();
+        Session2Link sessionBinder = mSessionToken.getExtras().getParcelable(KEY_SESSION2_LINK);
         Bundle connectionRequest = createConnectionRequest();
         try {
             sessionBinder.connect(mControllerStub, getNextSeqNumber(), connectionRequest);
@@ -366,7 +368,7 @@
 
     private boolean requestConnectToService() {
         // Service. Needs to get fresh binder whenever connection is needed.
-        final Intent intent = new Intent(MediaSession2Service.SERVICE_INTERFACE);
+        final Intent intent = new Intent(SESSION_SERVICE_INTERFACE);
         intent.setClassName(mSessionToken.getPackageName(), mSessionToken.getServiceName());
 
         // Use bindService() instead of startForegroundService() to start session service for three
diff --git a/media/apex/java/android/media/MediaSession2.java b/media/apex/java/android/media/MediaSession2.java
index fdd07fd..80c91cc 100644
--- a/media/apex/java/android/media/MediaSession2.java
+++ b/media/apex/java/android/media/MediaSession2.java
@@ -20,10 +20,10 @@
 import static android.media.MediaConstants.KEY_PACKAGE_NAME;
 import static android.media.MediaConstants.KEY_PID;
 import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE;
-import static android.media.MediaConstants.KEY_SESSION2LINK;
+import static android.media.MediaConstants.KEY_SESSION2_LINK;
+import static android.media.MediaConstants.KEY_SESSION2_TOKEN;
 import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR;
 import static android.media.Session2Command.RESULT_INFO_SKIPPED;
-import static android.media.Session2Token.TYPE_SESSION;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -34,7 +34,6 @@
 import android.media.session.MediaSessionManager.RemoteUserInfo;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Process;
 import android.os.ResultReceiver;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -108,8 +107,10 @@
         mCallbackExecutor = callbackExecutor;
         mCallback = callback;
         mSessionStub = new Session2Link(this);
-        mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(),
-                mSessionStub);
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(KEY_SESSION2_LINK, mSessionStub);
+        mSessionToken = new Session2Token(context, id, extras);
         mSessionManager = (MediaSessionManager) mContext.getSystemService(
                 Context.MEDIA_SESSION_SERVICE);
         // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked.
@@ -141,6 +142,8 @@
             for (ControllerInfo info : controllerInfos) {
                 info.notifyDisconnected();
             }
+            mSessionToken.destroy();
+            mSessionManager.notifySession2Destroyed(mSessionToken);
         } catch (Exception e) {
             // Should not be here.
         }
@@ -328,7 +331,7 @@
                 // It's needed because we cannot call synchronous calls between
                 // session/controller.
                 Bundle connectionResult = new Bundle();
-                connectionResult.putParcelable(KEY_SESSION2LINK, mSessionStub);
+                connectionResult.putParcelable(KEY_SESSION2_TOKEN, mSessionToken);
                 connectionResult.putParcelable(KEY_ALLOWED_COMMANDS,
                         controllerInfo.mAllowedCommands);
                 connectionResult.putBoolean(KEY_PLAYBACK_ACTIVE, isPlaybackActive());
diff --git a/media/apex/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java
index 5bb746a..f18cd31 100644
--- a/media/apex/java/android/media/MediaSession2Service.java
+++ b/media/apex/java/android/media/MediaSession2Service.java
@@ -16,6 +16,8 @@
 
 package android.media;
 
+import static android.media.Session2Token.SESSION_SERVICE_INTERFACE;
+
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -45,10 +47,6 @@
  * for consistent behavior across all devices.
  */
 public abstract class MediaSession2Service extends Service {
-    /**
-     * The {@link Intent} that must be declared as handled by the service.
-     */
-    public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
 
     private static final String TAG = "MediaSession2Service";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -100,7 +98,7 @@
     @Override
     @Nullable
     public IBinder onBind(@NonNull Intent intent) {
-        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+        if (SESSION_SERVICE_INTERFACE.equals(intent.getAction())) {
             synchronized (mLock) {
                 return mStub;
             }
diff --git a/media/apex/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java
similarity index 70%
rename from media/apex/java/android/media/Session2Token.java
rename to media/java/android/media/Session2Token.java
index 238cc2b..80494ad 100644
--- a/media/apex/java/android/media/Session2Token.java
+++ b/media/java/android/media/Session2Token.java
@@ -19,13 +19,17 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.media.session.MediaSessionManager;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.Process;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -35,7 +39,7 @@
 import java.util.Objects;
 
 /**
- * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}.
+ * Represents an ongoing MediaSession2 or a MediaSession2Service.
  * If it's representing a session service, it may not be ongoing.
  * <p>
  * This API is not generally intended for third party application developers.
@@ -44,7 +48,7 @@
  * for consistent behavior across all devices.
  * <p>
  * This may be passed to apps by the session owner to allow them to create a
- * {@link MediaController2} to communicate with the session.
+ * MediaController2 to communicate with the session.
  * <p>
  * It can be also obtained by {@link android.media.session.MediaSessionManager}.
  */
@@ -64,6 +68,13 @@
     };
 
     /**
+     * The {@link Intent} that must be declared for the session service.
+     * @hide
+     */
+    @SystemApi
+    public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service";
+
+    /**
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
@@ -72,22 +83,26 @@
     }
 
     /**
-     * Type for {@link MediaSession2}.
+     * Type for MediaSession2.
      */
     public static final int TYPE_SESSION = 0;
 
     /**
-     * Type for {@link MediaSession2Service}.
+     * Type for MediaSession2Service.
      */
     public static final int TYPE_SESSION_SERVICE = 1;
 
+    private final String mSessionId;
+    private final int mPid;
     private final int mUid;
     @TokenType
     private final int mType;
     private final String mPackageName;
     private final String mServiceName;
-    private final Session2Link mSessionLink;
     private final ComponentName mComponentName;
+    private final Bundle mExtras;
+
+    private boolean mDestroyed = false;
 
     /**
      * Constructor for the token with type {@link #TYPE_SESSION_SERVICE}.
@@ -106,44 +121,67 @@
         final PackageManager manager = context.getPackageManager();
         final int uid = getUid(manager, serviceComponent.getPackageName());
 
-        if (!isInterfaceDeclared(manager, MediaSession2Service.SERVICE_INTERFACE,
-                serviceComponent)) {
+        if (!isInterfaceDeclared(manager, SESSION_SERVICE_INTERFACE, serviceComponent)) {
             Log.w(TAG, serviceComponent + " doesn't implement MediaSession2Service.");
         }
+        mSessionId = null;
         mComponentName = serviceComponent;
         mPackageName = serviceComponent.getPackageName();
         mServiceName = serviceComponent.getClassName();
+        mPid = -1;
         mUid = uid;
         mType = TYPE_SESSION_SERVICE;
-        mSessionLink = null;
+        mExtras = null;
     }
 
-    Session2Token(int uid, int type, String packageName, Session2Link sessionLink) {
-        mUid = uid;
-        mType = type;
-        mPackageName = packageName;
+    /**
+     * Constructor for the token with type {@link #TYPE_SESSION}.
+     *
+     * @param context The context.
+     * @param sessionId The ID of the session. Should be unique.
+     * @param extras The extras.
+     * @hide
+     */
+    @SystemApi
+    public Session2Token(@NonNull Context context, @NonNull String sessionId,
+            @Nullable Bundle extras) {
+        if (sessionId == null) {
+            throw new IllegalArgumentException("sessionId shouldn't be null");
+        }
+        if (context == null) {
+            throw new IllegalArgumentException("context shouldn't be null");
+        }
+        mSessionId = sessionId;
+        mPid = Process.myPid();
+        mUid = Process.myUid();
+        mType = TYPE_SESSION;
+        mPackageName = context.getPackageName();
+        mExtras = extras;
         mServiceName = null;
         mComponentName = null;
-        mSessionLink = sessionLink;
     }
 
     Session2Token(Parcel in) {
+        mSessionId = in.readString();
+        mPid = in.readInt();
         mUid = in.readInt();
         mType = in.readInt();
         mPackageName = in.readString();
         mServiceName = in.readString();
-        mSessionLink = in.readParcelable(null);
         mComponentName = ComponentName.unflattenFromString(in.readString());
+        mExtras = in.readParcelable(null);
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mSessionId);
+        dest.writeInt(mPid);
         dest.writeInt(mUid);
         dest.writeInt(mType);
         dest.writeString(mPackageName);
         dest.writeString(mServiceName);
-        dest.writeParcelable(mSessionLink, flags);
         dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString());
+        dest.writeParcelable(mExtras, flags);
     }
 
     @Override
@@ -153,7 +191,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mType, mUid, mPackageName, mServiceName, mSessionLink);
+        return Objects.hash(mSessionId, mPid, mUid, mType, mPackageName, mServiceName);
     }
 
     @Override
@@ -162,17 +200,27 @@
             return false;
         }
         Session2Token other = (Session2Token) obj;
-        return mUid == other.mUid
-                && TextUtils.equals(mPackageName, other.mPackageName)
-                && TextUtils.equals(mServiceName, other.mServiceName)
+        return TextUtils.equals(mSessionId, other.mSessionId)
+                && mPid == other.mPid
+                && mUid == other.mUid
                 && mType == other.mType
-                && Objects.equals(mSessionLink, other.mSessionLink);
+                && TextUtils.equals(mPackageName, other.mPackageName)
+                && TextUtils.equals(mServiceName, other.mServiceName);
     }
 
     @Override
     public String toString() {
         return "Session2Token {pkg=" + mPackageName + " type=" + mType
-                + " service=" + mServiceName + " Session2Link=" + mSessionLink + "}";
+                + " service=" + mServiceName + "}";
+    }
+
+    /**
+     * @return pid of the session
+     * @hide
+     */
+    @SystemApi
+    public int getPid() {
+        return mPid;
     }
 
     /**
@@ -207,8 +255,36 @@
         return mType;
     }
 
-    Session2Link getSessionLink() {
-        return mSessionLink;
+    /**
+     * @return extras
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras == null ? new Bundle() : new Bundle(mExtras);
+    }
+
+    /**
+     * Destroys this session token. After this method is called,
+     * {@link MediaSessionManager#notifySession2Created(Session2Token)} should not be called
+     * with this token.
+     *
+     * @see MediaSessionManager#notifySession2Created(Session2Token)
+     * @hide
+     */
+    @SystemApi
+    public void destroy() {
+        mDestroyed = true;
+    }
+
+    /**
+     * @return whether this token is destroyed
+     * @hide
+     */
+    @SystemApi
+    public boolean isDestroyed() {
+        return mDestroyed;
     }
 
     private static boolean isInterfaceDeclared(PackageManager manager, String serviceInterface,
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index ed16250..fa6e034 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -37,6 +37,7 @@
     SessionLink createSession(String packageName, in SessionCallbackLink sessionCb, String tag,
             int userId);
     void notifySession2Created(in Session2Token sessionToken);
+    void notifySession2Destroyed(in Session2Token sessionToken);
     List<ControllerLink> getSessions(in ComponentName compName, int userId);
     List<Session2Token> getSession2Tokens(int userId);
     void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index c64c452..cae4d17 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -26,7 +26,6 @@
 import android.content.Context;
 import android.media.AudioManager;
 import android.media.IRemoteVolumeController;
-import android.media.MediaSession2;
 import android.media.Session2Token;
 import android.os.Handler;
 import android.os.IBinder;
@@ -115,11 +114,11 @@
     }
 
     /**
-     * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is
+     * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is
      * created.
      * <p>
      * Do not use this API directly, but create a new instance through the
-     * {@link MediaSession2.Builder} instead.
+     * MediaSession2.Builder instead.
      *
      * @param token newly created session2 token
      */
@@ -130,6 +129,9 @@
         if (token.getType() != Session2Token.TYPE_SESSION) {
             throw new IllegalArgumentException("token's type should be TYPE_SESSION");
         }
+        if (token.isDestroyed()) {
+            throw new IllegalArgumentException("token is already destroyed");
+        }
         try {
             mService.notifySession2Created(token);
         } catch (RemoteException e) {
@@ -138,6 +140,31 @@
     }
 
     /**
+     * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is
+     * destroyed.
+     * <p>
+     * Do not use this API directly, but close a session with MediaSession2#close() instead.
+     *
+     * @param token destroyed session2 token
+     */
+    public void notifySession2Destroyed(@NonNull Session2Token token) {
+        if (token == null) {
+            throw new IllegalArgumentException("token shouldn't be null");
+        }
+        if (token.getType() != Session2Token.TYPE_SESSION) {
+            throw new IllegalArgumentException("token's type should be TYPE_SESSION");
+        }
+        if (!token.isDestroyed()) {
+            throw new IllegalArgumentException("token should have been destroyed");
+        }
+        try {
+            mService.notifySession2Destroyed(token);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Get a list of controllers for all ongoing sessions. The controllers will
      * be provided in priority order with the most important controller at index
      * 0.
@@ -192,7 +219,7 @@
      * current user.
      * <p>
      * Although this API can be used without any restriction, each session owners can accept or
-     * reject your uses of {@link MediaSession2}.
+     * reject your uses of MediaSession2.
      *
      * @return A list of {@link Session2Token}.
      */
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 48dbf55..852d296 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -101,7 +101,7 @@
         "libhidlbase",
         "libhidlmemory",
 
-        "libmediametrics",  // Used by MediaMetrics. Will be replaced with stable C API.
+        "libmediametrics",
         "libbinder",  // Used by JWakeLock and MediaMetrics.
 
         "libutils",  // Have to use shared lib to make libandroid_runtime behave correctly.
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index 3ded8c2..de60b08 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "MediaMetricsJNI"
+
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
 
@@ -21,7 +23,10 @@
 #include <media/MediaAnalyticsItem.h>
 
 
-// Copeid from core/jni/ (libandroid_runtime.so)
+// This source file is compiled and linked into both:
+// core/jni/ (libandroid_runtime.so)
+// media/jni (libmedia2_jni.so)
+
 namespace android {
 
 // place the attributes into a java PersistableBundle object
@@ -29,7 +34,7 @@
 
     jclass clazzBundle = env->FindClass("android/os/PersistableBundle");
     if (clazzBundle==NULL) {
-        ALOGD("can't find android/os/PersistableBundle");
+        ALOGE("can't find android/os/PersistableBundle");
         return NULL;
     }
     // sometimes the caller provides one for us to fill
@@ -86,5 +91,138 @@
     return mybundle;
 }
 
+// convert the specified batch  metrics attributes to a persistent bundle.
+// The encoding of the byte array is specified in
+//     frameworks/av/media/libmediametrics/MediaAnalyticsItem.cpp
+//
+// type encodings; matches frameworks/av/media/libmediametrics/MediaAnalyticsItem.cpp
+enum { kInt32 = 0, kInt64, kDouble, kRate, kCString};
+
+jobject MediaMetricsJNI::writeAttributesToBundle(JNIEnv* env, jobject mybundle, char *buffer, size_t length) {
+    ALOGV("writeAttributes()");
+
+    if (buffer == NULL || length <= 0) {
+        ALOGW("bad parameters to writeAttributesToBundle()");
+        return NULL;
+    }
+
+    jclass clazzBundle = env->FindClass("android/os/PersistableBundle");
+    if (clazzBundle==NULL) {
+        ALOGE("can't find android/os/PersistableBundle");
+        return NULL;
+    }
+    // sometimes the caller provides one for us to fill
+    if (mybundle == NULL) {
+        // create the bundle
+        jmethodID constructID = env->GetMethodID(clazzBundle, "<init>", "()V");
+        mybundle = env->NewObject(clazzBundle, constructID);
+        if (mybundle == NULL) {
+            ALOGD("unable to create mybundle");
+            return NULL;
+        }
+    }
+
+    int left = length;
+    char *buf = buffer;
+
+    // grab methods that we can invoke
+    jmethodID setIntID = env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V");
+    jmethodID setLongID = env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V");
+    jmethodID setDoubleID = env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V");
+    jmethodID setStringID = env->GetMethodID(clazzBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V");
+
+
+#define _EXTRACT(size, val) \
+    { if ((size) > left) goto badness; memcpy(&val, buf, (size)); buf += (size); left -= (size);}
+#define _SKIP(size) \
+    { if ((size) > left) goto badness; buf += (size); left -= (size);}
+
+    int32_t bufsize;
+    _EXTRACT(sizeof(int32_t), bufsize);
+    if (bufsize != length) {
+        goto badness;
+    }
+    int32_t proto;
+    _EXTRACT(sizeof(int32_t), proto);
+    if (proto != 0) {
+        ALOGE("unsupported wire protocol %d", proto);
+        goto badness;
+    }
+
+    int32_t count;
+    _EXTRACT(sizeof(int32_t), count);
+
+    // iterate through my attributes
+    // -- get name, get type, get value, insert into bundle appropriately.
+    for (int i = 0 ; i < count; i++ ) {
+            // prop name len (int16)
+            int16_t keylen;
+            _EXTRACT(sizeof(int16_t), keylen);
+            if (keylen <= 0) goto badness;
+            // prop name itself
+            char *key = buf;
+            jstring keyName = env->NewStringUTF(buf);
+            _SKIP(keylen);
+
+            // prop type (int8_t)
+            int8_t attrType;
+            _EXTRACT(sizeof(int8_t), attrType);
+
+	    int16_t attrSize;
+            _EXTRACT(sizeof(int16_t), attrSize);
+
+            switch (attrType) {
+                case kInt32:
+                    {
+                        int32_t i32;
+                        _EXTRACT(sizeof(int32_t), i32);
+                        env->CallVoidMethod(mybundle, setIntID,
+                                            keyName, (jint) i32);
+                        break;
+                    }
+                case kInt64:
+                    {
+                        int64_t i64;
+                        _EXTRACT(sizeof(int64_t), i64);
+                        env->CallVoidMethod(mybundle, setLongID,
+                                            keyName, (jlong) i64);
+                        break;
+                    }
+                case kDouble:
+                    {
+                        double d64;
+                        _EXTRACT(sizeof(double), d64);
+                        env->CallVoidMethod(mybundle, setDoubleID,
+                                            keyName, (jdouble) d64);
+                        break;
+                    }
+                case kCString:
+                    {
+                        jstring value = env->NewStringUTF(buf);
+                        env->CallVoidMethod(mybundle, setStringID,
+                                            keyName, value);
+                        _SKIP(attrSize);
+                        break;
+                    }
+                default:
+                        ALOGW("ignoring Attribute '%s' unknown type: %d",
+                              key, attrType);
+			_SKIP(attrSize);
+                        break;
+            }
+    }
+
+    // should have consumed it all
+    if (left != 0) {
+        ALOGW("did not consume entire buffer; left(%d) != 0", left);
+	goto badness;
+    }
+
+    return mybundle;
+
+  badness:
+    return NULL;
+}
+
 };  // namespace android
 
diff --git a/media/jni/android_media_MediaMetricsJNI.h b/media/jni/android_media_MediaMetricsJNI.h
index fd621ea..a10780f 100644
--- a/media/jni/android_media_MediaMetricsJNI.h
+++ b/media/jni/android_media_MediaMetricsJNI.h
@@ -27,6 +27,7 @@
 class MediaMetricsJNI {
 public:
     static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle);
+    static jobject writeAttributesToBundle(JNIEnv* env, jobject mybundle, char *buffer, size_t length);
 };
 
 };  // namespace android
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index 9b4e730..3069161 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -786,22 +786,17 @@
         return 0;
     }
 
-    Parcel p;
-    int key = FOURCC('m','t','r','X');
-    status_t status = mp->getParameter(key, &p);
+    char *buffer = NULL;
+    size_t length = 0;
+    status_t status = mp->getMetrics(&buffer, &length);
     if (status != OK) {
         ALOGD("getMetrics() failed: %d", status);
         return (jobject) NULL;
     }
 
-    p.setDataPosition(0);
-    MediaAnalyticsItem *item = new MediaAnalyticsItem;
-    item->readFromParcel(p);
-    jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL);
+    jobject mybundle = MediaMetricsJNI::writeAttributesToBundle(env, NULL, buffer, length);
 
-    // housekeeping
-    delete item;
-    item = NULL;
+    free(buffer);
 
     return mybundle;
 }
diff --git a/native/webview/plat_support/draw_fn.h b/native/webview/plat_support/draw_fn.h
index 0490e65..e31ce19 100644
--- a/native/webview/plat_support/draw_fn.h
+++ b/native/webview/plat_support/draw_fn.h
@@ -20,7 +20,8 @@
 // android to chromium are versioned.
 //
 // 1 is Android Q. This matches kAwDrawGLInfoVersion version 3.
-static const int kAwDrawFnVersion = 1;
+// 2 Adds transfer_function_* and color_space_toXYZD50 to AwDrawFn_DrawGLParams.
+static const int kAwDrawFnVersion = 2;
 
 struct AwDrawFn_OnSyncParams {
   int version;
@@ -64,6 +65,16 @@
   // Input: current transformation matrix in surface pixels.
   // Uses the column-based OpenGL matrix format.
   float transform[16];
+
+  // Input: Color space parameters.
+  float transfer_function_g;
+  float transfer_function_a;
+  float transfer_function_b;
+  float transfer_function_c;
+  float transfer_function_d;
+  float transfer_function_e;
+  float transfer_function_f;
+  float color_space_toXYZD50[9];
 };
 
 struct AwDrawFn_InitVkParams {
diff --git a/native/webview/plat_support/draw_functor.cpp b/native/webview/plat_support/draw_functor.cpp
index b97bbc3..afe103a 100644
--- a/native/webview/plat_support/draw_functor.cpp
+++ b/native/webview/plat_support/draw_functor.cpp
@@ -55,6 +55,8 @@
 
 void draw_gl(int functor, void* data,
              const uirenderer::DrawGlInfo& draw_gl_params) {
+  float gabcdef[7];
+  draw_gl_params.color_space_ptr->transferFn(gabcdef);
   AwDrawFn_DrawGLParams params = {
       .version = kAwDrawFnVersion,
       .clip_left = draw_gl_params.clipLeft,
@@ -64,12 +66,24 @@
       .width = draw_gl_params.width,
       .height = draw_gl_params.height,
       .is_layer = draw_gl_params.isLayer,
+      .transfer_function_g = gabcdef[0],
+      .transfer_function_a = gabcdef[1],
+      .transfer_function_b = gabcdef[2],
+      .transfer_function_c = gabcdef[3],
+      .transfer_function_d = gabcdef[4],
+      .transfer_function_e = gabcdef[5],
+      .transfer_function_f = gabcdef[6],
   };
   COMPILE_ASSERT(NELEM(params.transform) == NELEM(draw_gl_params.transform),
                  mismatched_transform_matrix_sizes);
   for (int i = 0; i < NELEM(params.transform); ++i) {
     params.transform[i] = draw_gl_params.transform[i];
   }
+  COMPILE_ASSERT(sizeof(params.color_space_toXYZD50) == sizeof(skcms_Matrix3x3),
+                 gamut_transform_size_mismatch);
+  draw_gl_params.color_space_ptr->toXYZD50(
+      reinterpret_cast<skcms_Matrix3x3*>(&params.color_space_toXYZD50));
+
   SupportData* support = static_cast<SupportData*>(data);
   support->callbacks.draw_gl(functor, support->data, &params);
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index dbddf71..b37c5e6 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -114,9 +114,16 @@
                     new DeviceProvisionedController.DeviceProvisionedListener() {
                         @Override
                         public void onDeviceProvisionedChanged() {
-                            mDeviceIsProvisioned =
-                                    mDeviceProvisionedController.isDeviceProvisioned();
-                            restartNavBars();
+                            mHandler.post(() -> {
+                                // on initial boot we are getting a call even though the value
+                                // is the same so we are confirming the reset is needed
+                                boolean deviceProvisioned =
+                                        mDeviceProvisionedController.isDeviceProvisioned();
+                                if (mDeviceIsProvisioned != deviceProvisioned) {
+                                    mDeviceIsProvisioned = deviceProvisioned;
+                                    restartNavBars();
+                                }
+                            });
                         }
                     });
         }
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
index bc6e2fc..5acf4fb 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -291,7 +291,7 @@
         Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES);
         if (messages == null || messages.length == 0) {
             return Arrays.asList(new ConversationActions.Message.Builder(
-                    ConversationActions.Message.PERSON_USER_REMOTE)
+                    ConversationActions.Message.PERSON_USER_OTHERS)
                     .setText(notification.extras.getCharSequence(Notification.EXTRA_TEXT))
                     .build());
         }
@@ -310,7 +310,7 @@
                 break;
             }
             Person author = localUser != null && localUser.equals(senderPerson)
-                    ? ConversationActions.Message.PERSON_USER_LOCAL : senderPerson;
+                    ? ConversationActions.Message.PERSON_USER_SELF : senderPerson;
             extractMessages.push(new ConversationActions.Message.Builder(author)
                     .setText(message.getText())
                     .setReferenceTime(
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
index 707349b..7f8127a 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
@@ -154,7 +154,7 @@
         ConversationActions.Message secondMessage = messages.get(0);
         MessageSubject.assertThat(secondMessage).hasText("secondMessage");
         MessageSubject.assertThat(secondMessage)
-                .hasPerson(ConversationActions.Message.PERSON_USER_LOCAL);
+                .hasPerson(ConversationActions.Message.PERSON_USER_SELF);
         MessageSubject.assertThat(secondMessage)
                 .hasReferenceTime(createZonedDateTimeFromMsUtc(2000));
 
diff --git a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
index f21809f..4ae044de 100644
--- a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
+++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
@@ -103,9 +103,7 @@
         MockitoAnnotations.initMocks(this);
 
         when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm);
-        when(mContext.getSystemServiceName(ConnectivityManager.class))
-                .thenReturn(Context.CONNECTIVITY_SERVICE);
-        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
+        when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm);
         when(mContext.getResources()).thenReturn(mResources);
         when(mResources.getInteger(R.integer.config_networkAvoidBadWifi))
                 .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE);
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
index d957801..305a1ff 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
@@ -22,7 +22,6 @@
 import android.app.usage.NetworkStats;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
-import android.net.ConnectivityManager;
 import android.net.INetworkStatsService;
 import android.net.INetworkStatsSession;
 import android.net.NetworkPolicy;
@@ -35,14 +34,14 @@
 import android.text.format.DateUtils;
 import android.util.Pair;
 
+import androidx.annotation.VisibleForTesting;
+import androidx.loader.content.AsyncTaskLoader;
+
 import com.android.settingslib.NetworkPolicyEditor;
 
 import java.time.ZonedDateTime;
 import java.util.Iterator;
 
-import androidx.annotation.VisibleForTesting;
-import androidx.loader.content.AsyncTaskLoader;
-
 /**
  * Loader for network data usage history. It returns a list of usage data per billing cycle.
  */
@@ -121,8 +120,7 @@
 
             long cycleEnd = historyEnd;
             while (cycleEnd > historyStart) {
-                final long cycleStart = Math.max(
-                    historyStart, cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4));
+                final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
                 recordUsage(cycleStart, cycleEnd);
                 cycleEnd = cycleStart;
             }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
index 2d8ea12..b8a143a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
@@ -130,7 +130,8 @@
             .thenReturn(networkHistory);
         final long now = System.currentTimeMillis();
         final long fourWeeksAgo = now - (DateUtils.WEEK_IN_MILLIS * 4);
-        when(networkHistory.getStart()).thenReturn(fourWeeksAgo);
+        final long twoDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 2);
+        when(networkHistory.getStart()).thenReturn(twoDaysAgo);
         when(networkHistory.getEnd()).thenReturn(now);
 
         mLoader.loadFourWeeksData();
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 0a62b7c..e0d178f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -57,6 +57,7 @@
         "androidx.slice_slice-builders",
         "androidx.arch.core_core-runtime",
         "androidx.lifecycle_lifecycle-extensions",
+        "androidx.dynamicanimation_dynamicanimation",
         "SystemUI-tags",
         "SystemUI-proto",
         "dagger2-2.19",
@@ -108,6 +109,7 @@
         "androidx.slice_slice-builders",
         "androidx.arch.core_core-runtime",
         "androidx.lifecycle_lifecycle-extensions",
+        "androidx.dynamicanimation_dynamicanimation",
         "SystemUI-tags",
         "SystemUI-proto",
         "metrics-helper-lib",
diff --git a/packages/SystemUI/docs/physics-animation-layout-config-methods.png b/packages/SystemUI/docs/physics-animation-layout-config-methods.png
new file mode 100644
index 0000000..c3a45e2
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-layout-config-methods.png
Binary files differ
diff --git a/packages/SystemUI/docs/physics-animation-layout-control-methods.png b/packages/SystemUI/docs/physics-animation-layout-control-methods.png
new file mode 100644
index 0000000..e77c676
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-layout-control-methods.png
Binary files differ
diff --git a/packages/SystemUI/docs/physics-animation-layout.md b/packages/SystemUI/docs/physics-animation-layout.md
new file mode 100644
index 0000000..a67b5e8
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-layout.md
@@ -0,0 +1,56 @@
+# Physics Animation Layout
+
+## Overview
+**PhysicsAnimationLayout** works with an implementation of **PhysicsAnimationController** to construct and maintain physics animations for each of its child views. During the initial construction of the animations, the layout queries the controller for configuration settings such as which properties to animate, which animations to chain together, and what stiffness or bounciness to use. Once the animations are built to the controller’s specifications, the controller can then ask the layout to start, stop and manipulate them arbitrarily to achieve any desired animation effect. The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively.
+
+An example usage is Bubbles, which uses a PhysicsAnimationLayout for its stack of bubbles. Bubbles has controller subclasses including StackAnimationController and ExpansionAnimationController. StackAnimationController tells the layout to configure the translation animations to be chained (for the ‘following’ drag effect), and has methods such as ```moveStack(x, y)``` to animate the stack to a given point. ExpansionAnimationController asks for no animations to be chained, and exposes methods like ```expandStack()``` and ```collapseStack()```, which animate the bubbles to positions along the bottom of the screen.
+
+## PhysicsAnimationController
+PhysicsAnimationController is a public abstract class in PhysicsAnimationLayout. Controller instances must override configuration methods, which are used by the layout while constructing the animations, and animation control methods, which are called to initiate animations in response to events.
+
+### Configuration Methods
+![Diagram of how animations are configured using the controller's configuration methods.](physics-animation-layout-config-methods.png)
+The controller must override the following methods:
+
+```Set<ViewProperty> getAnimatedProperties()```
+Returns the properties, such as TRANSLATION_X and TRANSLATION_Y, for which the layout should construct physics animations.
+
+```int getNextAnimationInChain(ViewProperty property, int index)```
+If the animation at the given index should update another animation whenever its value changes, return the index of the other animation. Otherwise, return NONE. This is used to chain animations together, so that when one animation moves, the other ‘follows’ closely behind.
+
+```float getOffsetForChainedPropertyAnimation(ViewProperty property)```
+Value to add every time chained animations update the subsequent animation in the chain. For example, returning TRANSLATION_X offset = 20px means that if the first animation in the chain is animated to 10px, the second will update to 30px, the third to 50px, etc.
+
+```SpringForce getSpringForce(ViewProperty property)```
+Returns a SpringForce instance to use for animations of the given property. This allows the controller to configure stiffness and bounciness values. Since the physics animations internally use SpringForce instances to hold inflight animation values, this method needs to return a new SpringForce instance each time - no constants allowed.
+
+### Animation Control Methods
+![Diagram of how calls to animateValueForChildAtIndex dispatch to DynamicAnimations.](physics-animation-layout-control-methods.png)
+Once the layout has used the controller’s configuration properties to build the animations, the controller can use them to actually run animations. This is done for two reasons - reacting to a view being added or removed, or responding to another class (such as a touch handler or broadcast receiver) requesting an animation. ```onChildAdded``` and ```onChildRemoved``` are called automatically by the layout, giving the controller the opportunity to animate the child in/out. Custom methods are called by anyone with access to the controller instance to do things like expand, collapse, or move the child views.
+
+In either case, the controller has access to the layout’s protected ```animateValueForChildAtIndex(ViewProperty property, int index, float value)``` method. This method is used to actually run an animation.
+
+For example, moving the first child view to *(100, 200)*:
+
+```
+animateValueForChildAtIndex(TRANSLATION_X, 0, 100);
+animateValueForChildAtIndex(TRANSLATION_Y, 0, 200);
+```
+
+This would use the physics animations constructed by the layout to spring the view to *(100, 200)*.
+
+If the controller’s ```getNextAnimationInChain``` method set up the first child’s TRANSLATION_X/Y animations to be chained to the second child’s, this would result in the second child also springing towards (100, 200), plus any offset returned by ```getOffsetForChainedPropertyAnimation```.
+
+## PhysicsAnimationLayout
+The layout itself is a FrameLayout descendant with a few extra methods:
+
+```setController(PhysicsAnimationController controller)```
+Attaches the layout to the controller, so that the controller can access the layout’s protected methods. It also constructs or reconfigures the physics animations according to the new controller’s configuration methods.
+
+```setEndListenerForProperty(ViewProperty property, AnimationEndListener endListener)```
+Sets an end listener that is called when all animations on the given property have ended.
+
+```setMaxRenderedChildren(int max)```
+Child views beyond this limit will be set to GONE, and won't be animated, for performance reasons. Defaults to **5**.
+
+It has one protected method, ```animateValueForChildAtIndex(ViewProperty property, int index, float value)```, which is visible to PhysicsAnimationController descendants. This method dispatches the given value to the appropriate animation.
\ No newline at end of file
diff --git a/packages/SystemUI/docs/physics-animation-testing.md b/packages/SystemUI/docs/physics-animation-testing.md
new file mode 100644
index 0000000..47354d4
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-testing.md
@@ -0,0 +1,11 @@
+# Physics Animation Testing
+Physics animations are notoriously difficult to test, since they’re essentially small simulations. They have no set duration, and they’re considered ‘finished’ only when the movements imparted by the animation are too small to be user-visible. Mid-states are not deterministic.
+
+For this reason, we only test the end state of animations. Manual testing should be sufficient to reveal flaws in the en-route animation visuals. In a worst-case failure case, as long as the end state is correct, usability will not be affected - animations might just look a bit off until the UI elements settle to their proper positions.
+
+## Waiting for Animations to End
+Testing any kind of animation can be tricky, since animations need to run on the main thread, and they’re asynchronous - the test has to wait for the animation to finish before we can assert anything about its end state. For normal animations, we can invoke skipToEnd to avoid waiting. While this method is available for SpringAnimation, it’s not available for FlingAnimation since its end state is not initially known. A FlingAnimation’s ‘end’ is when the friction simulation reports that motion has slowed to an invisible level. For this reason, we have to actually run the physics animations.
+
+To accommodate this, all tests of the layout itself, as well as any animation controller subclasses, use **PhysicsAnimationLayoutTestCase**. The layout provided to controllers by the test case is a **TestablePhysicsAnimationLayout**, a subclass of PhysicsAnimationLayout whose animation-related methods have been overridden to force them to run on the main thread via a Handler. Animations will simply crash if they’re called directly from the test thread, so this is important.
+
+The test case also provides ```waitForPropertyAnimations```, which uses a **CountDownLatch** to wait for all animations on a given property to complete before continuing the test. This works since the test is not running on the same thread as the animation, so a blocking call to ```latch.await()``` does not affect the animations’ progress. The latch is initialized with a count equal to the number of properties we’re listening to. We then add end listeners to the layout for each property, which call ```latch.countDown()```. Once all of the properties’ animations have completed, the latch count reaches zero and the test’s call to ```await()``` returns, with the animations complete.
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml
index 204408cd..13186fc 100644
--- a/packages/SystemUI/res/layout/bubble_view.xml
+++ b/packages/SystemUI/res/layout/bubble_view.xml
@@ -22,8 +22,8 @@
 
     <com.android.systemui.bubbles.BadgedImageView
         android:id="@+id/bubble_image"
-        android:layout_width="@dimen/bubble_size"
-        android:layout_height="@dimen/bubble_size"
+        android:layout_width="@dimen/individual_bubble_size"
+        android:layout_height="@dimen/individual_bubble_size"
         android:padding="@dimen/bubble_view_padding"
         android:clipToPadding="false"/>
 
diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml
new file mode 100644
index 0000000..e6f2376
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_grid.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.systemui.globalactions.GlobalActionsGridLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@id/global_actions_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:clipToPadding="false"
+    android:theme="@style/qs_theme"
+    android:gravity="bottom|center"
+    android:clipChildren="false"
+>
+
+    <LinearLayout
+        android:layout_height="290dp"
+        android:layout_width="412dp"
+        android:gravity="bottom"
+        android:padding="0dp"
+        android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin"
+    >
+        <!-- For separated items-->
+        <LinearLayout
+            android:id="@+id/separated_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
+            android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+            android:paddingTop="@dimen/global_actions_grid_top_padding"
+            android:paddingLeft="@dimen/global_actions_grid_left_padding"
+            android:paddingBottom="@dimen/global_actions_grid_bottom_padding"
+            android:paddingRight="@dimen/global_actions_grid_right_padding"
+            android:orientation="vertical"
+            android:background="?android:attr/colorBackgroundFloating"
+            android:translationZ="@dimen/global_actions_translate"
+        />
+
+        <Space android:layout_width="match_parent" android:layout_height="2dp"
+               android:layout_weight="1" />
+
+        <!-- Grid of action items -->
+        <com.android.systemui.globalactions.ListGridLayout
+            android:id="@android:id/list"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:layoutDirection="rtl"
+            android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+            android:translationZ="@dimen/global_actions_translate"
+            android:paddingLeft="@dimen/global_actions_grid_left_padding"
+            android:paddingRight="@dimen/global_actions_grid_right_padding"
+            android:paddingTop="@dimen/global_actions_grid_top_padding"
+            android:paddingBottom="@dimen/global_actions_grid_bottom_padding"
+            android:background="?android:attr/colorBackgroundFloating"
+        >
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="bottom|right"
+                android:visibility="gone"
+                android:gravity="bottom"
+                android:orientation="vertical"
+            />
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="bottom|right"
+                android:visibility="gone"
+                android:gravity="bottom"
+                android:orientation="vertical"
+            />
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="bottom|right"
+                android:visibility="gone"
+                android:gravity="bottom"
+                android:orientation="vertical"
+            />
+        </com.android.systemui.globalactions.ListGridLayout>
+    </LinearLayout>
+
+</com.android.systemui.globalactions.GlobalActionsGridLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml
new file mode 100644
index 0000000..0c11cd9
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- RelativeLayouts have an issue enforcing minimum heights, so just
+     work around this for now with LinearLayouts. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="72dp"
+    android:layout_height="72dp"
+    android:gravity="center"
+    android:orientation="vertical"
+    android:layout_marginTop="@dimen/global_actions_grid_item_vertical_margin"
+    android:layout_marginBottom="@dimen/global_actions_grid_item_vertical_margin"
+    android:layout_marginLeft="@dimen/global_actions_grid_item_side_margin"
+    android:layout_marginRight="@dimen/global_actions_grid_item_side_margin"
+    android:paddingEnd="4dip"
+    android:paddingStart="4dip">
+
+    <ImageView
+        android:id="@*android:id/icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_gravity="center"
+        android:scaleType="center"
+        android:alpha="?android:attr/primaryContentAlpha"
+    />
+
+    <TextView
+        android:id="@*android:id/message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top|center_horizontal"
+        android:paddingTop="10dp"
+        android:gravity="center"
+        android:textSize="12sp"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+    />
+
+    <TextView
+        android:id="@*android:id/status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top|center_horizontal"
+        android:gravity="center"
+        android:textColor="?android:attr/textColorTertiary"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+    />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_footer_carrier.xml b/packages/SystemUI/res/layout/qs_footer_carrier.xml
new file mode 100644
index 0000000..bd492b0
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_footer_carrier.xml
@@ -0,0 +1,49 @@
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/linear_footer_carrier"
+    android:layout_width="0dp"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:layout_weight="1"
+    android:gravity="center_vertical|start"
+    android:background="@android:color/transparent"
+    android:clickable="false"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:paddingStart="16dp" >
+
+    <include
+        layout="@layout/mobile_signal_group"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:visibility="gone" />
+
+    <view class="com.android.systemui.qs.QSFooterImpl$QSCarrierText"
+        android:id="@+id/qs_carrier_text"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:ellipsize="marquee"
+        android:textAppearance="@style/TextAppearance.QS.CarrierInfo"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textDirection="locale"
+        android:singleLine="true" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 890bf5d..abf9e05 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -42,30 +42,31 @@
         android:gravity="end" >
 
         <LinearLayout
+            android:id="@+id/qs_mobile"
             android:layout_width="0dp"
             android:layout_height="match_parent"
             android:layout_weight="1"
             android:gravity="center_vertical|start"
-            android:paddingStart="16dp">
+            android:orientation="horizontal"
+            android:layout_marginEnd="32dp">
 
             <include
-                layout="@layout/mobile_signal_group"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginEnd="8dp"
+                layout="@layout/qs_footer_carrier"
+                android:id="@+id/carrier1" />
+
+            <View
+                android:id="@+id/qs_carrier_divider"
+                android:layout_width="2dp"
+                android:layout_height="match_parent"
+                android:layout_marginTop="15dp"
+                android:layout_marginBottom="15dp"
+                android:background="?android:attr/dividerVertical"
                 android:visibility="gone" />
 
-            <com.android.keyguard.CarrierText
-                android:id="@+id/qs_carrier_text"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:layout_marginEnd="32dp"
-                android:ellipsize="marquee"
-                android:textAppearance="@style/TextAppearance.QS.CarrierInfo"
-                android:textColor="?android:attr/textColorPrimary"
-                android:textDirection="locale"
-                android:singleLine="true" />
+            <include
+                layout="@layout/qs_footer_carrier"
+                android:id="@+id/carrier2"
+                android:visibility="gone"/>
 
         </LinearLayout>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ab0bbe1..a14259e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -834,6 +834,18 @@
 
     <dimen name="global_actions_panel_width">120dp</dimen>
 
+    <dimen name="global_actions_grid_container_bottom_margin">16dp</dimen>
+
+    <dimen name="global_actions_grid_side_margin">4dp</dimen>
+    <dimen name="global_actions_grid_separated_panel_width">104dp</dimen>
+    <dimen name="global_actions_grid_top_padding">8dp</dimen>
+    <dimen name="global_actions_grid_bottom_padding">8dp</dimen>
+    <dimen name="global_actions_grid_left_padding">4dp</dimen>
+    <dimen name="global_actions_grid_right_padding">4dp</dimen>
+
+    <dimen name="global_actions_grid_item_side_margin">12dp</dimen>
+    <dimen name="global_actions_grid_item_vertical_margin">8dp</dimen>
+
     <dimen name="global_actions_top_padding">120dp</dimen>
 
     <dimen name="global_actions_padding">12dp</dimen>
@@ -982,8 +994,8 @@
     <dimen name="bubble_view_padding">0dp</dimen>
     <!-- Padding between bubbles when displayed in expanded state -->
     <dimen name="bubble_padding">8dp</dimen>
-    <!-- Size of the collapsed bubble -->
-    <dimen name="bubble_size">56dp</dimen>
+    <!-- Size of individual bubbles. -->
+    <dimen name="individual_bubble_size">56dp</dimen>
     <!-- How much to inset the icon in the circle -->
     <dimen name="bubble_icon_inset">16dp</dimen>
     <!-- Padding around the view displayed when the bubble is expanded -->
@@ -1000,10 +1012,20 @@
     <dimen name="bubble_expanded_header_height">48dp</dimen>
     <!-- Left and right padding applied to the header. -->
     <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen>
+    <!-- How far, horizontally, to animate the expanded view over when animating in/out. -->
+    <dimen name="bubble_expanded_animate_x_distance">100dp</dimen>
+    <!-- How far, vertically, to animate the expanded view over when animating in/out. -->
+    <dimen name="bubble_expanded_animate_y_distance">500dp</dimen>
     <!-- Max width of the message bubble-->
     <dimen name="bubble_message_max_width">144dp</dimen>
     <!-- Min width of the message bubble -->
     <dimen name="bubble_message_min_width">32dp</dimen>
     <!-- Interior padding of the message bubble -->
     <dimen name="bubble_message_padding">4dp</dimen>
+    <!-- Offset between bubbles in their stacked position. -->
+    <dimen name="bubble_stack_offset">5dp</dimen>
+    <!-- How far offscreen the bubble stack rests. -->
+    <dimen name="bubble_stack_offscreen">5dp</dimen>
+    <!-- How far down the screen the stack starts. -->
+    <dimen name="bubble_stack_starting_offset_y">100dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index bd34bea..6a68457 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -68,6 +68,7 @@
     <item type="id" name="panel_alpha_animator_tag"/>
     <item type="id" name="panel_alpha_animator_start_tag"/>
     <item type="id" name="panel_alpha_animator_end_tag"/>
+    <item type="id" name="cross_fade_layer_type_changed_tag"/>
 
     <!-- Whether the icon is from a notification for which targetSdk < L -->
     <item type="id" name="icon_is_pre_L"/>
@@ -115,6 +116,14 @@
     <item type="id" name="aod_mask_transition_progress_end_tag" />
     <item type="id" name="aod_mask_transition_progress_start_tag" />
 
+    <!-- For saving DynamicAnimation physics animations as view tags. -->
+    <item type="id" name="translation_x_dynamicanimation_tag"/>
+    <item type="id" name="translation_y_dynamicanimation_tag"/>
+    <item type="id" name="translation_z_dynamicanimation_tag"/>
+    <item type="id" name="alpha_dynamicanimation_tag"/>
+    <item type="id" name="scale_x_dynamicanimation_tag"/>
+    <item type="id" name="scale_y_dynamicanimation_tag"/>
+
     <!-- Global Actions Menu -->
     <item type="id" name="global_actions_view" />
 </resources>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index fd7a105..e8fabf5 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -21,4 +21,10 @@
          0) as we can allow the carrier text to stretch as far as needed in the QS footer. -->
     <integer name="qs_footer_actions_width">-2</integer>
     <integer name="qs_footer_actions_weight">0</integer>
+
+    <!-- Maximum number of bubbles to render and animate at one time. While the animations used are
+         lightweight translation animations, this number can be reduced on lower end devices if any
+         performance issues arise. -->
+    <integer name="bubbles_max_rendered">5</integer>
+
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index 8069ce4..adcb7a1 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -42,8 +42,8 @@
     private CarrierTextController.CarrierTextCallback mCarrierTextCallback =
             new CarrierTextController.CarrierTextCallback() {
                 @Override
-                public void updateCarrierText(CharSequence carrierText, boolean simsReady) {
-                    setText(carrierText);
+                public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+                    setText(info.carrierText);
                 }
 
                 @Override
@@ -53,7 +53,7 @@
 
                 @Override
                 public void finishedWakingUp() {
-                    setSelected(mShouldMarquee);
+                    setSelected(true);
                 }
             };
 
@@ -85,7 +85,6 @@
                 mShowMissingSim);
         mShouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
         setSelected(mShouldMarquee); // Allow marquee to work.
-
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 3698a6e..2ce6965 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -46,17 +46,11 @@
     private static final String TAG = "CarrierTextController";
 
     private final boolean mIsEmergencyCallCapable;
-
     private boolean mTelephonyCapable;
-
     private boolean mShowMissingSim;
-
     private boolean mShowAirplaneMode;
-
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
     private WifiManager mWifiManager;
-
     private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()];
     private CarrierTextCallback mCarrierTextCallback;
     private Context mContext;
@@ -132,10 +126,8 @@
     /**
      * Controller that provides updates on text with carriers names or SIM status.
      * Used by {@link CarrierText}.
-     * @param context
+     *
      * @param separator Separator between different parts of the text
-     * @param showAirplaneMode
-     * @param showMissingSim
      */
     public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
             boolean showMissingSim) {
@@ -186,6 +178,7 @@
     /**
      * Sets the listening status of this controller. If the callback is null, it is set to
      * not listening
+     *
      * @param callback Callback to provide text updates
      */
     public void setListening(CarrierTextCallback callback) {
@@ -199,7 +192,7 @@
             } else {
                 // Don't listen and clear out the text when the device isn't a phone.
                 mKeyguardUpdateMonitor = null;
-                callback.updateCarrierText("", false);
+                callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
             }
         } else {
             mCarrierTextCallback = null;
@@ -217,9 +210,11 @@
 
         List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
         final int numSubs = subs.size();
+        final int[] subsIds = new int[numSubs];
         if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
         for (int i = 0; i < numSubs; i++) {
             int subId = subs.get(i).getSubscriptionId();
+            subsIds[i] = subId;
             IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
             CharSequence carrierName = subs.get(i).getCarrierName();
             CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
@@ -294,9 +289,11 @@
         }
 
         if (mCarrierTextCallback != null) {
-            mCarrierTextCallback.updateCarrierText(displayText, anySimReadyAndInService);
-            mCarrierTextCallback.updateCarrierList(
-                    displayText.toString().split(mSeparator.toString()), anySimReadyAndInService);
+            mCarrierTextCallback.updateCarrierInfo(new CarrierTextCallbackInfo(
+                    displayText,
+                    displayText.toString().split(mSeparator.toString()),
+                    anySimReadyAndInService,
+                    subsIds));
         }
 
     }
@@ -482,22 +479,31 @@
     }
 
     /**
+     * Data structure for passing information to CarrierTextController subscribers
+     */
+    public static final class CarrierTextCallbackInfo {
+        public final CharSequence carrierText;
+        public final CharSequence[] listOfCarriers;
+        public final boolean anySimReady;
+        public final int[] subscriptionIds;
+
+        CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
+                boolean anySimReady, int[] subscriptionIds) {
+            this.carrierText = carrierText;
+            this.listOfCarriers = listOfCarriers;
+            this.anySimReady = anySimReady;
+            this.subscriptionIds = subscriptionIds;
+        }
+    }
+
+    /**
      * Callback to communicate to Views
      */
     public interface CarrierTextCallback {
         /**
-         * Provides an updated list of carrier names
-         * @param listOfCarriers
-         * @param simsReady Whether at least one SIM is ready and with service
+         * Provides updated carrier information.
          */
-        default void updateCarrierList(CharSequence[] listOfCarriers, boolean simsReady) {};
-
-        /**
-         * Provides an updated full carrier text
-         * @param carrierText
-         * @param simsReady Whether at least one SIM is ready and with service
-         */
-        default void updateCarrierText(CharSequence carrierText, boolean simsReady) {};
+        default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
 
         /**
          * Notifies the View that the device is going to sleep
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index d99f234..fece94e 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -44,6 +44,7 @@
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.power.EnhancedEstimates;
 import com.android.systemui.power.PowerUI;
+import com.android.systemui.privacy.PrivacyItemController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.AmbientPulseManager;
@@ -278,6 +279,7 @@
     @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
     @Inject Lazy<AutoHideController> mAutoHideController;
     @Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener;
+    @Inject Lazy<PrivacyItemController> mPrivacyItemController;
     @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper;
     @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler;
     @Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler;
@@ -452,6 +454,8 @@
         mProviders.put(ForegroundServiceNotificationListener.class,
                 mForegroundServiceNotificationListener::get);
         mProviders.put(ClockManager.class, mClockManager::get);
+        mProviders.put(PrivacyItemController.class, mPrivacyItemController::get);
+
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
index 0c7a9a9..85265f4 100644
--- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
@@ -26,11 +26,11 @@
  * Layout class representing the Global Actions menu which appears when the power button is held.
  */
 public abstract class MultiListLayout extends LinearLayout {
-    boolean mHasOutsideTouch;
-    boolean mHasSeparatedView;
+    protected boolean mHasOutsideTouch;
+    protected boolean mHasSeparatedView;
 
-    int mExpectedSeparatedItemCount;
-    int mExpectedListItemCount;
+    protected int mExpectedSeparatedItemCount;
+    protected int mExpectedListItemCount;
 
     public MultiListLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
index 92d3cc1..36a813b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -57,7 +57,7 @@
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         setScaleType(ScaleType.CENTER_CROP);
-        mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_size);
+        mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
         mDotRenderer = new BadgeRenderer(mIconSize);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index a457dee..b7bee30 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -18,9 +18,8 @@
 
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
-import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain;
 
@@ -229,10 +228,6 @@
         }
         mStackView.stackDismissed();
 
-        // Reset the position of the stack (TODO - or should we save / respect last user position?)
-        Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
-        mStackView.setPosition(startPoint.x, startPoint.y);
-
         updateVisibility();
         mNotificationEntryManager.updateNotifications();
     }
@@ -249,16 +244,14 @@
             BubbleView bubble = mBubbles.get(notif.key);
             mStackView.updateBubble(bubble, notif, updatePosition);
         } else {
-            boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE;
             if (mStackView == null) {
-                setPosition = true;
                 mStackView = new BubbleStackView(mContext);
                 ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
                 // XXX: Bug when you expand the shade on top of expanded bubble, there is no scrim
                 // between bubble and the shade
                 int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1;
                 sbv.addView(mStackView, bubblePosition,
-                        new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+                        new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
                 if (mExpandListener != null) {
                     mStackView.setExpandListener(mExpandListener);
                 }
@@ -273,11 +266,6 @@
             }
             mBubbles.put(bubble.getKey(), bubble);
             mStackView.addBubble(bubble);
-            if (setPosition) {
-                // Need to add the bubble to the stack before we can know the width
-                Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
-                mStackView.setPosition(startPoint.x, startPoint.y);
-            }
         }
         updateVisibility();
     }
@@ -423,24 +411,6 @@
         return mStackView;
     }
 
-    // TODO: factor in PIP location / maybe last place user had it
-    /**
-     * Gets an appropriate starting point to position the bubble stack.
-     */
-    private static Point getStartPoint(int size, Point displaySize) {
-        final int x = displaySize.x - size + EDGE_OVERLAP;
-        final int y = displaySize.y / 4;
-        return new Point(x, y);
-    }
-
-    /**
-     * Gets an appropriate position for the bubble when the stack is expanded.
-     */
-    static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) {
-        // Same place for now..
-        return new Point(EDGE_OVERLAP, size);
-    }
-
     /**
      * Whether the notification has been developer configured to bubble and is allowed by the user.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java
deleted file mode 100644
index c1063fa..0000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2018 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.bubbles;
-
-import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
-
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.systemui.bubbles.BubbleTouchHandler.FloatingView;
-
-import java.util.Arrays;
-
-/**
- * Math and animators to move bubbles around the screen.
- *
- * TODO: straight up copy paste from old prototype -- consider physics, see if bubble & pip
- * movements can be unified maybe?
- */
-public class BubbleMovementHelper {
-
-    private static final int MAGNET_ANIM_TIME = 150;
-    public static final int EDGE_OVERLAP = 0;
-
-    private Context mContext;
-    private Point mDisplaySize;
-
-    public BubbleMovementHelper(Context context) {
-        mContext = context;
-        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        mDisplaySize = new Point();
-        wm.getDefaultDisplay().getSize(mDisplaySize);
-    }
-
-    /**
-     * @return the distance between the two provided points.
-     */
-    static double distance(Point p1, Point p2) {
-        return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
-    }
-
-    /**
-     * @return the y value of a line defined by y = mx+b
-     */
-    static float findY(float m, float b, float x) {
-        return (m * x) + b;
-    }
-
-    /**
-     * @return the x value of a line defined by y = mx+b
-     */
-    static float findX(float m, float b, float y) {
-        return (y - b) / m;
-    }
-
-    /**
-     * Determines a point on the edge of the screen based on the velocity and position.
-     */
-    public Point getPointOnEdge(View bv, Point p, float velX, float velY) {
-        // Find the slope and the y-intercept
-        velX = velX == 0 ? 1 : velX;
-        final float m = velY / velX;
-        final float b = p.y - m * p.x;
-
-        // There are two lines it can intersect, find the two points
-        Point pointHoriz = new Point();
-        Point pointVert = new Point();
-
-        if (velX > 0) {
-            // right
-            pointHoriz.x = mDisplaySize.x;
-            pointHoriz.y = (int) findY(m, b, mDisplaySize.x);
-        } else {
-            // left
-            pointHoriz.x = EDGE_OVERLAP;
-            pointHoriz.y = (int) findY(m, b, 0);
-        }
-        if (velY > 0) {
-            // bottom
-            pointVert.x = (int) findX(m, b, mDisplaySize.y);
-            pointVert.y = mDisplaySize.y - getNavBarHeight();
-        } else {
-            // top
-            pointVert.x = (int) findX(m, b, 0);
-            pointVert.y = EDGE_OVERLAP;
-        }
-
-        // Use the point that's closest to the start position
-        final double distanceToVertPoint = distance(p, pointVert);
-        final double distanceToHorizPoint = distance(p, pointHoriz);
-        boolean useVert = distanceToVertPoint < distanceToHorizPoint;
-        // Check if we're being flung along the current edge, use opposite point in this case
-        // XXX: on*Edge methods should actually use 'down' position of view and compare 'up' but
-        // this works well enough for now
-        if (onSideEdge(bv, p) && Math.abs(velY) > Math.abs(velX)) {
-            // Flinging along left or right edge, favor vert edge
-            useVert = true;
-
-        } else if (onTopBotEdge(bv, p) && Math.abs(velX) > Math.abs(velY)) {
-            // Flinging along top or bottom edge
-            useVert = false;
-        }
-
-        if (useVert) {
-            pointVert.x = capX(pointVert.x, bv);
-            pointVert.y = capY(pointVert.y, bv);
-            return pointVert;
-
-        }
-        pointHoriz.x = capX(pointHoriz.x, bv);
-        pointHoriz.y = capY(pointHoriz.y, bv);
-        return pointHoriz;
-    }
-
-    /**
-     * @return whether the view is on a side edge of the screen (i.e. left or right).
-     */
-    public boolean onSideEdge(View fv, Point p) {
-        return p.x + fv.getWidth() + EDGE_OVERLAP <= mDisplaySize.x
-                - EDGE_OVERLAP
-                || p.x >= EDGE_OVERLAP;
-    }
-
-    /**
-     * @return whether the view is on a top or bottom edge of the screen.
-     */
-    public boolean onTopBotEdge(View bv, Point p) {
-        return p.y >= getStatusBarHeight() + EDGE_OVERLAP
-                || p.y + bv.getHeight() + EDGE_OVERLAP <= mDisplaySize.y
-                - EDGE_OVERLAP;
-    }
-
-    /**
-     * @return constrained x value based on screen size and how much a view can overlap with a side
-     *         edge.
-     */
-    public int capX(float x, View bv) {
-        // Floating things can't stick to top or bottom edges, so figure out if it's closer to
-        // left or right and just use that side + the overlap.
-        final float centerX = x + bv.getWidth() / 2;
-        if (centerX > mDisplaySize.x / 2) {
-            // Right side
-            return mDisplaySize.x - bv.getWidth() - EDGE_OVERLAP;
-        } else {
-            // Left side
-            return EDGE_OVERLAP;
-        }
-    }
-
-    /**
-     * @return constrained y value based on screen size and how much a view can overlap with a top
-     *         or bottom edge.
-     */
-    public int capY(float y, View bv) {
-        final int height = bv.getHeight();
-        if (y < getStatusBarHeight() + EDGE_OVERLAP) {
-            return getStatusBarHeight() + EDGE_OVERLAP;
-        }
-        if (y + height + EDGE_OVERLAP > mDisplaySize.y - EDGE_OVERLAP) {
-            return mDisplaySize.y - height - EDGE_OVERLAP;
-        }
-        return (int) y;
-    }
-
-    /**
-     * Animation to translate the provided view.
-     */
-    public AnimatorSet animateMagnetTo(final BubbleStackView bv) {
-        Point pos = bv.getPosition();
-
-        // Find the distance to each edge
-        final int leftDistance = pos.x;
-        final int rightDistance = mDisplaySize.x - leftDistance;
-        final int topDistance = pos.y;
-        final int botDistance = mDisplaySize.y - topDistance;
-
-        int smallest;
-        // Find the closest one
-        int[] distances = {
-                leftDistance, rightDistance, topDistance, botDistance
-        };
-        Arrays.sort(distances);
-        smallest = distances[0];
-
-        // Animate to the closest edge
-        Point p = new Point();
-        if (smallest == leftDistance) {
-            p.x = capX(EDGE_OVERLAP, bv);
-            p.y = capY(topDistance, bv);
-        }
-        if (smallest == rightDistance) {
-            p.x = capX(mDisplaySize.x, bv);
-            p.y = capY(topDistance, bv);
-        }
-        if (smallest == topDistance) {
-            p.x = capX(leftDistance, bv);
-            p.y = capY(0, bv);
-        }
-        if (smallest == botDistance) {
-            p.x = capX(leftDistance, bv);
-            p.y = capY(mDisplaySize.y, bv);
-        }
-        return getTranslateAnim(bv, p, MAGNET_ANIM_TIME);
-    }
-
-    /**
-     * Animation to fling the provided view.
-     */
-    public AnimatorSet animateFlingTo(final BubbleStackView bv, float velX, float velY) {
-        Point pos = bv.getPosition();
-        Point endPos = getPointOnEdge(bv, pos, velX, velY);
-        endPos = new Point(capX(endPos.x, bv), capY(endPos.y, bv));
-        final double distance = Math.sqrt(Math.pow(endPos.x - pos.x, 2)
-                + Math.pow(endPos.y - pos.y, 2));
-        final float sumVel = Math.abs(velX) + Math.abs(velY);
-        final int duration = Math.max(Math.min(200, (int) (distance * 1000f / (sumVel / 2))), 50);
-        return getTranslateAnim(bv, endPos, duration);
-    }
-
-    /**
-     * Animation to translate the provided view.
-     */
-    public AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration) {
-        return getTranslateAnim(v, p, duration, 0);
-    }
-
-    /**
-     * Animation to translate the provided view.
-     */
-    public AnimatorSet getTranslateAnim(final FloatingView v, Point p,
-            int duration, int startDelay) {
-        return getTranslateAnim(v, p, duration, startDelay, null);
-    }
-
-    /**
-     * Animation to translate the provided view.
-     *
-     * @param v the view to translate.
-     * @param p the point to translate to.
-     * @param duration the duration of the animation.
-     * @param startDelay the start delay of the animation.
-     * @param listener the listener to add to the animation.
-     *
-     * @return the animation.
-     */
-    public static AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration,
-            int startDelay, AnimatorListener listener) {
-        Point curPos = v.getPosition();
-        final ValueAnimator animX = ValueAnimator.ofFloat(curPos.x, p.x);
-        animX.setDuration(duration);
-        animX.setStartDelay(startDelay);
-        animX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float value = (float) animation.getAnimatedValue();
-                v.setPositionX((int) value);
-            }
-        });
-
-        final ValueAnimator animY = ValueAnimator.ofFloat(curPos.y, p.y);
-        animY.setDuration(duration);
-        animY.setStartDelay(startDelay);
-        animY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float value = (float) animation.getAnimatedValue();
-                v.setPositionY((int) value);
-            }
-        });
-        if (listener != null) {
-            animY.addListener(listener);
-        }
-
-        AnimatorSet set = new AnimatorSet();
-        set.playTogether(animX, animY);
-        set.setInterpolator(FAST_OUT_SLOW_IN);
-        return set;
-    }
-
-
-    // TODO -- now that this is in system we should be able to get these better, but ultimately
-    // makes more sense to move to movement bounds style a la PIP
-    /**
-     * Returns the status bar height.
-     */
-    public int getStatusBarHeight() {
-        Resources res = mContext.getResources();
-        int resourceId = res.getIdentifier("status_bar_height", "dimen", "android");
-        if (resourceId > 0) {
-            return res.getDimensionPixelSize(resourceId);
-        }
-        return 0;
-    }
-
-    /**
-     * Returns the status bar height.
-     */
-    public int getNavBarHeight() {
-        Resources res = mContext.getResources();
-        int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
-        if (resourceId > 0) {
-            return res.getDimensionPixelSize(resourceId);
-        }
-        return 0;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index dcd121b..5fdf76f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -16,59 +16,88 @@
 
 package com.android.systemui.bubbles;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.app.ActivityView;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.OvershootInterpolator;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
 import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.R;
+import com.android.systemui.bubbles.animation.ExpandedAnimationController;
+import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
+import com.android.systemui.bubbles.animation.StackAnimationController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
-import com.android.systemui.statusbar.notification.stack.ViewState;
 
 /**
  * Renders bubbles in a stack and handles animating expanded and collapsed states.
  */
 public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView {
-
     private static final String TAG = "BubbleStackView";
+
+    /**
+     * Friction applied to fling animations. Since the stack must land on one of the sides of the
+     * screen, we want less friction horizontally so that the stack has a better chance of making it
+     * to the side without needing a spring.
+     */
+    private static final float FLING_FRICTION_X = 1.15f;
+    private static final float FLING_FRICTION_Y = 1.5f;
+
+    /**
+     * Damping ratio to use for the stack spring animation used to spring the stack to its final
+     * position after a fling.
+     */
+    private static final float SPRING_DAMPING_RATIO = 0.85f;
+
+    /**
+     * Minimum fling velocity required to trigger moving the stack from one side of the screen to
+     * the other.
+     */
+    private static final float ESCAPE_VELOCITY = 750f;
+
     private Point mDisplaySize;
 
-    private FrameLayout mBubbleContainer;
+    private final SpringAnimation mExpandedViewXAnim;
+    private final SpringAnimation mExpandedViewYAnim;
+
+    private PhysicsAnimationLayout mBubbleContainer;
+    private StackAnimationController mStackAnimationController;
+    private ExpandedAnimationController mExpandedAnimationController;
+
     private BubbleExpandedViewContainer mExpandedViewContainer;
 
     private int mBubbleSize;
     private int mBubblePadding;
+    private int mExpandedAnimateXDistance;
+    private int mExpandedAnimateYDistance;
 
     private boolean mIsExpanded;
     private int mExpandedBubbleHeight;
     private BubbleTouchHandler mTouchHandler;
     private BubbleView mExpandedBubble;
-    private Point mCollapsedPosition;
     private BubbleController.BubbleExpandListener mExpandListener;
 
     private boolean mViewUpdatedRequested = false;
@@ -110,8 +139,12 @@
         setOnTouchListener(mTouchHandler);
 
         Resources res = getResources();
-        mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
+        mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
         mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mExpandedAnimateXDistance =
+                res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
+        mExpandedAnimateYDistance =
+                res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_y_distance);
 
         mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
         mDisplaySize = new Point();
@@ -120,6 +153,19 @@
 
         int padding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
         int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
+
+        mStackAnimationController = new StackAnimationController();
+        mExpandedAnimationController = new ExpandedAnimationController();
+
+        mBubbleContainer = new PhysicsAnimationLayout(context);
+        mBubbleContainer.setMaxRenderedChildren(
+                getResources().getInteger(R.integer.bubbles_max_rendered));
+        mBubbleContainer.setController(mStackAnimationController);
+        mBubbleContainer.setElevation(elevation);
+        mBubbleContainer.setPadding(padding, 0, padding, 0);
+        mBubbleContainer.setClipChildren(false);
+        addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
         mExpandedViewContainer = (BubbleExpandedViewContainer)
                 LayoutInflater.from(context).inflate(R.layout.bubble_expanded_view,
                         this /* parent */, false /* attachToRoot */);
@@ -128,11 +174,19 @@
         mExpandedViewContainer.setClipChildren(false);
         addView(mExpandedViewContainer);
 
-        mBubbleContainer = new FrameLayout(context);
-        mBubbleContainer.setElevation(elevation);
-        mBubbleContainer.setPadding(padding, 0, padding, 0);
-        mBubbleContainer.setClipChildren(false);
-        addView(mBubbleContainer);
+        mExpandedViewXAnim =
+                new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
+        mExpandedViewXAnim.setSpring(
+                new SpringForce()
+                        .setStiffness(SpringForce.STIFFNESS_LOW)
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
+
+        mExpandedViewYAnim =
+                new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_Y);
+        mExpandedViewYAnim.setSpring(
+                new SpringForce()
+                        .setStiffness(SpringForce.STIFFNESS_LOW)
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
 
         setClipChildren(false);
     }
@@ -144,38 +198,6 @@
     }
 
     @Override
-    public void onMeasure(int widthSpec, int heightSpec) {
-        super.onMeasure(widthSpec, heightSpec);
-
-        int bubbleHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec),
-                MeasureSpec.UNSPECIFIED);
-        if (mIsExpanded) {
-            ViewGroup parent = (ViewGroup) getParent();
-            int parentWidth = MeasureSpec.makeMeasureSpec(
-                    MeasureSpec.getSize(parent.getWidth()), MeasureSpec.EXACTLY);
-            int parentHeight = MeasureSpec.makeMeasureSpec(
-                    MeasureSpec.getSize(parent.getHeight()), MeasureSpec.EXACTLY);
-            measureChild(mBubbleContainer, parentWidth, bubbleHeightSpec);
-
-            int expandedViewHeight = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec),
-                    MeasureSpec.UNSPECIFIED);
-            measureChild(mExpandedViewContainer, parentWidth, expandedViewHeight);
-            setMeasuredDimension(widthSpec, parentHeight);
-        } else {
-            // Not expanded
-            measureChild(mExpandedViewContainer, 0, 0);
-
-            // Bubbles are translated a little to stack on top of each other
-            widthSpec = MeasureSpec.makeMeasureSpec(getStackWidth(), MeasureSpec.EXACTLY);
-            measureChild(mBubbleContainer, widthSpec, bubbleHeightSpec);
-
-            heightSpec = MeasureSpec.makeMeasureSpec(mBubbleContainer.getMeasuredHeight(),
-                    MeasureSpec.EXACTLY);
-            setMeasuredDimension(widthSpec, heightSpec);
-        }
-    }
-
-    @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         float x = ev.getRawX();
         float y = ev.getRawY();
@@ -293,9 +315,11 @@
             boolean updatePosition) {
         bubbleView.update(entry);
         if (updatePosition && !mIsExpanded) {
-            // If alerting it gets promoted to top of the stack
-            mBubbleContainer.removeView(bubbleView);
-            mBubbleContainer.addView(bubbleView, 0);
+            // If alerting it gets promoted to top of the stack.
+            if (mBubbleContainer.indexOfChild(bubbleView) != 0) {
+                mBubbleContainer.removeViewAndThen(bubbleView,
+                        () -> mBubbleContainer.addView(bubbleView, 0));
+            }
             requestUpdate();
         }
         if (mIsExpanded && bubbleView.equals(mExpandedBubble)) {
@@ -359,36 +383,51 @@
         if (mIsExpanded != shouldExpand) {
             mIsExpanded = shouldExpand;
             updateExpandedBubble();
+            applyCurrentState();
+            //requestUpdate();
+
+            mIsAnimating = true;
+
+            Runnable updateAfter = () -> {
+                applyCurrentState();
+                mIsAnimating = false;
+                requestUpdate();
+            };
 
             if (shouldExpand) {
-                // Save current position so that we might return there
-                savePosition();
+                mBubbleContainer.setController(mExpandedAnimationController);
+                mExpandedAnimationController.expandFromStack(
+                                mStackAnimationController.getStackPosition(), updateAfter);
+            } else {
+                mBubbleContainer.cancelAllAnimations();
+                mExpandedAnimationController.collapseBackToStack(
+                        () -> {
+                            mBubbleContainer.setController(mStackAnimationController);
+                            updateAfter.run();
+                        });
             }
 
-            // Determine the translation for the stack
-            Point position = shouldExpand
-                    ? BubbleController.getExpandPoint(this, mBubbleSize, mDisplaySize)
-                    : mCollapsedPosition;
-            int delay = shouldExpand ? 0 : 100;
-            AnimatorSet translationAnim = BubbleMovementHelper.getTranslateAnim(this, position,
-                    200, delay, null);
-            if (!shouldExpand) {
-                // First collapse the stack, then translate, maybe should expand at same time?
-                animateStackExpansion(() -> translationAnim.start());
-            } else {
-                // First translate, then expand
-                translationAnim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        mIsAnimating = true;
-                    }
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        animateStackExpansion(() -> mIsAnimating = false);
-                    }
-                });
-                translationAnim.start();
+            final float xStart =
+                    mStackAnimationController.getStackPosition().x < getWidth() / 2
+                            ? -mExpandedAnimateXDistance
+                            : mExpandedAnimateXDistance;
+
+            final float yStart = Math.min(
+                    mStackAnimationController.getStackPosition().y,
+                    mExpandedAnimateYDistance);
+            final float yDest = getStatusBarHeight() + mExpandedBubble.getHeight() + mBubblePadding;
+
+            if (shouldExpand) {
+                mExpandedViewContainer.setTranslationX(xStart);
+                mExpandedViewContainer.setTranslationY(yStart);
+                mExpandedViewContainer.setAlpha(0f);
             }
+
+            mExpandedViewXAnim.animateToFinalPosition(shouldExpand ? 0f : xStart);
+            mExpandedViewYAnim.animateToFinalPosition(shouldExpand ? yDest : yStart);
+            mExpandedViewContainer.animate()
+                    .setDuration(100)
+                    .alpha(shouldExpand ? 1f : 0f);
         }
     }
 
@@ -401,14 +440,6 @@
                 + mBubbleContainer.getPaddingStart();
     }
 
-    /**
-     * Saves the current position of the stack, used to save user placement of the stack to
-     * return to after an animation.
-     */
-    private void savePosition() {
-        mCollapsedPosition = getPosition();
-    }
-
     private void notifyExpansionChanged(BubbleView bubbleView, boolean expanded) {
         if (mExpandListener != null) {
             NotificationEntry entry = bubbleView != null ? bubbleView.getEntry() : null;
@@ -420,31 +451,151 @@
         return getBubbleAt(0);
     }
 
-    private BubbleView getBubbleAt(int i) {
+    /** Return the BubbleView at the given index from the bubble container. */
+    public BubbleView getBubbleAt(int i) {
         return mBubbleContainer.getChildCount() > i
                 ? (BubbleView) mBubbleContainer.getChildAt(i)
                 : null;
     }
 
     @Override
-    public void setPosition(int x, int y) {
-        setPositionX(x);
-        setPositionY(y);
+    public void setPosition(float x, float y) {
+        mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y);
     }
 
     @Override
-    public void setPositionX(int x) {
-        setTranslationX(x);
+    public void setPositionX(float x) {
+        // Unsupported, use setPosition(x, y).
     }
 
     @Override
-    public void setPositionY(int y) {
-        setTranslationY(y);
+    public void setPositionY(float y) {
+        // Unsupported, use setPosition(x, y).
     }
 
     @Override
-    public Point getPosition() {
-        return new Point((int) getTranslationX(), (int) getTranslationY());
+    public PointF getPosition() {
+        return mStackAnimationController.getStackPosition();
+    }
+
+    /** Called when a drag operation on an individual bubble has started. */
+    public void onBubbleDragStart(BubbleView bubble) {
+        // TODO: Save position and snap back if not dismissed.
+    }
+
+    /** Called with the coordinates to which an individual bubble has been dragged. */
+    public void onBubbleDragged(BubbleView bubble, float x, float y) {
+        bubble.setTranslationX(x);
+        bubble.setTranslationY(y);
+    }
+
+    /** Called when a drag operation on an individual bubble has finished. */
+    public void onBubbleDragFinish(BubbleView bubble, float x, float y, float velX, float velY) {
+        // TODO: Add fling to bottom to dismiss.
+    }
+
+    void onDragStart() {
+        if (mIsExpanded) {
+            return;
+        }
+
+        mStackAnimationController.cancelStackPositionAnimations();
+        mBubbleContainer.setController(mStackAnimationController);
+        mIsAnimating = false;
+    }
+
+    void onDragged(float x, float y) {
+        // TODO: We can drag if animating - just need to reroute inflight anims to drag point.
+        if (mIsExpanded) {
+            return;
+        }
+
+        mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y);
+    }
+
+    void onDragFinish(float x, float y, float velX, float velY) {
+        // TODO: Add fling to bottom to dismiss.
+
+        if (mIsExpanded || mIsAnimating) {
+            return;
+        }
+
+        final boolean stackOnLeftSide = x
+                - mBubbleContainer.getChildAt(0).getWidth() / 2
+                < mDisplaySize.x / 2;
+
+        final boolean stackShouldFlingLeft = stackOnLeftSide
+                ? velX < ESCAPE_VELOCITY
+                : velX < -ESCAPE_VELOCITY;
+
+        final RectF stackBounds = mStackAnimationController.getAllowableStackPositionRegion();
+
+        // Target X translation (either the left or right side of the screen).
+        final float destinationRelativeX = stackShouldFlingLeft
+                ? stackBounds.left : stackBounds.right;
+
+        // Minimum velocity required for the stack to make it to the side of the screen.
+        final float escapeVelocity = getMinXVelocity(
+                x,
+                destinationRelativeX,
+                FLING_FRICTION_X);
+
+        // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity so
+        // that it'll make it all the way to the side of the screen.
+        final float startXVelocity = stackShouldFlingLeft
+                ? Math.min(escapeVelocity, velX)
+                : Math.max(escapeVelocity, velX);
+
+        mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_X,
+                startXVelocity,
+                FLING_FRICTION_X,
+                new SpringForce()
+                        .setStiffness(SpringForce.STIFFNESS_LOW)
+                        .setDampingRatio(SPRING_DAMPING_RATIO),
+                destinationRelativeX);
+
+        mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_Y,
+                velY,
+                FLING_FRICTION_Y,
+                new SpringForce()
+                        .setStiffness(SpringForce.STIFFNESS_LOW)
+                        .setDampingRatio(SPRING_DAMPING_RATIO),
+                /* destination */ null);
+    }
+
+    /**
+     * Minimum velocity, in pixels/second, required to get from x to destX while being slowed by a
+     * given frictional force.
+     *
+     * This is not derived using real math, I just made it up because the math in FlingAnimation
+     * looks hard and this seems to work. It doesn't actually matter because if it doesn't make it
+     * to the edge via Fling, it'll get Spring'd there anyway.
+     *
+     * TODO(tsuji, or someone who likes math): Figure out math.
+     */
+    private float getMinXVelocity(float x, float destX, float friction) {
+        return (destX - x) * (friction * 5) + ESCAPE_VELOCITY;
+    }
+
+    @Override
+    public void getBoundsOnScreen(Rect outRect) {
+        if (!mIsExpanded) {
+            mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
+        } else {
+            mBubbleContainer.getBoundsOnScreen(outRect);
+        }
+    }
+
+    private int getStatusBarHeight() {
+        if (getRootWindowInsets() != null) {
+            return Math.max(
+                    getRootWindowInsets().getSystemWindowInsetTop(),
+                    getRootWindowInsets().getDisplayCutout().getSafeInsetTop());
+        }
+
+        return 0;
     }
 
     private boolean isIntersecting(View view, float x, float y) {
@@ -478,22 +629,6 @@
             final PendingIntent intent = mExpandedBubble.getAppOverlayIntent();
             mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString());
             mExpandedViewContainer.setExpandedView(expandedView);
-            expandedView.setCallback(new ActivityView.StateCallback() {
-                @Override
-                public void onActivityViewReady(ActivityView view) {
-                    Log.d(TAG, "onActivityViewReady("
-                            + mExpandedBubble.getEntry().key + "): " + view);
-                    view.startActivity(intent);
-                }
-
-                @Override
-                public void onActivityViewDestroyed(ActivityView view) {
-                    NotificationEntry entry = mExpandedBubble != null
-                            ? mExpandedBubble.getEntry() : null;
-                    Log.d(TAG, "onActivityViewDestroyed(key="
-                            + ((entry != null) ? entry.key : "(none)") + "): " + view);
-                }
-            });
         } else {
             // Bubble with notification view expanded state
             ExpandableNotificationRow row = mExpandedBubble.getRowView();
@@ -510,9 +645,8 @@
             mExpandedViewContainer.setHeaderText(null);
 
         }
-        int pointerPosition = mExpandedBubble.getPosition().x
-                + (mExpandedBubble.getWidth() / 2);
-        mExpandedViewContainer.setPointerPosition(pointerPosition);
+        float pointerPosition = mExpandedBubble.getPosition().x + (mExpandedBubble.getWidth() / 2);
+        mExpandedViewContainer.setPointerPosition((int) pointerPosition);
     }
 
     private void applyCurrentState() {
@@ -522,7 +656,6 @@
         if (!mIsExpanded) {
             mExpandedViewContainer.setExpandedView(null);
         } else {
-            mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight());
             View expandedView = mExpandedViewContainer.getExpandedView();
             if (expandedView instanceof ActivityView) {
                 if (expandedView.isAttachedToWindow()) {
@@ -537,53 +670,6 @@
             BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
             bv.updateDotVisibility();
             bv.setZ(bubbsCount - i);
-
-            int transX = mIsExpanded ? (bv.getWidth() + mBubblePadding) * i : mBubblePadding * i;
-            ViewState viewState = new ViewState();
-            viewState.initFrom(bv);
-            viewState.xTranslation = transX;
-            viewState.applyToView(bv);
-
-            if (mIsExpanded) {
-                // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler
-                bv.setTag(new Point(transX, 0));
-            }
-        }
-    }
-
-    private void animateStackExpansion(Runnable endRunnable) {
-        int childCount = mBubbleContainer.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            BubbleView child = (BubbleView) mBubbleContainer.getChildAt(i);
-            int transX = mIsExpanded ? (mBubbleSize + mBubblePadding) * i : mBubblePadding * i;
-            int duration = childCount > 1 ? 200 : 0;
-            if (mIsExpanded) {
-                // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler
-                child.setTag(new Point(transX, 0));
-            }
-            ViewPropertyAnimator anim = child
-                    .animate()
-                    .setStartDelay(15 * i)
-                    .setDuration(duration)
-                    .setInterpolator(mIsExpanded
-                            ? new OvershootInterpolator()
-                            : new AccelerateInterpolator())
-                    .translationY(0)
-                    .translationX(transX);
-            final int fi = i;
-            // Probably want this choreographed with translation somehow / make it snappier
-            anim.withStartAction(() -> mIsAnimating = true);
-            anim.withEndAction(() -> {
-                if (endRunnable != null) {
-                    endRunnable.run();
-                }
-                if (fi == mBubbleContainer.getChildCount() - 1) {
-                    applyCurrentState();
-                    mIsAnimating = false;
-                    requestUpdate();
-                }
-            });
-            anim.start();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 97784b0..22cd2fc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -19,7 +19,7 @@
 import static com.android.systemui.pip.phone.PipDismissViewController.SHOW_TARGET_DELAY;
 
 import android.content.Context;
-import android.graphics.Point;
+import android.graphics.PointF;
 import android.os.Handler;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -37,18 +37,16 @@
 
     private BubbleController mController = Dependency.get(BubbleController.class);
     private PipDismissViewController mDismissViewController;
-    private BubbleMovementHelper mMovementHelper;
 
     // The position of the bubble on down event
-    private int mBubbleDownPosX;
-    private int mBubbleDownPosY;
+    private float mBubbleDownPosX;
+    private float mBubbleDownPosY;
     // The touch position on down event
-    private int mDownX = -1;
-    private int mDownY = -1;
+    private float mDownX = -1;
+    private float mDownY = -1;
 
     private boolean mMovedEnough;
     private int mTouchSlopSquared;
-    private float mMinFlingVelocity;
     private VelocityTracker mVelocityTracker;
 
     private boolean mInDismissTarget;
@@ -71,32 +69,27 @@
         /**
          * Sets the position of the view.
          */
-        void setPosition(int x, int y);
+        void setPosition(float x, float y);
 
         /**
          * Sets the x position of the view.
          */
-        void setPositionX(int x);
+        void setPositionX(float x);
 
         /**
          * Sets the y position of the view.
          */
-        void setPositionY(int y);
+        void setPositionY(float y);
 
         /**
          * @return the position of the view.
          */
-        Point getPosition();
+        PointF getPosition();
     }
 
     public BubbleTouchHandler(Context context) {
         final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         mTouchSlopSquared = touchSlop * touchSlop;
-
-        // Multiply by 3 for better fling
-        mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity() * 3;
-
-        mMovementHelper = new BubbleMovementHelper(context);
         mDismissViewController = new PipDismissViewController(context);
     }
 
@@ -119,9 +112,11 @@
         FloatingView floatingView = (FloatingView) targetView;
         boolean isBubbleStack = floatingView instanceof BubbleStackView;
 
-        Point startPos = floatingView.getPosition();
-        int rawX = (int) event.getRawX();
-        int rawY = (int) event.getRawY();
+        PointF startPos = floatingView.getPosition();
+        float rawX = event.getRawX();
+        float rawY = event.getRawY();
+        float x = mBubbleDownPosX + rawX - mDownX;
+        float y = mBubbleDownPosY + rawY - mDownY;
         switch (action) {
             case MotionEvent.ACTION_DOWN:
                 trackMovement(event);
@@ -134,6 +129,13 @@
                 mDownX = rawX;
                 mDownY = rawY;
                 mMovedEnough = false;
+
+                if (isBubbleStack) {
+                    stack.onDragStart();
+                } else {
+                    stack.onBubbleDragStart((BubbleView) floatingView);
+                }
+
                 break;
 
             case MotionEvent.ACTION_MOVE:
@@ -145,22 +147,23 @@
                     mDownX = rawX;
                     mDownY = rawY;
                 }
-                final int deltaX = rawX - mDownX;
-                final int deltaY = rawY - mDownY;
+                final float deltaX = rawX - mDownX;
+                final float deltaY = rawY - mDownY;
                 if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) {
                     mMovedEnough = true;
                 }
-                int x = mBubbleDownPosX + rawX - mDownX;
-                int y = mBubbleDownPosY + rawY - mDownY;
 
                 if (mMovedEnough) {
-                    if (floatingView instanceof BubbleView && mBubbleDraggingOut == null) {
+                    if (floatingView instanceof BubbleView) {
                         mBubbleDraggingOut = ((BubbleView) floatingView);
+                        stack.onBubbleDragged(mBubbleDraggingOut, x, y);
+                    } else {
+                        stack.onDragged(x, y);
                     }
-                    floatingView.setPosition(x, y);
                 }
                 // TODO - when we're in the target stick to it / animate in some way?
-                mInDismissTarget = mDismissViewController.updateTarget((View) floatingView);
+                mInDismissTarget = mDismissViewController.updateTarget(
+                        isBubbleStack ? stack.getBubbleAt(0) : (View) floatingView);
                 break;
 
             case MotionEvent.ACTION_CANCEL:
@@ -181,19 +184,9 @@
                     final float velX = mVelocityTracker.getXVelocity();
                     final float velY = mVelocityTracker.getYVelocity();
                     if (isBubbleStack) {
-                        if ((Math.abs(velY) > mMinFlingVelocity)
-                                || (Math.abs(velX) > mMinFlingVelocity)) {
-                            // It's being flung somewhere
-                            mMovementHelper.animateFlingTo(stack, velX, velY).start();
-                        } else {
-                            // Magnet back to nearest edge
-                            mMovementHelper.animateMagnetTo(stack).start();
-                        }
+                        stack.onDragFinish(x, y, velX, velY);
                     } else {
-                        // Individual bubble got dragged but not dismissed.. lets animate it back
-                        // into position
-                        Point toGoTo = (Point) ((View) floatingView).getTag();
-                        mMovementHelper.getTranslateAnim(floatingView, toGoTo, 100, 0).start();
+                        stack.onBubbleDragFinish(mBubbleDraggingOut, x, y, velX, velY);
                     }
                 } else if (floatingView.equals(stack.getExpandedBubble())) {
                     stack.collapseStack();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index e8432b9..dc94832 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -22,7 +22,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.graphics.Color;
-import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
@@ -60,6 +60,8 @@
     private NotificationEntry mEntry;
     private PendingIntent mAppOverlayIntent;
     private ActivityView mActivityView;
+    private boolean mActivityViewReady;
+    private boolean mActivityViewStarted;
 
     public BubbleView(Context context) {
         this(context, null);
@@ -192,10 +194,10 @@
                         fraction = showDot ? fraction : 1 - fraction;
                         mBadgedImageView.setDotScale(fraction);
                     }).withEndAction(() -> {
-                        if (!showDot) {
-                            mBadgedImageView.setShowDot(false);
-                        }
-                    }).start();
+                if (!showDot) {
+                    mBadgedImageView.setShowDot(false);
+                }
+            }).start();
         }
     }
 
@@ -232,8 +234,21 @@
      */
     public ActivityView getActivityView() {
         if (mActivityView == null) {
-            mActivityView = new ActivityView(mContext);
+            mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
+                    true /* singleTaskInstance */);
             Log.d(TAG, "[getActivityView] created: " + mActivityView);
+            mActivityView.setCallback(new ActivityView.StateCallback() {
+                @Override
+                public void onActivityViewReady(ActivityView view) {
+                    mActivityViewReady = true;
+                    mActivityView.startActivity(mAppOverlayIntent);
+                }
+
+                @Override
+                public void onActivityViewDestroyed(ActivityView view) {
+                    mActivityViewReady = false;
+                }
+            });
         }
         return mActivityView;
     }
@@ -245,46 +260,44 @@
         if (mActivityView == null) {
             return;
         }
-        // HACK: Only release if initialized. There's no way to know if the ActivityView has
-        // been initialized. Calling release() if it hasn't been initialized will crash.
-
+        if (!mActivityViewReady) {
+            // release not needed, never initialized?
+            mActivityView = null;
+            return;
+        }
+        // HACK: release() will crash if the view is not attached.
         if (!mActivityView.isAttachedToWindow()) {
-            // HACK: release() will crash if the view is not attached.
-
             mActivityView.setVisibility(View.GONE);
             tmpParent.addView(mActivityView, new LinearLayout.LayoutParams(
                     ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.MATCH_PARENT));
         }
-        try {
-            mActivityView.release();
-        } catch (IllegalStateException ex) {
-            Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex);
-        }
+
+        mActivityView.release();
 
         ((ViewGroup) mActivityView.getParent()).removeView(mActivityView);
         mActivityView = null;
     }
 
     @Override
-    public void setPosition(int x, int y) {
+    public void setPosition(float x, float y) {
         setPositionX(x);
         setPositionY(y);
     }
 
     @Override
-    public void setPositionX(int x) {
+    public void setPositionX(float x) {
         setTranslationX(x);
     }
 
     @Override
-    public void setPositionY(int y) {
+    public void setPositionY(float y) {
         setTranslationY(y);
     }
 
     @Override
-    public Point getPosition() {
-        return new Point((int) getTranslationX(), (int) getTranslationY());
+    public PointF getPosition() {
+        return new PointF(getTranslationX(), getTranslationY());
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
new file mode 100644
index 0000000..f3ca938
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2019 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.bubbles.animation;
+
+import android.graphics.PointF;
+import android.view.View;
+import android.view.WindowInsets;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.systemui.R;
+
+import com.google.android.collect.Sets;
+
+import java.util.Set;
+
+/**
+ * Animation controller for bubbles when they're in their expanded state, or animating to/from the
+ * expanded state. This controls the expansion animation as well as bubbles 'dragging out' to be
+ * dismissed.
+ */
+public class ExpandedAnimationController
+        extends PhysicsAnimationLayout.PhysicsAnimationController {
+
+    /**
+     * The stack position from which the bubbles were expanded. Saved in {@link #expandFromStack}
+     * and used to return to stack form in {@link #collapseBackToStack}.
+     */
+    private PointF mExpandedFrom;
+
+    /** Horizontal offset between bubbles, which we need to know to re-stack them. */
+    private float mStackOffsetPx;
+    /** Spacing between bubbles in the expanded state. */
+    private float mBubblePaddingPx;
+    /** Size of each bubble. */
+    private float mBubbleSizePx;
+
+    @Override
+    protected void setLayout(PhysicsAnimationLayout layout) {
+        super.setLayout(layout);
+        mStackOffsetPx = layout.getResources().getDimensionPixelSize(
+                R.dimen.bubble_stack_offset);
+        mBubblePaddingPx = layout.getResources().getDimensionPixelSize(
+                R.dimen.bubble_padding);
+        mBubbleSizePx = layout.getResources().getDimensionPixelSize(
+                R.dimen.individual_bubble_size);
+    }
+
+    /**
+     * Animates expanding the bubbles into a row along the top of the screen.
+     *
+     * @return The y-value to which the bubbles were expanded, in case that's useful.
+     */
+    public float expandFromStack(PointF expandedFrom, Runnable after) {
+        mExpandedFrom = expandedFrom;
+
+        // How much to translate the next bubble, so that it is not overlapping the previous one.
+        float translateNextBubbleXBy = mBubblePaddingPx;
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
+            mLayout.animatePositionForChildAtIndex(i, translateNextBubbleXBy, getExpandedY());
+            translateNextBubbleXBy += mBubbleSizePx + mBubblePaddingPx;
+        }
+
+        runAfterTranslationsEnd(after);
+        return getExpandedY();
+    }
+
+    /** Animate collapsing the bubbles back to their stacked position. */
+    public void collapseBackToStack(Runnable after) {
+        // Stack to the left if we're going to the left, or right if not.
+        final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mExpandedFrom.x) ? -1 : 1;
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
+            mLayout.animatePositionForChildAtIndex(
+                    i, mExpandedFrom.x + (sideMultiplier * i * mStackOffsetPx), mExpandedFrom.y);
+        }
+
+        runAfterTranslationsEnd(after);
+    }
+
+    /** The Y value of the row of expanded bubbles. */
+    private float getExpandedY() {
+        final WindowInsets insets = mLayout.getRootWindowInsets();
+        if (insets != null) {
+            return mBubblePaddingPx + Math.max(
+                    insets.getSystemWindowInsetTop(),
+                    insets.getDisplayCutout().getSafeInsetTop());
+        }
+
+        return mBubblePaddingPx;
+    }
+
+    /** Runs the given Runnable after all translation-related animations have ended. */
+    private void runAfterTranslationsEnd(Runnable after) {
+        DynamicAnimation.OnAnimationEndListener allEndedListener =
+                (animation, canceled, value, velocity) -> {
+                    if (!mLayout.arePropertiesAnimating(
+                            DynamicAnimation.TRANSLATION_X,
+                            DynamicAnimation.TRANSLATION_Y)) {
+                        after.run();
+                    }
+                };
+
+        mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_X);
+        mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_Y);
+    }
+
+    @Override
+    Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
+        return Sets.newHashSet(
+                DynamicAnimation.TRANSLATION_X,
+                DynamicAnimation.TRANSLATION_Y);
+    }
+
+    @Override
+    int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
+        return NONE;
+    }
+
+    @Override
+    float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
+        return 0;
+    }
+
+    @Override
+    SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
+        return new SpringForce()
+                .setStiffness(SpringForce.STIFFNESS_LOW)
+                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+    }
+
+    @Override
+    void onChildAdded(View child, int index) {
+        // TODO: Animate the new bubble into the row, and push the other bubbles out of the way.
+        child.setTranslationY(getExpandedY());
+    }
+
+    @Override
+    void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) {
+        // TODO: Animate the bubble out, and pull the other bubbles into its position.
+        actuallyRemove.run();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java
new file mode 100644
index 0000000..4e0abc8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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.bubbles.animation;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+/**
+ * End listener that removes itself from its animation when called for the first time. Useful since
+ * anonymous OnAnimationEndListener instances can't pass themselves to
+ * {@link DynamicAnimation#removeEndListener}, but can call through to this superclass
+ * implementation.
+ */
+public class OneTimeEndListener implements DynamicAnimation.OnAnimationEndListener {
+
+    @Override
+    public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+            float velocity) {
+        animation.removeEndListener(this);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
new file mode 100644
index 0000000..1ced3a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2019 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.bubbles.animation;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Layout that constructs physics-based animations for each of its children, which behave according
+ * to settings provided by a {@link PhysicsAnimationController} instance.
+ *
+ * See physics-animation-layout.md.
+ */
+public class PhysicsAnimationLayout extends FrameLayout {
+    private static final String TAG = "Bubbs.PAL";
+
+    /**
+     * Controls the construction, configuration, and use of the physics animations supplied by this
+     * layout.
+     */
+    abstract static class PhysicsAnimationController {
+
+        /**
+         * Constant to return from {@link #getNextAnimationInChain} if the animation should not be
+         * chained at all.
+         */
+        protected static final int NONE = -1;
+
+        /** Set of properties for which the layout should construct physics animations. */
+        abstract Set<DynamicAnimation.ViewProperty> getAnimatedProperties();
+
+        /**
+         * Returns the index of the next animation after the given index in the animation chain, or
+         * {@link #NONE} if it should not be chained, or if the chain should end at the given index.
+         *
+         * If a next index is returned, an update listener will be added to the animation at the
+         * given index that dispatches value updates to the animation at the next index. This
+         * creates a 'following' effect.
+         *
+         * Typical implementations of this method will return either index + 1, or index - 1, to
+         * create forward or backward chains between adjacent child views, but this is not required.
+         */
+        abstract int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index);
+
+        /**
+         * Offsets to be added to the value that chained animations of the given property dispatch
+         * to subsequent child animations.
+         *
+         * This is used for things like maintaining the 'stack' effect in Bubbles, where bubbles
+         * stack off to the left or right side slightly.
+         */
+        abstract float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property);
+
+        /**
+         * Returns the SpringForce to be used for the given child view's property animation. Despite
+         * these usually being similar or identical across properties and views, {@link SpringForce}
+         * also contains the SpringAnimation's final position, so we have to construct a new one for
+         * each animation rather than using a constant.
+         */
+        abstract SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view);
+
+        /**
+         * Called when a new child is added at the specified index. Controllers can use this
+         * opportunity to animate in the new view.
+         */
+        abstract void onChildAdded(View child, int index);
+
+        /**
+         * Called when a child is to be removed from the layout. Controllers can use this
+         * opportunity to animate out the new view before calling the provided callback to actually
+         * remove it.
+         *
+         * Controllers should be careful to ensure that actuallyRemove is called on all code paths
+         * or child views will never be removed.
+         */
+        abstract void onChildToBeRemoved(View child, int index, Runnable actuallyRemove);
+
+        protected PhysicsAnimationLayout mLayout;
+
+        PhysicsAnimationController() { }
+
+        protected void setLayout(PhysicsAnimationLayout layout) {
+            this.mLayout = layout;
+        }
+
+        protected PhysicsAnimationLayout getLayout() {
+            return mLayout;
+        }
+    }
+
+    /**
+     * End listeners that are called when every child's animation of the given property has
+     * finished.
+     */
+    protected final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation.OnAnimationEndListener>
+            mEndListenerForProperty = new HashMap<>();
+
+    /**
+     * List of views that were passed to removeView, but are currently being animated out. These
+     * views will be actually removed by the controller (via super.removeView) once they're done
+     * animating out.
+     */
+    private final List<View> mViewsToBeActuallyRemoved = new ArrayList<>();
+
+    /** The currently active animation controller. */
+    private PhysicsAnimationController mController;
+
+    /**
+     * The maximum number of children to render and animate at a time. See
+     * {@link #setMaxRenderedChildren}.
+     */
+    private int mMaxRenderedChildren = 5;
+
+    public PhysicsAnimationLayout(Context context) {
+        super(context);
+    }
+
+    /**
+     * The maximum number of children to render and animate at a time. Any child views added beyond
+     * this limit will be set to {@link View#GONE}. If any animations attempt to run on the view,
+     * the corresponding property will be set with no animation.
+     */
+    public void setMaxRenderedChildren(int max) {
+        this.mMaxRenderedChildren = max;
+    }
+
+    /**
+     * Sets the animation controller and constructs or reconfigures the layout's physics animations
+     * to meet the controller's specifications.
+     */
+    public void setController(PhysicsAnimationController controller) {
+        cancelAllAnimations();
+        mEndListenerForProperty.clear();
+
+        this.mController = controller;
+        mController.setLayout(this);
+
+        // Set up animations for this controller's animated properties.
+        for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
+            setUpAnimationsForProperty(property);
+        }
+    }
+
+    /**
+     * Sets an end listener that will be called when all child animations for a given property have
+     * stopped running.
+     */
+    public void setEndListenerForProperty(
+            DynamicAnimation.OnAnimationEndListener listener,
+            DynamicAnimation.ViewProperty property) {
+        mEndListenerForProperty.put(property, listener);
+    }
+
+    /**
+     * Removes the end listener that would have been called when all child animations for a given
+     * property stopped running.
+     */
+    public void removeEndListenerForProperty(DynamicAnimation.ViewProperty property) {
+        mEndListenerForProperty.remove(property);
+    }
+
+    /**
+     * Returns the index of the view that precedes the given index, ignoring views that were passed
+     * to removeView, but are currently being animated out before actually being removed.
+     *
+     * @return index of the preceding view, or -1 if there are none.
+     */
+    public int getPrecedingNonRemovedViewIndex(int index) {
+        for (int i = index + 1; i < getChildCount(); i++) {
+            View precedingView = getChildAt(i);
+            if (!mViewsToBeActuallyRemoved.contains(precedingView)) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        super.addView(child, index, params);
+        setChildrenVisibility();
+
+        // Set up animations for the new view, if the controller is set. If it isn't set, we'll be
+        // setting up animations for all children when setController is called.
+        if (mController != null) {
+            for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
+                setUpAnimationForChild(property, child, index);
+            }
+
+            mController.onChildAdded(child, index);
+        }
+    }
+
+    @Override
+    public void removeView(View view) {
+        removeViewAndThen(view, /* callback */ null);
+    }
+
+    /**
+     * Let the controller know that this view should be removed, and then call the callback once the
+     * controller has finished any removal animations and the view has actually been removed.
+     */
+    public void removeViewAndThen(View view, Runnable callback) {
+        if (mController != null) {
+            final int index = indexOfChild(view);
+            // Remove the view only if it exists in this layout, and we're not already working on
+            // animating its removal.
+            if (index > -1 && !mViewsToBeActuallyRemoved.contains(view)) {
+                mViewsToBeActuallyRemoved.add(view);
+                setChildrenVisibility();
+
+                // Tell the controller to animate this view out, and call the callback when it wants
+                // to actually remove the view.
+                mController.onChildToBeRemoved(view, index, () -> {
+                    removeViewImmediateAndThen(view, callback);
+                    mViewsToBeActuallyRemoved.remove(view);
+                });
+            }
+        } else {
+            // Without a controller, nobody will animate this view out, so it gets an unceremonious
+            // departure.
+            removeViewImmediateAndThen(view, callback);
+        }
+    }
+
+    /** Checks whether any animations of the given properties are still running. */
+    public boolean arePropertiesAnimating(DynamicAnimation.ViewProperty... properties) {
+        for (int i = 0; i < getChildCount(); i++) {
+            for (DynamicAnimation.ViewProperty property : properties) {
+                if (getAnimationAtIndex(property, i).isRunning()) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /** Cancels all animations that are running on all child views, for all properties. */
+    public void cancelAllAnimations() {
+        if (mController == null) {
+            return;
+        }
+
+        for (int i = 0; i < getChildCount(); i++) {
+            for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
+                getAnimationAtIndex(property, i).cancel();
+            }
+        }
+    }
+
+    /**
+     * Animates the property of the child at the given index to the given value, then runs the
+     * callback provided when the animation ends.
+     */
+    protected void animateValueForChildAtIndex(
+            DynamicAnimation.ViewProperty property,
+            int index,
+            float value,
+            float startVel,
+            Runnable after) {
+        if (index < getChildCount()) {
+            final SpringAnimation animation = getAnimationAtIndex(property, index);
+            if (after != null) {
+                animation.addEndListener(new OneTimeEndListener() {
+                    @Override
+                    public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
+                            float value, float velocity) {
+                        super.onAnimationEnd(animation, canceled, value, velocity);
+                        after.run();
+                    }
+                });
+            }
+
+            if (startVel != Float.MAX_VALUE) {
+                animation.setStartVelocity(startVel);
+            }
+
+            animation.animateToFinalPosition(value);
+        }
+    }
+
+    /** Shortcut to animate a value with a callback, but no start velocity. */
+    protected void animateValueForChildAtIndex(
+            DynamicAnimation.ViewProperty property,
+            int index,
+            float value,
+            Runnable after) {
+        animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, after);
+    }
+
+    /** Shortcut to animate a value with a start velocity, but no callback. */
+    protected void animateValueForChildAtIndex(
+            DynamicAnimation.ViewProperty property,
+            int index,
+            float value,
+            float startVel) {
+        animateValueForChildAtIndex(property, index, value, startVel, /* callback */ null);
+    }
+
+    /** Shortcut to animate a value without changing the velocity or providing a callback. */
+    protected void animateValueForChildAtIndex(
+            DynamicAnimation.ViewProperty property,
+            int index,
+            float value) {
+        animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, /* callback */ null);
+    }
+
+    /** Shortcut to animate a child view's TRANSLATION_X and TRANSLATION_Y values. */
+    protected void animatePositionForChildAtIndex(int index, float x, float y) {
+        animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_X, index, x);
+        animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_Y, index, y);
+    }
+
+    /** Whether the first child would be left of center if translated to the given x value. */
+    protected boolean isFirstChildXLeftOfCenter(float x) {
+        if (getChildCount() > 0) {
+            return x + (getChildAt(0).getWidth() / 2) < getWidth() / 2;
+        } else {
+            return false; // If there's no first child, really anything is correct, right?
+        }
+    }
+
+    /** ViewProperty's toString is useless, this returns a readable name for debug logging. */
+    protected static String getReadablePropertyName(DynamicAnimation.ViewProperty property) {
+        if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+            return "TRANSLATION_X";
+        } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
+            return "TRANSLATION_Y";
+        } else if (property.equals(DynamicAnimation.SCALE_X)) {
+            return "SCALE_X";
+        } else if (property.equals(DynamicAnimation.SCALE_Y)) {
+            return "SCALE_Y";
+        } else if (property.equals(DynamicAnimation.ALPHA)) {
+            return "ALPHA";
+        } else {
+            return "Unknown animation property.";
+        }
+    }
+
+
+    /** Immediately removes the view, without notifying the controller, then runs the callback. */
+    private void removeViewImmediateAndThen(View view, Runnable callback) {
+        super.removeView(view);
+
+        if (callback != null) {
+            callback.run();
+        }
+
+        setChildrenVisibility();
+    }
+
+    /**
+     * Retrieves the animation of the given property from the view at the given index via the view
+     * tag system.
+     */
+    private SpringAnimation getAnimationAtIndex(
+            DynamicAnimation.ViewProperty property, int index) {
+        return (SpringAnimation) getChildAt(index).getTag(getTagIdForProperty(property));
+    }
+
+    /** Sets up SpringAnimations of the given property for each child view in the layout. */
+    private void setUpAnimationsForProperty(DynamicAnimation.ViewProperty property) {
+        for (int i = 0; i < getChildCount(); i++) {
+            setUpAnimationForChild(property, getChildAt(i), i);
+        }
+    }
+
+    /** Constructs a SpringAnimation of the given property for a child view. */
+    private void setUpAnimationForChild(
+            DynamicAnimation.ViewProperty property, View child, int index) {
+        SpringAnimation newAnim = new SpringAnimation(child, property);
+        newAnim.addUpdateListener((animation, value, velocity) -> {
+            final int nextAnimInChain =
+                    mController.getNextAnimationInChain(property, indexOfChild(child));
+            if (nextAnimInChain == PhysicsAnimationController.NONE) {
+                return;
+            }
+
+            final int animIndex = indexOfChild(child);
+            final float offset =
+                    mController.getOffsetForChainedPropertyAnimation(property);
+
+            // If this property's animations should be chained, then check to see if there is a
+            // subsequent animation within the rendering limit, and if so, tell it to animate to
+            // this animation's new value (plus the offset).
+            if (nextAnimInChain < Math.min(
+                    getChildCount(),
+                    mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())) {
+                getAnimationAtIndex(property, animIndex + 1)
+                        .animateToFinalPosition(value + offset);
+            } else if (nextAnimInChain < getChildCount()) {
+                // If the next child view is not rendered, update the property directly without
+                // animating it, so that the view is still in the correct state if it later
+                // becomes visible.
+                for (int i = nextAnimInChain; i < getChildCount(); i++) {
+                    // 'value' here is the value of the last child within the rendering limit,
+                    // not the first child's value - so we want to subtract the last child's
+                    // index when calculating the offset.
+                    property.setValue(getChildAt(i), value + offset * (i - animIndex));
+                }
+            }
+        });
+
+        newAnim.setSpring(mController.getSpringForce(property, child));
+        newAnim.addEndListener(new AllAnimationsForPropertyFinishedEndListener(property));
+        child.setTag(getTagIdForProperty(property), newAnim);
+    }
+
+    /** Hides children beyond the max rendering count. */
+    private void setChildrenVisibility() {
+        for (int i = 0; i < getChildCount(); i++) {
+            getChildAt(i).setVisibility(
+                    // Ignore views that are animating out when calculating whether to hide the
+                    // view. That is, if we're supposed to render 5 views, but 4 are animating out
+                    // and will soon be removed, render up to 9 views temporarily.
+                    i < (mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())
+                        ? View.VISIBLE
+                        : View.GONE);
+        }
+    }
+
+    /** Return a stable ID to use as a tag key for the given property's animations. */
+    private int getTagIdForProperty(DynamicAnimation.ViewProperty property) {
+        if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+            return R.id.translation_x_dynamicanimation_tag;
+        } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
+            return R.id.translation_y_dynamicanimation_tag;
+        } else if (property.equals(DynamicAnimation.SCALE_X)) {
+            return R.id.scale_x_dynamicanimation_tag;
+        } else if (property.equals(DynamicAnimation.SCALE_Y)) {
+            return R.id.scale_y_dynamicanimation_tag;
+        } else if (property.equals(DynamicAnimation.ALPHA)) {
+            return R.id.alpha_dynamicanimation_tag;
+        }
+
+        return -1;
+    }
+
+    /**
+     * End listener that is added to each individual DynamicAnimation, which dispatches to a single
+     * listener when every other animation of the given property is no longer running.
+     *
+     * This is required since chained DynamicAnimations can stop and start again due to changes in
+     * upstream animations. This means that adding an end listener to just the last animation is not
+     * sufficient. By firing only when every other animation on the property has stopped running, we
+     * ensure that no animation will be restarted after the single end listener is called.
+     */
+    protected class AllAnimationsForPropertyFinishedEndListener
+            implements DynamicAnimation.OnAnimationEndListener {
+        private DynamicAnimation.ViewProperty mProperty;
+
+        AllAnimationsForPropertyFinishedEndListener(DynamicAnimation.ViewProperty property) {
+            this.mProperty = property;
+        }
+
+        @Override
+        public void onAnimationEnd(
+                DynamicAnimation anim, boolean canceled, float value, float velocity) {
+            if (!arePropertiesAnimating(mProperty)) {
+                if (mEndListenerForProperty.containsKey(mProperty)) {
+                    mEndListenerForProperty.get(mProperty).onAnimationEnd(anim, canceled, value,
+                            velocity);
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
new file mode 100644
index 0000000..a113a63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2019 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.bubbles.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FlingAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.systemui.R;
+
+import com.google.android.collect.Sets;
+
+import java.util.HashMap;
+import java.util.Set;
+
+/**
+ * Animation controller for bubbles when they're in their stacked state. Stacked bubbles sit atop
+ * each other with a slight offset to the left or right (depending on which side of the screen they
+ * are on). Bubbles 'follow' each other when dragged, and can be flung to the left or right sides of
+ * the screen.
+ */
+public class StackAnimationController extends
+        PhysicsAnimationLayout.PhysicsAnimationController {
+
+    private static final String TAG = "Bubbs.StackCtrl";
+
+    /** Scale factor to use initially for new bubbles being animated in. */
+    private static final float ANIMATE_IN_STARTING_SCALE = 1.15f;
+
+    /** Translation factor (multiplied by stack offset) to use for new bubbles being animated in. */
+    private static final int ANIMATE_IN_TRANSLATION_FACTOR = 4;
+
+    /**
+     * Values to use for the default {@link SpringForce} provided to the physics animation layout.
+     */
+    private static final float DEFAULT_STIFFNESS = 2500f;
+    private static final float DEFAULT_BOUNCINESS = 0.85f;
+
+    /**
+     * The canonical position of the stack. This is typically the position of the first bubble, but
+     * we need to keep track of it separately from the first bubble's translation in case there are
+     * no bubbles, or the first bubble was just added and being animated to its new position.
+     */
+    private PointF mStackPosition = new PointF();
+
+    /**
+     * Animations on the stack position itself, which would have been started in
+     * {@link #flingThenSpringFirstBubbleWithStackFollowing}. These animations dispatch to
+     * {@link #moveFirstBubbleWithStackFollowing} to move the entire stack (with 'following' effect)
+     * to a legal position on the side of the screen.
+     */
+    private HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mStackPositionAnimations =
+            new HashMap<>();
+
+    /** Horizontal offset of bubbles in the stack. */
+    private float mStackOffset;
+    /** Diameter of the bubbles themselves. */
+    private int mIndividualBubbleSize;
+    /** Size of spacing around the bubbles, separating it from the edge of the screen. */
+    private int mBubblePadding;
+    /** How far offscreen the stack rests. */
+    private int mBubbleOffscreen;
+    /** How far down the screen the stack starts, when there is no pre-existing location. */
+    private int mStackStartingVerticalOffset;
+
+    private Point mDisplaySize;
+    private RectF mAllowableStackPositionRegion;
+
+    @Override
+    protected void setLayout(PhysicsAnimationLayout layout) {
+        super.setLayout(layout);
+
+        Resources res = layout.getResources();
+        mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
+        mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
+        mStackStartingVerticalOffset =
+                res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
+
+        mDisplaySize = new Point();
+        WindowManager wm =
+                (WindowManager) layout.getContext().getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getSize(mDisplaySize);
+    }
+
+    /**
+     * Instantly move the first bubble to the given point, and animate the rest of the stack behind
+     * it with the 'following' effect.
+     */
+    public void moveFirstBubbleWithStackFollowing(float x, float y) {
+        moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X, x);
+        moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y, y);
+    }
+
+    /**
+     * The position of the stack - typically the position of the first bubble; if no bubbles have
+     * been added yet, it will be where the first bubble will go when added.
+     */
+    public PointF getStackPosition() {
+        return mStackPosition;
+    }
+
+    /**
+     * Flings the first bubble along the given property's axis, using the provided configuration
+     * values. When the animation ends - either by hitting the min/max, or by friction sufficiently
+     * reducing momentum - a SpringAnimation takes over to snap the bubble to the given final
+     * position.
+     */
+    public void flingThenSpringFirstBubbleWithStackFollowing(
+            DynamicAnimation.ViewProperty property,
+            float vel,
+            float friction,
+            SpringForce spring,
+            Float finalPosition) {
+        Log.d(TAG, String.format("Flinging %s.",
+                        PhysicsAnimationLayout.getReadablePropertyName(property)));
+
+        StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
+        final float currentValue = firstBubbleProperty.getValue(this);
+        final RectF bounds = getAllowableStackPositionRegion();
+        final float min =
+                property.equals(DynamicAnimation.TRANSLATION_X)
+                        ? bounds.left
+                        : bounds.top;
+        final float max =
+                property.equals(DynamicAnimation.TRANSLATION_X)
+                        ? bounds.right
+                        : bounds.bottom;
+
+        FlingAnimation flingAnimation = new FlingAnimation(this, firstBubbleProperty);
+        flingAnimation.setFriction(friction)
+                .setStartVelocity(vel)
+
+                // If the bubble's property value starts beyond the desired min/max, use that value
+                // instead so that the animation won't immediately end. If, for example, the user
+                // drags the bubbles into the navigation bar, but then flings them upward, we want
+                // the fling to occur despite temporarily having a value outside of the min/max. If
+                // the bubbles are out of bounds and flung even farther out of bounds, the fling
+                // animation will halt immediately and the SpringAnimation will take over, springing
+                // it in reverse to the (legal) final position.
+                .setMinValue(Math.min(currentValue, min))
+                .setMaxValue(Math.max(currentValue, max))
+
+                .addEndListener((animation, canceled, endValue, endVelocity) -> {
+                    if (!canceled) {
+                        springFirstBubbleWithStackFollowing(property, spring, endVelocity,
+                                finalPosition != null
+                                        ? finalPosition
+                                        : Math.max(min, Math.min(max, endValue)));
+                    }
+                });
+
+        cancelStackPositionAnimation(property);
+        mStackPositionAnimations.put(property, flingAnimation);
+        flingAnimation.start();
+    }
+
+    /**
+     * Cancel any stack position animations that were started by calling
+     * @link #flingThenSpringFirstBubbleWithStackFollowing}, and remove any corresponding end
+     * listeners.
+     */
+    public void cancelStackPositionAnimations() {
+        cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_X);
+        cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_Y);
+
+        mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X);
+        mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_Y);
+    }
+
+    /**
+     * Returns the region within which the stack is allowed to rest. This goes slightly off the left
+     * and right sides of the screen, below the status bar/cutout and above the navigation bar.
+     * While the stack is not allowed to rest outside of these bounds, it can temporarily be
+     * animated or dragged beyond them.
+     */
+    public RectF getAllowableStackPositionRegion() {
+        final WindowInsets insets = mLayout.getRootWindowInsets();
+        mAllowableStackPositionRegion = new RectF();
+
+        if (insets != null) {
+            mAllowableStackPositionRegion.left =
+                    -mBubbleOffscreen
+                            - mBubblePadding
+                            + Math.max(
+                            insets.getSystemWindowInsetLeft(),
+                            insets.getDisplayCutout().getSafeInsetLeft());
+            mAllowableStackPositionRegion.right =
+                    mLayout.getWidth()
+                            - mIndividualBubbleSize
+                            + mBubbleOffscreen
+                            - mBubblePadding
+                            - Math.max(
+                            insets.getSystemWindowInsetRight(),
+                            insets.getDisplayCutout().getSafeInsetRight());
+
+            mAllowableStackPositionRegion.top =
+                    mBubblePadding
+                            + Math.max(
+                            insets.getSystemWindowInsetTop(),
+                            insets.getDisplayCutout().getSafeInsetTop());
+            mAllowableStackPositionRegion.bottom =
+                    mLayout.getHeight()
+                            - mIndividualBubbleSize
+                            - mBubblePadding
+                            - Math.max(
+                            insets.getSystemWindowInsetBottom(),
+                            insets.getDisplayCutout().getSafeInsetBottom());
+        }
+
+        return mAllowableStackPositionRegion;
+    }
+
+    @Override
+    Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
+        return Sets.newHashSet(
+                DynamicAnimation.TRANSLATION_X, // For positioning.
+                DynamicAnimation.TRANSLATION_Y,
+                DynamicAnimation.ALPHA,         // For fading in new bubbles.
+                DynamicAnimation.SCALE_X,       // For 'popping in' new bubbles.
+                DynamicAnimation.SCALE_Y);
+    }
+
+    @Override
+    int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
+        if (property.equals(DynamicAnimation.TRANSLATION_X)
+                || property.equals(DynamicAnimation.TRANSLATION_Y)) {
+            return index + 1; // Just chain them linearly.
+        } else {
+            return NONE;
+        }
+    }
+
+
+    @Override
+    float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
+        if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+            // Offset to the left if we're on the left, or the right otherwise.
+            return mLayout.isFirstChildXLeftOfCenter(mStackPosition.x)
+                    ? -mStackOffset : mStackOffset;
+        } else {
+            return 0f;
+        }
+    }
+
+    @Override
+    SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
+        return new SpringForce()
+                .setDampingRatio(DEFAULT_BOUNCINESS)
+                .setStiffness(DEFAULT_STIFFNESS);
+    }
+
+    @Override
+    void onChildAdded(View child, int index) {
+        // If this is the first child added, position the stack in its starting position.
+        if (mLayout.getChildCount() == 1) {
+            moveStackToStartPosition();
+        }
+
+        if (mLayout.indexOfChild(child) == 0) {
+            child.setTranslationY(mStackPosition.y);
+
+            // Pop in the new bubble.
+            child.setScaleX(ANIMATE_IN_STARTING_SCALE);
+            child.setScaleY(ANIMATE_IN_STARTING_SCALE);
+            mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_X, 0, 1f);
+            mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_Y, 0, 1f);
+
+            // Fade in the new bubble.
+            child.setAlpha(0);
+            mLayout.animateValueForChildAtIndex(DynamicAnimation.ALPHA, 0, 1f);
+
+            // Start the new bubble 4x the normal offset distance in the opposite direction. We'll
+            // animate in from this position. Since the animations are chained, when the new bubble
+            // flies in from the side, it will push the other ones out of the way.
+            float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+            child.setTranslationX(mStackPosition.x - (ANIMATE_IN_TRANSLATION_FACTOR * xOffset));
+            mLayout.animateValueForChildAtIndex(
+                    DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x);
+        }
+    }
+
+    @Override
+    void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) {
+        // Animate the child out, actually removing it once its alpha is zero.
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.ALPHA, index, 0f, () -> {
+                    actuallyRemove.run();
+                });
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.SCALE_X, index, ANIMATE_IN_STARTING_SCALE);
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.SCALE_Y, index, ANIMATE_IN_STARTING_SCALE);
+
+        final boolean hasPrecedingChild = index + 1 < mLayout.getChildCount();
+        if (hasPrecedingChild) {
+            final int precedingViewIndex = mLayout.getPrecedingNonRemovedViewIndex(index);
+            if (precedingViewIndex >= 0) {
+                final float offsetX =
+                        getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+                mLayout.animatePositionForChildAtIndex(
+                        precedingViewIndex,
+                        mStackPosition.x + (index * offsetX),
+                        mStackPosition.y);
+            }
+        }
+    }
+
+    /** Moves the stack, without any animation, to the starting position. */
+    private void moveStackToStartPosition() {
+        mLayout.post(() -> setStackPosition(
+                getAllowableStackPositionRegion().right,
+                getAllowableStackPositionRegion().top + mStackStartingVerticalOffset));
+    }
+
+    /**
+     * Moves the first bubble instantly to the given X or Y translation, and instructs subsequent
+     * bubbles to animate 'following' to the new location.
+     */
+    private void moveFirstBubbleWithStackFollowing(
+            DynamicAnimation.ViewProperty property, float value) {
+
+        // Update the canonical stack position.
+        if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+            mStackPosition.x = value;
+        } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
+            mStackPosition.y = value;
+        }
+
+        if (mLayout.getChildCount() > 0) {
+            property.setValue(mLayout.getChildAt(0), value);
+            mLayout.animateValueForChildAtIndex(
+                    property,
+                    /* index */ 1,
+                    value + getOffsetForChainedPropertyAnimation(property));
+        }
+    }
+
+    /** Moves the stack to a position instantly, with no animation. */
+    private void setStackPosition(float x, float y) {
+        Log.d(TAG, String.format("Setting position to (%f, %f).", x, y));
+        mStackPosition.set(x, y);
+
+        cancelStackPositionAnimations();
+
+        // Since we're not using the chained animations, apply the offsets manually.
+        final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+        final float yOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_Y);
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
+            mLayout.getChildAt(i).setTranslationX(x + (i * xOffset));
+            mLayout.getChildAt(i).setTranslationY(y + (i * yOffset));
+        }
+    }
+
+    /**
+     * Springs the first bubble to the given final position, with the rest of the stack 'following'.
+     */
+    private void springFirstBubbleWithStackFollowing(
+            DynamicAnimation.ViewProperty property, SpringForce spring,
+            float vel, float finalPosition) {
+
+        Log.d(TAG, String.format("Springing %s to final position %f.",
+                        PhysicsAnimationLayout.getReadablePropertyName(property),
+                        finalPosition));
+
+        StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
+        SpringAnimation springAnimation =
+                new SpringAnimation(this, firstBubbleProperty)
+                        .setSpring(spring)
+                        .setStartVelocity(vel);
+
+        cancelStackPositionAnimation(property);
+        mStackPositionAnimations.put(property, springAnimation);
+        springAnimation.animateToFinalPosition(finalPosition);
+    }
+
+    /**
+     * Cancels any outstanding first bubble property animations that are running. This does not
+     * affect the SpringAnimations controlling the individual bubbles' 'following' effect - it only
+     * cancels animations started from {@link #springFirstBubbleWithStackFollowing} and
+     * {@link #flingThenSpringFirstBubbleWithStackFollowing}.
+     */
+    private void cancelStackPositionAnimation(DynamicAnimation.ViewProperty property) {
+        if (mStackPositionAnimations.containsKey(property)) {
+            mStackPositionAnimations.get(property).cancel();
+        }
+    }
+
+    /**
+     * FloatProperty that uses {@link #moveFirstBubbleWithStackFollowing} to set the first bubble's
+     * translation and animate the rest of the stack with it. A DynamicAnimation can animate this
+     * property directly to move the first bubble and cause the stack to 'follow' to the new
+     * location.
+     *
+     * This could also be achieved by simply animating the first bubble view and adding an update
+     * listener to dispatch movement to the rest of the stack. However, this would require
+     * duplication of logic in that update handler - it's simpler to keep all logic contained in the
+     * {@link #moveFirstBubbleWithStackFollowing} method.
+     */
+    private class StackPositionProperty
+            extends FloatPropertyCompat<StackAnimationController> {
+        private final DynamicAnimation.ViewProperty mProperty;
+
+        private StackPositionProperty(DynamicAnimation.ViewProperty property) {
+            super(property.toString());
+            mProperty = property;
+        }
+
+        @Override
+        public float getValue(StackAnimationController controller) {
+            return mProperty.getValue(mLayout.getChildAt(0));
+        }
+
+        @Override
+        public void setValue(StackAnimationController controller, float value) {
+            moveFirstBubbleWithStackFollowing(mProperty, value);
+        }
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 7b18fad..f5ac0d3 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -1090,9 +1090,16 @@
             }
         }
 
+        protected int getActionLayoutId(Context context) {
+            if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) {
+                return com.android.systemui.R.layout.global_actions_grid_item;
+            }
+            return com.android.systemui.R.layout.global_actions_item;
+        }
+
         public View create(
                 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
-            View v = inflater.inflate(com.android.systemui.R.layout.global_actions_item, parent,
+            View v = inflater.inflate(getActionLayoutId(context), parent,
                     false);
 
             ImageView icon = (ImageView) v.findViewById(R.id.icon);
@@ -1498,7 +1505,8 @@
             window.setBackgroundDrawable(mGradientDrawable);
             window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
 
-            setContentView(com.android.systemui.R.layout.global_actions_wrapped);
+
+            setContentView(getGlobalActionsLayoutId(context));
             mGlobalActionsLayout = (MultiListLayout)
                     findViewById(com.android.systemui.R.id.global_actions_view);
             mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss());
@@ -1515,6 +1523,13 @@
             setTitle(R.string.global_actions);
         }
 
+        private int getGlobalActionsLayoutId(Context context) {
+            if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) {
+                return com.android.systemui.R.layout.global_actions_grid;
+            }
+            return com.android.systemui.R.layout.global_actions_wrapped;
+        }
+
         private void updateList() {
             mGlobalActionsLayout.removeAllItems();
             ArrayList<Action> separatedActions =
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
new file mode 100644
index 0000000..0e49b5f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2019 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.globalactions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.HardwareBgDrawable;
+import com.android.systemui.MultiListLayout;
+
+/**
+ * Grid-based implementation of the button layout created by the global actions dialog.
+ */
+public class GlobalActionsGridLayout extends MultiListLayout {
+
+    boolean mBackgroundsSet;
+
+    public GlobalActionsGridLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    private void setBackgrounds() {
+        HardwareBgDrawable listBackground  = new HardwareBgDrawable(true, true, getContext());
+        HardwareBgDrawable separatedViewBackground = new HardwareBgDrawable(true, true,
+                getContext());
+        getListView().setBackground(listBackground);
+        getSeparatedView().setBackground(separatedViewBackground);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        // backgrounds set only once, the first time onMeasure is called after inflation
+        if (getListView() != null && !mBackgroundsSet) {
+            setBackgrounds();
+            mBackgroundsSet = true;
+        }
+    }
+
+    @Override
+    public void setExpectedListItemCount(int count) {
+        mExpectedListItemCount = count;
+        getListView().setExpectedCount(count);
+    }
+
+    @Override
+    protected ViewGroup getSeparatedView() {
+        return findViewById(com.android.systemui.R.id.separated_button);
+    }
+
+    @Override
+    protected ListGridLayout getListView() {
+        return findViewById(android.R.id.list);
+    }
+
+    @Override
+    public void removeAllItems() {
+        ViewGroup separatedList = getSeparatedView();
+        ListGridLayout list = getListView();
+        if (separatedList != null) {
+            separatedList.removeAllViews();
+        }
+        if (list != null) {
+            list.removeAllItems();
+        }
+    }
+
+    @Override
+    public ViewGroup getParentView(boolean separated, int index) {
+        if (separated) {
+            return getSeparatedView();
+        } else {
+            return getListView().getParentView(index);
+        }
+    }
+
+    /**
+     * Not used in this implementation of the Global Actions Menu, but necessary for some others.
+     */
+    @Override
+    public void setDivisionView(View v) {
+
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
new file mode 100644
index 0000000..3775515
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 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.globalactions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * Layout which uses nested LinearLayouts to create a grid with the following behavior:
+ *
+ * * Try to maintain a 'square' grid (equal number of columns and rows) based on the expected item
+ *   count.
+ * * Display and hide sub-lists as needed, depending on the expected item count.
+ * * Favor bias toward having more rows or columns depending on the orientation of the device
+ *   (TODO(123344999): Implement this, currently always favors adding more rows.)
+ * * Change the orientation (horizontal vs. vertical) of the container and sub-lists to act as rows
+ *   or columns depending on the orientation of the device.
+ *   (TODO(123344999): Implement this, currently always columns.)
+ *
+ * While we could implement this behavior with a GridLayout, it would take significantly more
+ * time and effort, and would require more substantial refactoring of the existing code in
+ * GlobalActionsDialog, since it would require manipulation of the child items themselves.
+ *
+ */
+
+public class ListGridLayout extends LinearLayout {
+    private int mExpectedCount;
+    private int mRows;
+    private int mColumns;
+
+    public ListGridLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * Remove all items from this grid.
+     */
+    public void removeAllItems() {
+        for (int i = 0; i < getChildCount(); i++) {
+            ViewGroup subList = (ViewGroup) getChildAt(i);
+            if (subList != null) {
+                subList.removeAllViews();
+            }
+        }
+    }
+
+    /**
+     * Get the parent view associated with the item which should be placed at the given position.
+     */
+    public ViewGroup getParentView(int index) {
+        ViewGroup firstParent = (ViewGroup) getChildAt(0);
+        if (mRows == 0) {
+            return firstParent;
+        }
+        int column = (int) Math.floor(index / mRows);
+        ViewGroup parent = (ViewGroup) getChildAt(column);
+        return parent != null ? parent : firstParent;
+    }
+
+    /**
+     * Sets the expected number of items that this grid will be responsible for rendering.
+     */
+    public void setExpectedCount(int count) {
+        mExpectedCount = count;
+        mRows = getRowCount();
+        mColumns = getColumnCount();
+
+        for (int i = 0; i < getChildCount(); i++) {
+            if (i <= mColumns) {
+                setSublistVisibility(i, true);
+            } else {
+                setSublistVisibility(i, false);
+            }
+        }
+
+    }
+
+    private void setSublistVisibility(int index, boolean visible) {
+        View subList = getChildAt(index);
+        Log.d("ListGrid", "index: " + index  + ", visibility: "  + visible);
+        if (subList != null) {
+            subList.setVisibility(visible ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    private int getRowCount() {
+        return (int) Math.ceil(Math.sqrt(mExpectedCount));
+    }
+
+    private int getColumnCount() {
+        return (int) Math.round(Math.sqrt(mExpectedCount));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index b218e80..2339fae 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -30,8 +30,12 @@
 import com.android.systemui.appops.AppOpItem
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.R
+import java.lang.ref.WeakReference
+import javax.inject.Inject
+import javax.inject.Singleton
 
-class PrivacyItemController(val context: Context, val callback: Callback) {
+@Singleton
+class PrivacyItemController @Inject constructor(val context: Context) {
 
     companion object {
         val OPS = intArrayOf(AppOpsManager.OP_CAMERA,
@@ -56,9 +60,10 @@
     private val uiHandler = Dependency.get(Dependency.MAIN_HANDLER)
     private var listening = false
     val systemApp = PrivacyApplication(context.getString(R.string.device_services), context)
+    private val callbacks = mutableListOf<WeakReference<Callback>>()
 
     private val notifyChanges = Runnable {
-        callback.privacyChanged(privacyList)
+        callbacks.forEach { it.get()?.privacyChanged(privacyList) }
     }
 
     private val updateListAndNotifyChanges = Runnable {
@@ -88,8 +93,8 @@
             registerReceiver()
         }
 
-    init {
-        registerReceiver()
+    private fun unregisterReceiver() {
+        context.unregisterReceiver(userSwitcherReceiver)
     }
 
     private fun registerReceiver() {
@@ -108,17 +113,41 @@
         bgHandler.post(updateListAndNotifyChanges)
     }
 
-    fun setListening(listen: Boolean) {
+    @VisibleForTesting
+    internal fun setListening(listen: Boolean) {
         if (listening == listen) return
         listening = listen
         if (listening) {
             appOpsController.addCallback(OPS, cb)
+            registerReceiver()
             update(true)
         } else {
             appOpsController.removeCallback(OPS, cb)
+            unregisterReceiver()
         }
     }
 
+    private fun addCallback(callback: WeakReference<Callback>) {
+        callbacks.add(callback)
+        if (callbacks.isNotEmpty() && !listening) setListening(true)
+        // Notify this callback if we didn't set to listening
+        else uiHandler.post(NotifyChangesToCallback(callback.get(), privacyList))
+    }
+
+    private fun removeCallback(callback: WeakReference<Callback>) {
+        // Removes also if the callback is null
+        callbacks.removeIf { it.get()?.equals(callback.get()) ?: true }
+        if (callbacks.isEmpty()) setListening(false)
+    }
+
+    fun addCallback(callback: Callback) {
+        addCallback(WeakReference(callback))
+    }
+
+    fun removeCallback(callback: Callback) {
+        removeCallback(WeakReference(callback))
+    }
+
     private fun updatePrivacyList() {
         privacyList = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
                 .mapNotNull { toPrivacyItem(it) }.distinct()
@@ -149,4 +178,13 @@
             }
         }
     }
+
+    private class NotifyChangesToCallback(
+        private val callback: Callback?,
+        private val list: List<PrivacyItem>
+    ) : Runnable {
+        override fun run() {
+            callback?.privacyChanged(list)
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index e63f88a..c0ed4b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -30,14 +30,18 @@
 import android.graphics.drawable.RippleDrawable;
 import android.os.Bundle;
 import android.os.UserManager;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
@@ -45,7 +49,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.keyguard.CarrierText;
+import com.android.keyguard.CarrierTextController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.settingslib.Utils;
 import com.android.settingslib.drawable.UserIconDrawable;
@@ -68,7 +72,11 @@
 import javax.inject.Named;
 
 public class QSFooterImpl extends FrameLayout implements QSFooter,
-        OnClickListener, OnUserInfoChangedListener, EmergencyListener, SignalCallback {
+        OnClickListener, OnUserInfoChangedListener, EmergencyListener, SignalCallback,
+        CarrierTextController.CarrierTextCallback {
+
+    private static final int SIM_SLOTS = 2;
+    private static final String TAG = "QSFooterImpl";
 
     private final ActivityStarter mActivityStarter;
     private final UserInfoController mUserInfoController;
@@ -77,7 +85,6 @@
     private SettingsButton mSettingsButton;
     protected View mSettingsContainer;
     private PageIndicator mPageIndicator;
-    private CarrierText mCarrierText;
 
     private boolean mQsDisabled;
     private QSPanel mQsPanel;
@@ -99,12 +106,20 @@
 
     private View mActionsContainer;
     private View mDragHandle;
-    private View mMobileGroup;
-    private ImageView mMobileSignal;
-    private ImageView mMobileRoaming;
+
+    private View mCarrierDivider;
+    private ViewGroup mMobileFooter;
+    private View[] mMobileGroups = new View[SIM_SLOTS];
+    private ViewGroup[] mCarrierGroups = new ViewGroup[SIM_SLOTS];
+    private TextView[] mCarrierTexts = new TextView[SIM_SLOTS];
+    private ImageView[] mMobileSignals = new ImageView[SIM_SLOTS];
+    private ImageView[] mMobileRoamings = new ImageView[SIM_SLOTS];
+    private final CellSignalState[] mInfos =
+            new CellSignalState[]{new CellSignalState(), new CellSignalState()};
+
     private final int mColorForeground;
-    private final CellSignalState mInfo = new CellSignalState();
     private OnClickListener mExpandClickListener;
+    private CarrierTextController mCarrierTextController;
 
     @Inject
     public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
@@ -134,10 +149,20 @@
         mSettingsContainer = findViewById(R.id.settings_button_container);
         mSettingsButton.setOnClickListener(this);
 
-        mMobileGroup = findViewById(R.id.mobile_combo);
-        mMobileSignal = findViewById(R.id.mobile_signal);
-        mMobileRoaming = findViewById(R.id.mobile_roaming);
-        mCarrierText = findViewById(R.id.qs_carrier_text);
+        mMobileFooter = findViewById(R.id.qs_mobile);
+        mCarrierGroups[0] = findViewById(R.id.carrier1);
+        mCarrierGroups[1] = findViewById(R.id.carrier2);
+
+        for (int i = 0; i < SIM_SLOTS; i++) {
+            mMobileGroups[i] = mCarrierGroups[i].findViewById(R.id.mobile_combo);
+            mMobileSignals[i] = mCarrierGroups[i].findViewById(R.id.mobile_signal);
+            mMobileRoamings[i] = mCarrierGroups[i].findViewById(R.id.mobile_roaming);
+            mCarrierTexts[i] = mCarrierGroups[i].findViewById(R.id.qs_carrier_text);
+        }
+        mCarrierDivider = findViewById(R.id.qs_carrier_divider);
+        CharSequence separator = mContext.getString(
+                com.android.internal.R.string.kg_text_message_separator);
+        mCarrierTextController = new CarrierTextController(mContext, separator, false, false);
 
         mMultiUserSwitch = findViewById(R.id.multi_user_switch);
         mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
@@ -204,8 +229,8 @@
     private TouchAnimator createFooterAnimator() {
         return new TouchAnimator.Builder()
                 .addFloat(mDivider, "alpha", 0, 1)
-                .addFloat(mCarrierText, "alpha", 0, 0, 1)
-                .addFloat(mMobileGroup, "alpha", 0, 1)
+                .addFloat(mMobileFooter, "alpha", 0, 0, 1)
+                .addFloat(mCarrierDivider, "alpha", 0, 1)
                 .addFloat(mActionsContainer, "alpha", 0, 1)
                 .addFloat(mDragHandle, "alpha", 1, 0, 0)
                 .addFloat(mPageIndicator, "alpha", 0, 1)
@@ -332,10 +357,12 @@
                 mNetworkController.addEmergencyListener(this);
                 mNetworkController.addCallback(this);
             }
+            mCarrierTextController.setListening(this);
         } else {
             mUserInfoController.removeCallback(this);
             mNetworkController.removeEmergencyListener(this);
             mNetworkController.removeCallback(this);
+            mCarrierTextController.setListening(null);
         }
     }
 
@@ -358,7 +385,8 @@
         if (v == mSettingsButton) {
             if (!mDeviceProvisionedController.isCurrentUserSetup()) {
                 // If user isn't setup just unlock the device and dump them back at SUW.
-                mActivityStarter.postQSRunnableDismissingKeyguard(() -> { });
+                mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
+                });
                 return;
             }
             MetricsLogger.action(mContext,
@@ -415,32 +443,64 @@
     }
 
     private void handleUpdateState() {
-        mMobileGroup.setVisibility(mInfo.visible ? View.VISIBLE : View.GONE);
-        if (mInfo.visible) {
-            mMobileRoaming.setVisibility(mInfo.roaming ? View.VISIBLE : View.GONE);
-            mMobileRoaming.setImageTintList(ColorStateList.valueOf(mColorForeground));
-            SignalDrawable d = new SignalDrawable(mContext);
-            d.setDarkIntensity(QuickStatusBarHeader.getColorIntensity(mColorForeground));
-            mMobileSignal.setImageDrawable(d);
-            mMobileSignal.setImageLevel(mInfo.mobileSignalIconId);
+        for (int i = 0; i < SIM_SLOTS; i++) {
+            mMobileGroups[i].setVisibility(mInfos[i].visible ? View.VISIBLE : View.GONE);
+            if (mInfos[i].visible) {
+                mMobileRoamings[i].setVisibility(mInfos[i].roaming ? View.VISIBLE : View.GONE);
+                mMobileRoamings[i].setImageTintList(ColorStateList.valueOf(mColorForeground));
+                SignalDrawable d = new SignalDrawable(mContext);
+                d.setDarkIntensity(QuickStatusBarHeader.getColorIntensity(mColorForeground));
+                mMobileSignals[i].setImageDrawable(d);
+                mMobileSignals[i].setImageLevel(mInfos[i].mobileSignalIconId);
 
-            StringBuilder contentDescription = new StringBuilder();
-            if (mInfo.contentDescription != null) {
-                contentDescription.append(mInfo.contentDescription).append(", ");
+                StringBuilder contentDescription = new StringBuilder();
+                if (mInfos[i].contentDescription != null) {
+                    contentDescription.append(mInfos[i].contentDescription).append(", ");
+                }
+                if (mInfos[i].roaming) {
+                    contentDescription
+                            .append(mContext.getString(R.string.data_connection_roaming))
+                            .append(", ");
+                }
+                // TODO: show mobile data off/no internet text for 5 seconds before carrier text
+                if (TextUtils.equals(mInfos[i].typeContentDescription,
+                        mContext.getString(R.string.data_connection_no_internet))
+                        || TextUtils.equals(mInfos[i].typeContentDescription,
+                        mContext.getString(R.string.cell_data_off_content_description))) {
+                    contentDescription.append(mInfos[i].typeContentDescription);
+                }
+                mMobileSignals[i].setContentDescription(contentDescription);
             }
-            if (mInfo.roaming) {
-                contentDescription
-                        .append(mContext.getString(R.string.data_connection_roaming))
-                        .append(", ");
+        }
+        mCarrierDivider.setVisibility(
+                mInfos[0].visible && mInfos[1].visible ? View.VISIBLE : View.GONE);
+    }
+
+    @Override
+    public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+        if (info.anySimReady) {
+            boolean[] slotSeen = new boolean[SIM_SLOTS];
+            for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
+                int slot = SubscriptionManager.getSlotIndex(info.subscriptionIds[i]);
+                mInfos[slot].visible = true;
+                slotSeen[slot] = true;
+                mCarrierTexts[slot].setText(info.listOfCarriers[i].toString().trim());
+                mCarrierGroups[slot].setVisibility(View.VISIBLE);
             }
-            // TODO: show mobile data off/no internet text for 5 seconds before carrier text
-            if (TextUtils.equals(mInfo.typeContentDescription,
-                    mContext.getString(R.string.data_connection_no_internet))
-                || TextUtils.equals(mInfo.typeContentDescription,
-                    mContext.getString(R.string.cell_data_off_content_description))) {
-                contentDescription.append(mInfo.typeContentDescription);
+            for (int i = 0; i < SIM_SLOTS; i++) {
+                if (!slotSeen[i]) {
+                    mInfos[i].visible = false;
+                    mCarrierGroups[i].setVisibility(View.GONE);
+                }
             }
-            mMobileSignal.setContentDescription(contentDescription);
+            handleUpdateState();
+        } else {
+            mInfos[0].visible = false;
+            mInfos[1].visible = false;
+            mCarrierTexts[0].setText(info.carrierText);
+            mCarrierGroups[0].setVisibility(View.VISIBLE);
+            mCarrierGroups[1].setVisibility(View.GONE);
+            handleUpdateState();
         }
     }
 
@@ -450,18 +510,23 @@
             int qsType, boolean activityIn, boolean activityOut,
             String typeContentDescription,
             String description, boolean isWide, int subId, boolean roaming) {
-        mInfo.visible = statusIcon.visible;
-        mInfo.mobileSignalIconId = statusIcon.icon;
-        mInfo.contentDescription = statusIcon.contentDescription;
-        mInfo.typeContentDescription = typeContentDescription;
-        mInfo.roaming = roaming;
+        int slotIndex = SubscriptionManager.getSlotIndex(subId);
+        if (slotIndex >= SIM_SLOTS) {
+            Log.e(TAG, "setMobileDataIndicators - slot: " + slotIndex);
+        }
+        mInfos[slotIndex].visible = statusIcon.visible;
+        mInfos[slotIndex].mobileSignalIconId = statusIcon.icon;
+        mInfos[slotIndex].contentDescription = statusIcon.contentDescription;
+        mInfos[slotIndex].typeContentDescription = typeContentDescription;
+        mInfos[slotIndex].roaming = roaming;
         handleUpdateState();
     }
 
     @Override
     public void setNoSims(boolean hasNoSims, boolean simDetected) {
         if (hasNoSims) {
-            mInfo.visible = false;
+            mInfos[0].visible = false;
+            mInfos[1].visible = false;
         }
         handleUpdateState();
     }
@@ -473,4 +538,38 @@
         String typeContentDescription;
         boolean roaming;
     }
+
+
+    /**
+     * TextView that changes its ellipsize value with its visibility.
+     */
+    public static class QSCarrierText extends TextView {
+        public QSCarrierText(Context context) {
+            super(context);
+        }
+
+        public QSCarrierText(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+        }
+
+        public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr,
+                int defStyleRes) {
+            super(context, attrs, defStyleAttr, defStyleRes);
+        }
+
+        @Override
+        protected void onVisibilityChanged(View changedView, int visibility) {
+            super.onVisibilityChanged(changedView, visibility);
+            // Only show marquee when visible
+            if (visibility == VISIBLE) {
+                setEllipsize(TextUtils.TruncateAt.MARQUEE);
+            } else {
+                setEllipsize(TextUtils.TruncateAt.END);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 75ab5df..2d64ecd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -180,14 +180,14 @@
     public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
             NextAlarmController nextAlarmController, ZenModeController zenModeController,
             BatteryController batteryController, StatusBarIconController statusBarIconController,
-            ActivityStarter activityStarter) {
+            ActivityStarter activityStarter, PrivacyItemController privacyItemController) {
         super(context, attrs);
         mAlarmController = nextAlarmController;
         mZenController = zenModeController;
         mBatteryController = batteryController;
         mStatusBarIconController = statusBarIconController;
         mActivityStarter = activityStarter;
-        mPrivacyItemController = new PrivacyItemController(context, mPICCallback);
+        mPrivacyItemController = privacyItemController;
         mShownCount = getStoredShownCount();
     }
 
@@ -512,7 +512,6 @@
             return;
         }
         mHeaderQsPanel.setListening(listening);
-        mPrivacyItemController.setListening(listening);
         mListening = listening;
 
         if (listening) {
@@ -520,9 +519,11 @@
             mAlarmController.addCallback(this);
             mContext.registerReceiver(mRingerReceiver,
                     new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
+            mPrivacyItemController.addCallback(mPICCallback);
         } else {
             mZenController.removeCallback(this);
             mAlarmController.removeCallback(this);
+            mPrivacyItemController.removeCallback(mPICCallback);
             mContext.unregisterReceiver(mRingerReceiver);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 6c3e504..04534ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -19,6 +19,7 @@
 import android.view.View;
 
 import com.android.systemui.Interpolators;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 /**
@@ -92,9 +93,15 @@
 
     private static void updateLayerType(View view, float alpha) {
         if (view.hasOverlappingRendering() && alpha > 0.0f && alpha < 1.0f) {
-            view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-        } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) {
-            view.setLayerType(View.LAYER_TYPE_NONE, null);
+            if (view.getLayerType() != View.LAYER_TYPE_HARDWARE) {
+                view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                view.setTag(R.id.cross_fade_layer_type_changed_tag, true);
+            }
+        } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE
+                && view.getTag(R.id.cross_fade_layer_type_changed_tag) != null) {
+            if (view.getTag(R.id.cross_fade_layer_type_changed_tag) != null) {
+                view.setLayerType(View.LAYER_TYPE_NONE, null);
+            }
         }
     }
 
@@ -114,7 +121,7 @@
                 .setStartDelay(delay)
                 .setInterpolator(Interpolators.ALPHA_IN)
                 .withEndAction(null);
-        if (view.hasOverlappingRendering()) {
+        if (view.hasOverlappingRendering() && view.getLayerType() != View.LAYER_TYPE_HARDWARE) {
             view.animate().withLayer();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 7b94c74..35b7ef4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -167,13 +167,10 @@
      */
     public static NotificationVisibility.NotificationLocation getNotificationLocation(
             NotificationEntry entry) {
-        ExpandableNotificationRow row = entry.getRow();
-        ExpandableViewState childViewState = row.getViewState();
-
-        if (childViewState == null) {
+        if (entry == null || entry.getRow() == null || entry.getRow().getViewState() == null) {
             return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
         }
-        return convertNotificationLocation(childViewState.location);
+        return convertNotificationLocation(entry.getRow().getViewState().location);
     }
 
     private static NotificationVisibility.NotificationLocation convertNotificationLocation(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
index db7b4fc..4bdc170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
@@ -17,8 +17,15 @@
 package com.android.systemui.statusbar.notification.row.wrapper;
 
 import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.os.Build;
 import android.view.View;
 
+import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
@@ -42,6 +49,47 @@
     }
 
     @Override
+    public void onReinflated() {
+        super.onReinflated();
+
+        Configuration configuration = mView.getResources().getConfiguration();
+        boolean nightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                == Configuration.UI_MODE_NIGHT_YES;
+
+        float[] hsl = new float[] {0f, 0f, 0f};
+        ColorUtils.colorToHSL(mBackgroundColor, hsl);
+        boolean backgroundIsDark = Color.alpha(mBackgroundColor) == 0
+                || hsl[1] == 0 && hsl[2] < 0.5;
+        boolean backgroundHasColor = hsl[1] > 0;
+
+        // Let's invert the notification colors when we're in night mode and
+        // the notification background isn't colorized.
+        if (!backgroundIsDark && !backgroundHasColor && nightMode
+                && mRow.getEntry().targetSdk < Build.VERSION_CODES.Q) {
+            Paint paint = new Paint();
+            ColorMatrix matrix = new ColorMatrix();
+            ColorMatrix tmp = new ColorMatrix();
+            // Inversion should happen on Y'UV space to conseve the colors and
+            // only affect the luminosity.
+            matrix.setRGB2YUV();
+            tmp.set(new float[]{
+                    -1f, 0f, 0f, 0f, 255f,
+                    0f, 1f, 0f, 0f, 0f,
+                    0f, 0f, 1f, 0f, 0f,
+                    0f, 0f, 0f, 1f, 0f
+            });
+            matrix.postConcat(tmp);
+            tmp.setYUV2RGB();
+            matrix.postConcat(tmp);
+            paint.setColorFilter(new ColorMatrixColorFilter(matrix));
+            mView.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
+
+            hsl[2] = 1f - hsl[2];
+            mBackgroundColor = ColorUtils.HSLToColor(hsl);
+        }
+    }
+
+    @Override
     protected boolean shouldClearBackgroundOnReapply() {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 1efdc56..9258c99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -37,7 +37,7 @@
     protected final View mView;
     protected final ExpandableNotificationRow mRow;
 
-    private int mBackgroundColor = 0;
+    protected int mBackgroundColor = 0;
 
     public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
         if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
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 03375d20..e953ad5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -113,6 +113,7 @@
      * Ratio representing being in ambient mode or not.
      */
     private float mDarkAmount;
+    private boolean mDozing;
 
     public KeyguardStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -210,7 +211,7 @@
                 mMultiUserSwitch.setVisibility(View.GONE);
             }
         }
-        mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
+        mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable || mDozing);
     }
 
     private void updateSystemIconsLayoutParams() {
@@ -347,7 +348,7 @@
         mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
         Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
         onThemeChanged();
-        updateDozeState();
+        updateDarkState();
     }
 
     @Override
@@ -506,21 +507,29 @@
         }
     }
 
+    public void setDozing(boolean dozing) {
+        if (mDozing == dozing) {
+            return;
+        }
+        mDozing = dozing;
+        updateVisibilities();
+    }
+
     public void setDarkAmount(float darkAmount) {
         mDarkAmount = darkAmount;
         if (darkAmount == 0) {
             dozeTimeTick();
         }
-        updateDozeState();
+        updateDarkState();
     }
 
     public void dozeTimeTick() {
         mCurrentBurnInOffsetX = getBurnInOffset(mBurnInOffset, true /* xAxis */);
         mCurrentBurnInOffsetY = getBurnInOffset(mBurnInOffset, false /* xAxis */);
-        updateDozeState();
+        updateDarkState();
     }
 
-    private void updateDozeState() {
+    private void updateDarkState() {
         float alpha = 1f - mDarkAmount;
         int visibility = alpha != 0f ? VISIBLE : INVISIBLE;
         mCarrierLabel.setAlpha(alpha * alpha);
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 0d5ebb9..c108371 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -140,7 +140,8 @@
 
     private KeyguardAffordanceHelper mAffordanceHelper;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
-    private KeyguardStatusBarView mKeyguardStatusBar;
+    @VisibleForTesting
+    protected KeyguardStatusBarView mKeyguardStatusBar;
     private ViewGroup mBigClockContainer;
     private QS mQs;
     private FrameLayout mQsFrame;
@@ -2792,6 +2793,7 @@
         if (mDozing) {
             mNotificationStackScroller.setShowDarkShelf(!hasCustomClock());
         }
+        mKeyguardStatusBar.setDozing(mDozing);
 
         if (mBarState == StatusBarState.KEYGUARD
                 || mBarState == StatusBarState.SHADE_LOCKED) {
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 43c35f1..18711c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -173,7 +173,7 @@
         mProvisionedController = Dependency.get(DeviceProvisionedController.class);
         mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
         mLocationController = Dependency.get(LocationController.class);
-        mPrivacyItemController = new PrivacyItemController(mContext, this);
+        mPrivacyItemController = Dependency.get(PrivacyItemController.class);
 
         mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
         mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -266,7 +266,7 @@
         mNextAlarmController.addCallback(mNextAlarmCallback);
         mDataSaver.addCallback(this);
         mKeyguardMonitor.addCallback(this);
-        mPrivacyItemController.setListening(true);
+        mPrivacyItemController.addCallback(this);
 
         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallback(this);
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
@@ -294,7 +294,7 @@
         mNextAlarmController.removeCallback(mNextAlarmCallback);
         mDataSaver.removeCallback(this);
         mKeyguardMonitor.removeCallback(this);
-        mPrivacyItemController.setListening(false);
+        mPrivacyItemController.removeCallback(this);
         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallback(this);
         mContext.unregisterReceiver(mIntentReceiver);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 60a20cf..e802757 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -61,7 +61,6 @@
     private IActivityManager mActivityManager;
     @Mock
     private DozeParameters mDozeParameters;
-    @Mock
     private FrameLayout mStatusBarView;
     @Captor
     private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
@@ -80,6 +79,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mStatusBarView = new FrameLayout(mContext);
         mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
 
         // Bubbles get added to status bar window view
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
new file mode 100644
index 0000000..1bb7ef4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 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.bubbles.animation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+import com.android.systemui.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
+
+    @Spy
+    private ExpandedAnimationController mExpandedController = new ExpandedAnimationController();
+
+    private int mStackOffset;
+    private float mBubblePadding;
+    private float mBubbleSize;
+
+    private PointF mExpansionPoint;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        addOneMoreThanRenderLimitBubbles();
+        mLayout.setController(mExpandedController);
+        Resources res = mLayout.getResources();
+        mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
+        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+    }
+
+    @Test
+    public void testExpansionAndCollapse() throws InterruptedException {
+        mExpansionPoint = new PointF(100, 100);
+        Runnable afterExpand = Mockito.mock(Runnable.class);
+        mExpandedController.expandFromStack(mExpansionPoint, afterExpand);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+        testExpanded();
+        Mockito.verify(afterExpand).run();
+
+        Runnable afterCollapse = Mockito.mock(Runnable.class);
+        mExpandedController.collapseBackToStack(afterCollapse);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+        testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1);
+        Mockito.verify(afterExpand).run();
+    }
+
+    /** Check that children are in the correct positions for being stacked. */
+    private void testStackedAtPosition(float x, float y, int offsetMultiplier) {
+        // Make sure the rest of the stack moved again, including the first bubble not moving, and
+        // is stacked to the right now that we're on the right side of the screen.
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
+            assertEquals(x + i * offsetMultiplier * mStackOffset,
+                    mViews.get(i).getTranslationX(), 2f);
+            assertEquals(y, mViews.get(i).getTranslationY(), 2f);
+        }
+    }
+
+    /** Check that children are in the correct positions for being expanded. */
+    private void testExpanded() {
+        // Make sure the rest of the stack moved again, including the first bubble not moving, and
+        // is stacked to the right now that we're on the right side of the screen.
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
+            assertEquals(mBubblePadding + (i * (mBubbleSize + mBubblePadding)),
+                    mViews.get(i).getTranslationX(),
+                    2f);
+            assertEquals(mBubblePadding + mCutoutInsetSize,
+                    mViews.get(i).getTranslationY(), 2f);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
new file mode 100644
index 0000000..bfc02d9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2019 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.bubbles.animation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+
+import android.os.SystemClock;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.google.android.collect.Sets;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+/** Tests the PhysicsAnimationLayout itself, with a basic test animation controller. */
+public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
+    static final float TEST_TRANSLATION_X_OFFSET = 15f;
+
+    @Spy
+    private TestableAnimationController mTestableController = new TestableAnimationController();
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // By default, use translation animations, chain the X animations with the default
+        // offset, and don't actually remove views immediately (since most implementations will wait
+        // to animate child views out before actually removing them).
+        mTestableController.setAnimatedProperties(Sets.newHashSet(
+                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y));
+        mTestableController.setChainedProperties(Sets.newHashSet(DynamicAnimation.TRANSLATION_X));
+        mTestableController.setOffsetForProperty(
+                DynamicAnimation.TRANSLATION_X, TEST_TRANSLATION_X_OFFSET);
+        mTestableController.setRemoveImmediately(false);
+    }
+
+    @Test
+    public void testRenderVisibility() {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+
+        // The last child should be GONE, the rest VISIBLE.
+        for (int i = 0; i < mMaxRenderedBubbles + 1; i++) {
+            assertEquals(i == mMaxRenderedBubbles ? View.GONE : View.VISIBLE,
+                    mLayout.getChildAt(i).getVisibility());
+        }
+    }
+
+    @Test
+    public void testHierarchyChanges() {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+
+        // Make sure the controller was notified of all the views we added.
+        for (View mView : mViews) {
+            Mockito.verify(mTestableController).onChildAdded(mView, 0);
+        }
+
+        // Remove some views and ensure the controller was notified, with the proper indices.
+        mTestableController.setRemoveImmediately(true);
+        mLayout.removeView(mViews.get(1));
+        mLayout.removeView(mViews.get(2));
+        Mockito.verify(mTestableController).onChildToBeRemoved(
+                eq(mViews.get(1)), eq(1), any());
+        Mockito.verify(mTestableController).onChildToBeRemoved(
+                eq(mViews.get(2)), eq(1), any());
+
+        // Make sure we still get view added notifications after doing some removals.
+        final View newBubble = new FrameLayout(mContext);
+        mLayout.addView(newBubble, 0);
+        Mockito.verify(mTestableController).onChildAdded(newBubble, 0);
+    }
+
+    @Test
+    public void testUpdateValueNotChained() throws InterruptedException {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+
+        // Don't chain any values.
+        mTestableController.setChainedProperties(Sets.newHashSet());
+
+        // Child views should not be translated.
+        assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f);
+        assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f);
+
+        // Animate the first child's translation X.
+        final CountDownLatch animLatch = new CountDownLatch(1);
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                100,
+                animLatch::countDown);
+        animLatch.await(1, TimeUnit.SECONDS);
+
+        // Ensure that the first view has been translated, but not the second one.
+        assertEquals(100, mLayout.getChildAt(0).getTranslationX(), .1f);
+        assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f);
+    }
+
+    @Test
+    public void testUpdateValueXChained() throws InterruptedException {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+        testChainedTranslationAnimations();
+    }
+
+    @Test
+    public void testSetEndListeners() throws InterruptedException {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+        mTestableController.setChainedProperties(Sets.newHashSet());
+
+        final CountDownLatch xLatch = new CountDownLatch(1);
+        OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() {
+            @Override
+            public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+                    float velocity) {
+                super.onAnimationEnd(animation, canceled, value, velocity);
+                xLatch.countDown();
+            }
+        });
+
+        final CountDownLatch yLatch = new CountDownLatch(1);
+        final OneTimeEndListener yEndListener = Mockito.spy(new OneTimeEndListener() {
+            @Override
+            public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+                    float velocity) {
+                super.onAnimationEnd(animation, canceled, value, velocity);
+                yLatch.countDown();
+            }
+        });
+
+        // Set end listeners for both x and y.
+        mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
+        mLayout.setEndListenerForProperty(yEndListener, DynamicAnimation.TRANSLATION_Y);
+
+        // Animate x, and wait for it to finish.
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                100);
+        xLatch.await();
+        yLatch.await(1, TimeUnit.SECONDS);
+
+        // Make sure the x end listener was called only one time, and the y listener was never
+        // called since we didn't animate y. Wait 1 second after the original animation end trigger
+        // to make sure it doesn't get called again.
+        Mockito.verify(xEndListener, Mockito.after(1000).times(1))
+                .onAnimationEnd(
+                        any(),
+                        eq(false),
+                        eq(100f),
+                        anyFloat());
+        Mockito.verify(yEndListener, Mockito.after(1000).never())
+                .onAnimationEnd(any(), anyBoolean(), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testRemoveEndListeners() throws InterruptedException {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+        mTestableController.setChainedProperties(Sets.newHashSet());
+
+        final CountDownLatch xLatch = new CountDownLatch(1);
+        OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() {
+            @Override
+            public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+                    float velocity) {
+                super.onAnimationEnd(animation, canceled, value, velocity);
+                xLatch.countDown();
+            }
+        });
+
+        // Set the end listener.
+        mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
+
+        // Animate x, and wait for it to finish.
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                100);
+        xLatch.await();
+
+        InOrder endListenerCalls = inOrder(xEndListener);
+        endListenerCalls.verify(xEndListener, Mockito.times(1))
+                .onAnimationEnd(
+                        any(),
+                        eq(false),
+                        eq(100f),
+                        anyFloat());
+
+        // Animate X again, remove the end listener.
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                1000);
+        mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X);
+        xLatch.await(1, TimeUnit.SECONDS);
+
+        // Make sure the end listener was not called.
+        endListenerCalls.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testPrecedingNonRemovedIndex() {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+
+        // Call removeView at index 4, but don't actually remove it yet (as if we're animating it
+        // out). The preceding, non-removed view index to 3 should initially be 4, but then 5 since
+        // 4 is on its way out.
+        assertEquals(4, mLayout.getPrecedingNonRemovedViewIndex(3));
+        mLayout.removeView(mViews.get(4));
+        assertEquals(5, mLayout.getPrecedingNonRemovedViewIndex(3));
+
+        // Call removeView at index 1, and actually remove it immediately. With the old view at 1
+        // instantly gone, the preceding view to 0 should be 1 in both cases.
+        assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0));
+        mTestableController.setRemoveImmediately(true);
+        mLayout.removeView(mViews.get(1));
+        assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0));
+    }
+
+    @Test
+    public void testSetController() throws InterruptedException {
+        // Add the bubbles, then set the controller, to make sure that a controller added to an
+        // already-initialized view works correctly.
+        addOneMoreThanRenderLimitBubbles();
+        mLayout.setController(mTestableController);
+        testChainedTranslationAnimations();
+
+        TestableAnimationController secondController =
+                Mockito.spy(new TestableAnimationController());
+        secondController.setAnimatedProperties(Sets.newHashSet(
+                DynamicAnimation.SCALE_X, DynamicAnimation.SCALE_Y));
+        secondController.setChainedProperties(Sets.newHashSet(
+                DynamicAnimation.SCALE_X));
+        secondController.setOffsetForProperty(
+                DynamicAnimation.SCALE_X, 10f);
+        secondController.setRemoveImmediately(true);
+
+        mLayout.setController(secondController);
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.SCALE_X,
+                0,
+                1.5f);
+
+        waitForPropertyAnimations(DynamicAnimation.SCALE_X);
+
+        // Make sure we never asked the original controller about any SCALE animations, that would
+        // mean the controller wasn't switched over properly.
+        Mockito.verify(mTestableController, Mockito.never())
+                .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt());
+        Mockito.verify(mTestableController, Mockito.never())
+                .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X));
+
+        // Make sure we asked the new controller about its animated properties, and configuration
+        // options.
+        Mockito.verify(secondController, Mockito.atLeastOnce())
+                .getAnimatedProperties();
+        Mockito.verify(secondController, Mockito.atLeastOnce())
+                .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt());
+        Mockito.verify(secondController, Mockito.atLeastOnce())
+                .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X));
+
+        mLayout.setController(mTestableController);
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                100f);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X);
+
+        // Make sure we never asked the second controller about the TRANSLATION_X animation.
+        Mockito.verify(secondController, Mockito.never())
+                .getNextAnimationInChain(eq(DynamicAnimation.TRANSLATION_X), anyInt());
+        Mockito.verify(secondController, Mockito.never())
+                .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.TRANSLATION_X));
+
+    }
+
+    @Test
+    public void testArePropertiesAnimating() throws InterruptedException {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+
+        assertFalse(mLayout.arePropertiesAnimating(
+                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y));
+
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                100);
+
+        // Wait for the animations to get underway.
+        SystemClock.sleep(50);
+
+        assertTrue(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_X));
+        assertFalse(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_Y));
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X);
+
+        assertFalse(mLayout.arePropertiesAnimating(
+                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y));
+    }
+
+    @Test
+    public void testCancelAllAnimations() throws InterruptedException {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                1000);
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_Y,
+                0,
+                1000);
+
+        mLayout.cancelAllAnimations();
+
+        waitForLayoutMessageQueue();
+
+        // Animations should be somewhere before their end point.
+        assertTrue(mViews.get(0).getTranslationX() < 1000);
+        assertTrue(mViews.get(0).getTranslationY() < 1000);
+    }
+
+
+    /** Standard test of chained translation animations. */
+    private void testChainedTranslationAnimations() throws InterruptedException {
+        assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f);
+        assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f);
+
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                100);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X);
+
+        // Since we enabled chaining, animating the first view to 100 should animate the second to
+        // 115 (since we set the offset to 15) and the third to 130, etc. Despite the sixth bubble
+        // not being visible, or animated, make sure that it has the appropriate chained
+        // translation.
+        for (int i = 0; i < mMaxRenderedBubbles + 1; i++) {
+            assertEquals(
+                    100 + i * TEST_TRANSLATION_X_OFFSET,
+                    mLayout.getChildAt(i).getTranslationX(), .1f);
+        }
+
+        // Ensure that the Y translations were unaffected.
+        assertEquals(0, mLayout.getChildAt(0).getTranslationY(), .1f);
+        assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f);
+
+        // Animate the first child's Y translation.
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_Y,
+                0,
+                100);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_Y);
+
+        // Ensure that only the first view's Y translation chained, since we only chained X
+        // translations.
+        assertEquals(100, mLayout.getChildAt(0).getTranslationY(), .1f);
+        assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f);
+    }
+
+    /**
+     * Animation controller with configuration methods whose return values can be set by individual
+     * tests.
+     */
+    private class TestableAnimationController
+            extends PhysicsAnimationLayout.PhysicsAnimationController {
+        private Set<DynamicAnimation.ViewProperty> mAnimatedProperties = new HashSet<>();
+        private Set<DynamicAnimation.ViewProperty> mChainedProperties = new HashSet<>();
+        private HashMap<DynamicAnimation.ViewProperty, Float> mOffsetForProperty = new HashMap<>();
+        private boolean mRemoveImmediately = false;
+
+        void setAnimatedProperties(
+                Set<DynamicAnimation.ViewProperty> animatedProperties) {
+            mAnimatedProperties = animatedProperties;
+        }
+
+        void setChainedProperties(
+                Set<DynamicAnimation.ViewProperty> chainedProperties) {
+            mChainedProperties = chainedProperties;
+        }
+
+        void setOffsetForProperty(
+                DynamicAnimation.ViewProperty property, float offset) {
+            mOffsetForProperty.put(property, offset);
+        }
+
+        public void setRemoveImmediately(boolean removeImmediately) {
+            mRemoveImmediately = removeImmediately;
+        }
+
+        @Override
+        Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
+            return mAnimatedProperties;
+        }
+
+        @Override
+        int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
+            return mChainedProperties.contains(property) ? index + 1 : NONE;
+        }
+
+        @Override
+        float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
+            return mOffsetForProperty.getOrDefault(property, 0f);
+        }
+
+        @Override
+        SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
+            return new SpringForce();
+        }
+
+        @Override
+        void onChildAdded(View child, int index) {}
+
+        @Override
+        void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) {
+            if (mRemoveImmediately) {
+                actuallyRemove.run();
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
new file mode 100644
index 0000000..186a762
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2019 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.bubbles.animation;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.DisplayCutout;
+import android.view.View;
+import android.view.WindowInsets;
+import android.widget.FrameLayout;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test case for tests that involve the {@link PhysicsAnimationLayout}. This test case constructs a
+ * testable version of the layout, and provides some helpful methods to add views to the layout and
+ * wait for physics animations to finish running.
+ *
+ * See physics-animation-testing.md.
+ */
+public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
+    TestablePhysicsAnimationLayout mLayout;
+    List<View> mViews = new ArrayList<>();
+
+    Handler mMainThreadHandler;
+
+    int mMaxRenderedBubbles;
+    int mSystemWindowInsetSize = 50;
+    int mCutoutInsetSize = 100;
+
+    int mWidth = 1000;
+    int mHeight = 1000;
+
+    @Mock
+    private WindowInsets mWindowInsets;
+
+    @Mock
+    private DisplayCutout mCutout;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mLayout = new TestablePhysicsAnimationLayout(mContext);
+        mLayout.setLeft(0);
+        mLayout.setRight(mWidth);
+        mLayout.setTop(0);
+        mLayout.setBottom(mHeight);
+
+        mMaxRenderedBubbles =
+                getContext().getResources().getInteger(R.integer.bubbles_max_rendered);
+        mMainThreadHandler = new Handler(Looper.getMainLooper());
+
+        when(mWindowInsets.getSystemWindowInsetTop()).thenReturn(mSystemWindowInsetSize);
+        when(mWindowInsets.getSystemWindowInsetBottom()).thenReturn(mSystemWindowInsetSize);
+        when(mWindowInsets.getSystemWindowInsetLeft()).thenReturn(mSystemWindowInsetSize);
+        when(mWindowInsets.getSystemWindowInsetRight()).thenReturn(mSystemWindowInsetSize);
+
+        when(mWindowInsets.getDisplayCutout()).thenReturn(mCutout);
+        when(mCutout.getSafeInsetTop()).thenReturn(mCutoutInsetSize);
+        when(mCutout.getSafeInsetBottom()).thenReturn(mCutoutInsetSize);
+        when(mCutout.getSafeInsetLeft()).thenReturn(mCutoutInsetSize);
+        when(mCutout.getSafeInsetRight()).thenReturn(mCutoutInsetSize);
+    }
+
+    /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */
+    void addOneMoreThanRenderLimitBubbles() {
+        for (int i = 0; i < mMaxRenderedBubbles + 1; i++) {
+            final View newView = new FrameLayout(mContext);
+            mLayout.addView(newView, 0);
+            mViews.add(0, newView);
+
+            newView.setTranslationX(0);
+            newView.setTranslationY(0);
+        }
+    }
+
+    /**
+     * Uses a {@link java.util.concurrent.CountDownLatch} to wait for the given properties'
+     * animations to finish before allowing the test to proceed.
+     */
+    void waitForPropertyAnimations(DynamicAnimation.ViewProperty... properties)
+            throws InterruptedException {
+        final CountDownLatch animLatch = new CountDownLatch(properties.length);
+        for (DynamicAnimation.ViewProperty property : properties) {
+            mLayout.setTestEndListenerForProperty(new OneTimeEndListener() {
+                @Override
+                public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
+                        float value,
+                        float velocity) {
+                    super.onAnimationEnd(animation, canceled, value, velocity);
+                    animLatch.countDown();
+                }
+            }, property);
+        }
+        animLatch.await(1, TimeUnit.SECONDS);
+    }
+
+    /** Uses a latch to wait for the message queue to finish. */
+    void waitForLayoutMessageQueue() throws InterruptedException {
+        // Wait for layout, then the view should be actually removed.
+        CountDownLatch layoutLatch = new CountDownLatch(1);
+        mLayout.post(layoutLatch::countDown);
+        layoutLatch.await(1, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Testable subclass of the PhysicsAnimationLayout that ensures methods that trigger animations
+     * are run on the main thread, which is a requirement of DynamicAnimation.
+     */
+    protected class TestablePhysicsAnimationLayout extends PhysicsAnimationLayout {
+        public TestablePhysicsAnimationLayout(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void setController(PhysicsAnimationController controller) {
+            mMainThreadHandler.post(() -> super.setController(controller));
+            try {
+                waitForLayoutMessageQueue();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        @Override
+        public void cancelAllAnimations() {
+            mMainThreadHandler.post(super::cancelAllAnimations);
+        }
+
+        @Override
+        protected void animateValueForChildAtIndex(DynamicAnimation.ViewProperty property,
+                int index, float value, float startVel, Runnable after) {
+            mMainThreadHandler.post(() ->
+                    super.animateValueForChildAtIndex(property, index, value, startVel, after));
+        }
+
+        @Override
+        public WindowInsets getRootWindowInsets() {
+            return mWindowInsets;
+        }
+
+        /**
+         * Sets an end listener that will be called after the 'real' end listener that was already
+         * set.
+         */
+        private void setTestEndListenerForProperty(DynamicAnimation.OnAnimationEndListener listener,
+                DynamicAnimation.ViewProperty property) {
+            final DynamicAnimation.OnAnimationEndListener realEndListener =
+                    mEndListenerForProperty.get(property);
+
+            setEndListenerForProperty((animation, canceled, value, velocity) -> {
+                if (realEndListener != null) {
+                    realEndListener.onAnimationEnd(animation, canceled, value, velocity);
+                }
+
+                listener.onAnimationEnd(animation, canceled, value, velocity);
+            }, property);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
new file mode 100644
index 0000000..0f686df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2019 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.bubbles.animation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.PointF;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.systemui.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
+
+    @Spy
+    private TestableStackController mStackController = new TestableStackController();
+
+    private int mStackOffset;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        addOneMoreThanRenderLimitBubbles();
+        mLayout.setController(mStackController);
+        mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset);
+    }
+
+    /**
+     * Test moving around the stack, and make sure the position is updated correctly, and the stack
+     * direction is correct.
+     */
+    @Test
+    public void testMoveFirstBubbleWithStackFollowing() throws InterruptedException {
+        mStackController.moveFirstBubbleWithStackFollowing(200, 100);
+
+        // The first bubble should have moved instantly, the rest should be waiting for animation.
+        assertEquals(200, mViews.get(0).getTranslationX(), .1f);
+        assertEquals(100, mViews.get(0).getTranslationY(), .1f);
+        assertEquals(0, mViews.get(1).getTranslationX(), .1f);
+        assertEquals(0, mViews.get(1).getTranslationY(), .1f);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+        // Make sure the rest of the stack got moved to the right place and is stacked to the left.
+        testStackedAtPosition(200, 100, -1);
+        assertEquals(new PointF(200, 100), mStackController.getStackPosition());
+
+        mStackController.moveFirstBubbleWithStackFollowing(1000, 500);
+
+        // The first bubble again should have moved instantly while the rest remained where they
+        // were until the animation takes over.
+        assertEquals(1000, mViews.get(0).getTranslationX(), .1f);
+        assertEquals(500, mViews.get(0).getTranslationY(), .1f);
+        assertEquals(200 + -mStackOffset, mViews.get(1).getTranslationX(), .1f);
+        assertEquals(100, mViews.get(1).getTranslationY(), .1f);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+        // Make sure the rest of the stack moved again, including the first bubble not moving, and
+        // is stacked to the right now that we're on the right side of the screen.
+        testStackedAtPosition(1000, 500, 1);
+        assertEquals(new PointF(1000, 500), mStackController.getStackPosition());
+    }
+
+    @Test
+    public void testFlingSideways() throws InterruptedException {
+        // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much
+        // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top
+        // but should bounce back down.
+        mStackController.flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_X,
+                5000f, 1.15f, new SpringForce(), mWidth * 1f);
+        mStackController.flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_Y,
+                0f, 1.15f, new SpringForce(), 0f);
+
+        // Nothing should move initially since the animations haven't begun, including the first
+        // view.
+        assertEquals(0f, mViews.get(0).getTranslationX(), 1f);
+        assertEquals(0f, mViews.get(0).getTranslationY(), 1f);
+
+        // Wait for the flinging.
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
+                DynamicAnimation.TRANSLATION_Y);
+
+        // Wait for the springing.
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
+                DynamicAnimation.TRANSLATION_Y);
+
+        // Once the dust has settled, we should have flung all the way to the right side, with the
+        // stack stacked off to the right now.
+        testStackedAtPosition(mWidth * 1f, 0f, 1);
+    }
+
+    @Test
+    public void testFlingUpFromBelowBottomCenter() throws InterruptedException {
+        // Move to the center of the screen, just past the bottom.
+        mStackController.moveFirstBubbleWithStackFollowing(mWidth / 2f, mHeight + 100);
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+        // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much
+        // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top
+        // but should bounce back down.
+        mStackController.flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_X,
+                0, 1.15f, new SpringForce(), 27f);
+        mStackController.flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_Y,
+                5000f, 1.15f, new SpringForce(), 27f);
+
+        // Nothing should move initially since the animations haven't begun.
+        assertEquals(mWidth / 2f, mViews.get(0).getTranslationX(), .1f);
+        assertEquals(mHeight + 100, mViews.get(0).getTranslationY(), .1f);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
+                DynamicAnimation.TRANSLATION_Y);
+
+        // Once the dust has settled, we should have flung a bit but then sprung to the final
+        // destination which is (27, 27).
+        testStackedAtPosition(27, 27, -1);
+    }
+
+    @Test
+    public void testChildAdded() throws InterruptedException {
+        // Move the stack to y = 500.
+        mStackController.moveFirstBubbleWithStackFollowing(0f, 500f);
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
+                DynamicAnimation.TRANSLATION_Y);
+
+        final View newView = new FrameLayout(mContext);
+        mLayout.addView(
+                newView,
+                0,
+                new FrameLayout.LayoutParams(50, 50));
+
+        waitForPropertyAnimations(
+                DynamicAnimation.TRANSLATION_X,
+                DynamicAnimation.TRANSLATION_Y,
+                DynamicAnimation.SCALE_X,
+                DynamicAnimation.SCALE_Y);
+
+        // The new view should be at the top of the stack, in the correct position.
+        assertEquals(0f, newView.getTranslationX(), .1f);
+        assertEquals(500f, newView.getTranslationY(), .1f);
+        assertEquals(1f, newView.getScaleX(), .1f);
+        assertEquals(1f, newView.getScaleY(), .1f);
+        assertEquals(1f, newView.getAlpha(), .1f);
+    }
+
+    @Test
+    public void testChildRemoved() throws InterruptedException {
+        final View firstView = mLayout.getChildAt(0);
+        mLayout.removeView(firstView);
+
+        // The view should still be there, since the controller is animating it out and hasn't yet
+        // actually removed it from the parent view.
+        assertEquals(0, mLayout.indexOfChild(firstView));
+
+        waitForPropertyAnimations(DynamicAnimation.ALPHA);
+        waitForLayoutMessageQueue();
+
+        assertEquals(-1, mLayout.indexOfChild(firstView));
+
+        // The subsequent view should have been translated over to 0, not stacked off to the left.
+        assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f);
+    }
+
+    /**
+     * Checks every child view to make sure it's stacked at the given coordinates, off to the left
+     * or right side depending on offset multiplier.
+     */
+    private void testStackedAtPosition(float x, float y, int offsetMultiplier) {
+        // Make sure the rest of the stack moved again, including the first bubble not moving, and
+        // is stacked to the right now that we're on the right side of the screen.
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
+            assertEquals(x + i * offsetMultiplier * mStackOffset,
+                    mViews.get(i).getTranslationX(), 2f);
+            assertEquals(y, mViews.get(i).getTranslationY(), 2f);
+        }
+    }
+
+    /**
+     * Testable version of the stack controller that dispatches its animations on the main thread.
+     */
+    private class TestableStackController extends StackAnimationController {
+        @Override
+        public void flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.ViewProperty property, float vel, float friction,
+                SpringForce spring, Float finalPosition) {
+            mMainThreadHandler.post(() ->
+                    super.flingThenSpringFirstBubbleWithStackFollowing(
+                            property, vel, friction, spring, finalPosition));
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index e6d7ee7..98bf3c27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -45,9 +45,12 @@
 import org.mockito.Mock
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -73,6 +76,8 @@
     private lateinit var userManager: UserManager
     @Captor
     private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>>
+    @Captor
+    private lateinit var argCaptorCallback: ArgumentCaptor<AppOpsController.Callback>
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var privacyItemController: PrivacyItemController
@@ -95,7 +100,16 @@
             }
         })).`when`(userManager).getProfiles(anyInt())
 
-        privacyItemController = PrivacyItemController(mContext, callback)
+        privacyItemController = PrivacyItemController(mContext)
+    }
+
+    @Test
+    fun testSetListeningTrueByAddingCallback() {
+        privacyItemController.addCallback(callback)
+        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS),
+                any(AppOpsController.Callback::class.java))
+        testableLooper.processAllMessages()
+        verify(callback).privacyChanged(anyList())
     }
 
     @Test
@@ -103,8 +117,6 @@
         privacyItemController.setListening(true)
         verify(appOpsController).addCallback(eq(PrivacyItemController.OPS),
                 any(AppOpsController.Callback::class.java))
-        testableLooper.processAllMessages()
-        verify(callback).privacyChanged(anyList())
     }
 
     @Test
@@ -121,7 +133,7 @@
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1)))
                 .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
 
-        privacyItemController.setListening(true)
+        privacyItemController.addCallback(callback)
         testableLooper.processAllMessages()
         verify(callback).privacyChanged(capture(argCaptor))
         assertEquals(1, argCaptor.value.size)
@@ -131,7 +143,7 @@
     fun testSystemApps() {
         doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, SYSTEM_UID, TEST_PACKAGE_NAME,
                 0))).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
-        privacyItemController.setListening(true)
+        privacyItemController.addCallback(callback)
         testableLooper.processAllMessages()
         verify(callback).privacyChanged(capture(argCaptor))
         assertEquals(1, argCaptor.value.size)
@@ -142,8 +154,8 @@
     @Test
     fun testRegisterReceiver_allUsers() {
         val spiedContext = spy(mContext)
-        val itemController = PrivacyItemController(spiedContext, callback)
-
+        val itemController = PrivacyItemController(spiedContext)
+        itemController.setListening(true)
         verify(spiedContext, atLeastOnce()).registerReceiverAsUser(
                 eq(itemController.userSwitcherReceiver), eq(UserHandle.ALL), any(), eq(null),
                 eq(null))
@@ -170,4 +182,54 @@
                 Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED))
         verify(userManager).getProfiles(anyInt())
     }
+
+    @Test
+    fun testAddMultipleCallbacks() {
+        val otherCallback = mock(PrivacyItemController.Callback::class.java)
+        privacyItemController.addCallback(callback)
+        testableLooper.processAllMessages()
+        verify(callback).privacyChanged(anyList())
+
+        privacyItemController.addCallback(otherCallback)
+        testableLooper.processAllMessages()
+        verify(otherCallback).privacyChanged(anyList())
+        // Adding a callback should not unnecessarily call previous ones
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun testMultipleCallbacksAreUpdated() {
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+
+        val otherCallback = mock(PrivacyItemController.Callback::class.java)
+        privacyItemController.addCallback(callback)
+        privacyItemController.addCallback(otherCallback)
+        testableLooper.processAllMessages()
+        reset(callback)
+        reset(otherCallback)
+
+        verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback))
+        argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
+        testableLooper.processAllMessages()
+        verify(callback).privacyChanged(anyList())
+        verify(otherCallback).privacyChanged(anyList())
+    }
+
+    @Test
+    fun testRemoveCallback() {
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        val otherCallback = mock(PrivacyItemController.Callback::class.java)
+        privacyItemController.addCallback(callback)
+        privacyItemController.addCallback(otherCallback)
+        testableLooper.processAllMessages()
+        reset(callback)
+        reset(otherCallback)
+
+        verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback))
+        privacyItemController.removeCallback(callback)
+        argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
+        testableLooper.processAllMessages()
+        verify(callback, never()).privacyChanged(anyList())
+        verify(otherCallback).privacyChanged(anyList())
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index b7b95ef..3b98f0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -51,6 +51,8 @@
     private NotificationStackScrollLayout mNotificationStackScrollLayout;
     @Mock
     private KeyguardStatusView mKeyguardStatusView;
+    @Mock
+    private KeyguardStatusBarView mKeyguardStatusBar;
     private NotificationPanelView mNotificationPanelView;
 
     @Before
@@ -93,6 +95,7 @@
             super(NotificationPanelViewTest.this.mContext, null);
             mNotificationStackScroller = mNotificationStackScrollLayout;
             mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView;
+            mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar;
         }
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index bcff4e0..303230b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -402,7 +402,7 @@
                 MagnificationGestureHandler magnificationGestureHandler =
                         new MagnificationGestureHandler(mContext,
                                 mAms.getMagnificationController(),
-                                detectControlGestures, triggerable);
+                                detectControlGestures, triggerable, displayId);
                 addFirstEventHandler(displayId, magnificationGestureHandler);
                 mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
             }
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 49db488..2fbaee6 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -43,7 +43,6 @@
 import android.util.MathUtils;
 import android.util.Slog;
 import android.util.TypedValue;
-import android.view.Display;
 import android.view.GestureDetector;
 import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.MotionEvent;
@@ -149,6 +148,8 @@
     private PointerCoords[] mTempPointerCoords;
     private PointerProperties[] mTempPointerProperties;
 
+    private final int mDisplayId;
+
     private final Queue<MotionEvent> mDebugInputEventHistory;
     private final Queue<MotionEvent> mDebugOutputEventHistory;
 
@@ -162,11 +163,13 @@
      * @param detectShortcutTrigger {@code true} if this detector should be "triggerable" by some
      *                           external shortcut invoking {@link #notifyShortcutTriggered},
      *                           {@code false} if it should ignore such triggers.
+     * @param displayId The logical display id.
      */
     public MagnificationGestureHandler(Context context,
             MagnificationController magnificationController,
             boolean detectTripleTap,
-            boolean detectShortcutTrigger) {
+            boolean detectShortcutTrigger,
+            int displayId) {
         if (DEBUG_ALL) {
             Log.i(LOG_TAG,
                     "MagnificationGestureHandler(detectTripleTap = " + detectTripleTap
@@ -174,6 +177,7 @@
         }
 
         mMagnificationController = magnificationController;
+        mDisplayId = displayId;
 
         mDelegatingState = new DelegatingState();
         mDetectingState = new DetectingState(context);
@@ -259,8 +263,7 @@
 
     void notifyShortcutTriggered() {
         if (mDetectShortcutTrigger) {
-            // TODO: multi-display support for magnification gesture handler
-            boolean wasMagnifying = mMagnificationController.resetIfNeeded(Display.DEFAULT_DISPLAY,
+            boolean wasMagnifying = mMagnificationController.resetIfNeeded(mDisplayId,
                     /* animate */ true);
             if (wasMagnifying) {
                 clearAndTransitionToStateDetecting();
@@ -422,8 +425,7 @@
                 Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
                         + " scrollY: " + distanceY);
             }
-            // TODO: multi-display support for magnification gesture handler
-            mMagnificationController.offsetMagnifiedRegion(Display.DEFAULT_DISPLAY, distanceX,
+            mMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
                     distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
             return /* event consumed: */ true;
         }
@@ -440,8 +442,7 @@
                 return mScaling;
             }
 
-            // TODO: multi-display support for magnification gesture handler
-            final float initialScale = mMagnificationController.getScale(Display.DEFAULT_DISPLAY);
+            final float initialScale = mMagnificationController.getScale(mDisplayId);
             final float targetScale = initialScale * detector.getScaleFactor();
 
             // Don't allow a gesture to move the user further outside the
@@ -463,8 +464,7 @@
             final float pivotX = detector.getFocusX();
             final float pivotY = detector.getFocusY();
             if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x");
-            // TODO: multi-display support for magnification gesture handler
-            mMagnificationController.setScale(Display.DEFAULT_DISPLAY, scale, pivotX, pivotY, false,
+            mMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
                     AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
             return /* handled: */ true;
         }
@@ -524,10 +524,9 @@
                     }
                     final float eventX = event.getX();
                     final float eventY = event.getY();
-                    // TODO: multi-display support for magnification gesture handler
                     if (mMagnificationController.magnificationRegionContains(
-                            Display.DEFAULT_DISPLAY, eventX, eventY)) {
-                        mMagnificationController.setCenter(Display.DEFAULT_DISPLAY, eventX, eventY,
+                            mDisplayId, eventX, eventY)) {
+                        mMagnificationController.setCenter(mDisplayId, eventX, eventY,
                                 /* animate */ mLastMoveOutsideMagnifiedRegion,
                                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
                         mLastMoveOutsideMagnifiedRegion = false;
@@ -665,9 +664,8 @@
 
                     mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
 
-                    // TODO: multi-display support for magnification gesture handler
                     if (!mMagnificationController.magnificationRegionContains(
-                            Display.DEFAULT_DISPLAY, event.getX(), event.getY())) {
+                            mDisplayId, event.getX(), event.getY())) {
 
                         transitionToDelegatingStateAndClear();
 
@@ -684,8 +682,7 @@
                             // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
                             // to ensure reachability of
                             // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
-                            // TODO: multi-display support for magnification gesture handler
-                            || mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
+                            || mMagnificationController.isMagnifying(mDisplayId)) {
 
                         afterMultiTapTimeoutTransitionToDelegatingState();
 
@@ -697,8 +694,7 @@
                 }
                 break;
                 case ACTION_POINTER_DOWN: {
-                    // TODO: multi-display support for magnification gesture handler
-                    if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
+                    if (mMagnificationController.isMagnifying(mDisplayId)) {
                         transitionTo(mPanningScalingState);
                         clear();
                     } else {
@@ -727,9 +723,8 @@
 
                     mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
 
-                    // TODO: multi-display support for magnification gesture handler
                     if (!mMagnificationController.magnificationRegionContains(
-                            Display.DEFAULT_DISPLAY, event.getX(), event.getY())) {
+                            mDisplayId, event.getX(), event.getY())) {
 
                         transitionToDelegatingStateAndClear();
 
@@ -880,8 +875,7 @@
             clear();
 
             // Toggle zoom
-            // TODO: multi-display support for magnification gesture handler
-            if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
+            if (mMagnificationController.isMagnifying(mDisplayId)) {
                 zoomOff();
             } else {
                 zoomOn(up.getX(), up.getY());
@@ -893,9 +887,8 @@
             if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
             clear();
 
-            // TODO: multi-display support for magnification gesture handler
             mViewportDraggingState.mZoomedInBeforeDrag =
-                    mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY);
+                    mMagnificationController.isMagnifying(mDisplayId);
 
             zoomOn(down.getX(), down.getY());
 
@@ -922,8 +915,7 @@
             if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")");
 
             mShortcutTriggered = state;
-            // TODO: multi-display support for magnification gesture handler
-            mMagnificationController.setForceShowMagnifiableBounds(Display.DEFAULT_DISPLAY, state);
+            mMagnificationController.setForceShowMagnifiableBounds(mDisplayId, state);
         }
 
         /**
@@ -958,8 +950,7 @@
         final float scale = MathUtils.constrain(
                 mMagnificationController.getPersistedScale(),
                 MIN_SCALE, MAX_SCALE);
-        // TODO: multi-display support for magnification gesture handler
-        mMagnificationController.setScaleAndCenter(Display.DEFAULT_DISPLAY,
+        mMagnificationController.setScaleAndCenter(mDisplayId,
                 scale, centerX, centerY,
                 /* animate */ true,
                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -967,8 +958,7 @@
 
     private void zoomOff() {
         if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()");
-        // TODO: multi-display support for magnification gesture handler
-        mMagnificationController.reset(Display.DEFAULT_DISPLAY, /* animate */ true);
+        mMagnificationController.reset(mDisplayId, /* animate */ true);
     }
 
     private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) {
@@ -990,6 +980,7 @@
                 ", mCurrentState=" + State.nameOf(mCurrentState) +
                 ", mPreviousState=" + State.nameOf(mPreviousState) +
                 ", mMagnificationController=" + mMagnificationController +
+                ", mDisplayId=" + mDisplayId +
                 '}';
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index 8ffadde..65e31f3 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -435,7 +435,7 @@
         MotionEvent click_event = MotionEvent.obtain(event.getDownTime(),
                 event.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
                 coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0,
-                event.getSource(), event.getFlags());
+                event.getSource(), event.getDisplayId(), event.getFlags());
         final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS);
         sendActionDownAndUp(click_event, policyFlags, targetAccessibilityFocus);
         click_event.recycle();
@@ -1029,7 +1029,7 @@
                 event.getEventTime(), event.getAction(), event.getPointerCount(),
                 props, coords, event.getMetaState(), event.getButtonState(),
                 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
-                event.getSource(), event.getFlags());
+                event.getSource(), event.getDisplayId(), event.getFlags());
     }
 
     /**
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1519c17..14e2354 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1884,12 +1884,6 @@
                 "ConnectivityService");
     }
 
-    private void enforceControlAlwaysOnVpnPermission() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
-                "ConnectivityService");
-    }
-
     private void enforceNetworkStackSettingsOrSetup() {
         enforceAnyPermissionOf(
             android.Manifest.permission.NETWORK_SETTINGS,
@@ -1897,12 +1891,6 @@
             android.Manifest.permission.NETWORK_STACK);
     }
 
-    private void enforceNetworkStackPermission() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.NETWORK_STACK,
-                "ConnectivityService");
-    }
-
     private boolean checkNetworkStackPermission() {
         return PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.NETWORK_STACK);
@@ -4159,9 +4147,8 @@
     }
 
     @Override
-    public boolean setAlwaysOnVpnPackage(
-            int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) {
-        enforceControlAlwaysOnVpnPermission();
+    public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown) {
+        enforceConnectivityInternalPermission();
         enforceCrossUserPermission(userId);
 
         synchronized (mVpns) {
@@ -4175,11 +4162,11 @@
                 Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                 return false;
             }
-            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist)) {
+            if (!vpn.setAlwaysOnPackage(packageName, lockdown)) {
                 return false;
             }
             if (!startAlwaysOnVpn(userId)) {
-                vpn.setAlwaysOnPackage(null, false, null);
+                vpn.setAlwaysOnPackage(null, false);
                 return false;
             }
         }
@@ -4188,7 +4175,7 @@
 
     @Override
     public String getAlwaysOnVpnPackage(int userId) {
-        enforceControlAlwaysOnVpnPermission();
+        enforceConnectivityInternalPermission();
         enforceCrossUserPermission(userId);
 
         synchronized (mVpns) {
@@ -4202,36 +4189,6 @@
     }
 
     @Override
-    public boolean isVpnLockdownEnabled(int userId) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                Slog.w(TAG, "User " + userId + " has no Vpn configuration");
-                return false;
-            }
-            return vpn.getLockdown();
-        }
-    }
-
-    @Override
-    public List<String> getVpnLockdownWhitelist(int userId) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                Slog.w(TAG, "User " + userId + " has no Vpn configuration");
-                return null;
-            }
-            return vpn.getLockdownWhitelist();
-        }
-    }
-
-    @Override
     public int checkMobileProvisioning(int suggestedTimeOutMs) {
         // TODO: Remove?  Any reason to trigger a provisioning check?
         return -1;
@@ -4460,7 +4417,7 @@
             if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
                 Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user "
                         + userId);
-                vpn.setAlwaysOnPackage(null, false, null);
+                vpn.setAlwaysOnPackage(null, false);
             }
         }
     }
@@ -6340,7 +6297,7 @@
             synchronized (mVpns) {
                 final String alwaysOnPackage = getAlwaysOnVpnPackage(userId);
                 if (alwaysOnPackage != null) {
-                    setAlwaysOnVpnPackage(userId, null, false, null);
+                    setAlwaysOnVpnPackage(userId, null, false);
                     setVpnPackageAuthorization(alwaysOnPackage, userId, false);
                 }
 
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 89ff338..c064453 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -46,9 +46,7 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
-import android.net.InetAddresses;
 import android.net.INetd;
-import android.net.INetdUnsolicitedEventListener;
 import android.net.INetworkManagementEventObserver;
 import android.net.ITetheringStatsProvider;
 import android.net.InterfaceConfiguration;
@@ -63,6 +61,7 @@
 import android.net.TetherStatsParcel;
 import android.net.UidRange;
 import android.net.shared.NetdService;
+import android.net.shared.NetworkObserverRegistry;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Handler;
@@ -207,16 +206,13 @@
 
     private INetd mNetdService;
 
-    private final NetdUnsolicitedEventListener mNetdUnsolicitedEventListener;
+    private NMSNetworkObserverRegistry mNetworkObserverRegistry;
 
     private IBatteryStats mBatteryStats;
 
     private final Thread mThread;
     private CountDownLatch mConnectedSignal = new CountDownLatch(1);
 
-    private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
-            new RemoteCallbackList<>();
-
     private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
 
     @GuardedBy("mTetheringStatsProviders")
@@ -326,8 +322,6 @@
 
         mDaemonHandler = new Handler(FgThread.get().getLooper());
 
-        mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener();
-
         // Add ourself to the Watchdog monitors.
         Watchdog.getInstance().addMonitor(this);
 
@@ -346,7 +340,7 @@
         mFgHandler = null;
         mThread = null;
         mServices = null;
-        mNetdUnsolicitedEventListener = null;
+        mNetworkObserverRegistry = null;
     }
 
     static NetworkManagementService create(Context context, String socket, SystemServices services)
@@ -394,14 +388,12 @@
 
     @Override
     public void registerObserver(INetworkManagementEventObserver observer) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        mObservers.register(observer);
+        mNetworkObserverRegistry.registerObserver(observer);
     }
 
     @Override
     public void unregisterObserver(INetworkManagementEventObserver observer) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        mObservers.unregister(observer);
+        mNetworkObserverRegistry.unregisterObserver(observer);
     }
 
     @FunctionalInterface
@@ -409,127 +401,101 @@
         public void sendCallback(INetworkManagementEventObserver o) throws RemoteException;
     }
 
-    private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) {
-        final int length = mObservers.beginBroadcast();
-        try {
-            for (int i = 0; i < length; i++) {
-                try {
-                    eventCallback.sendCallback(mObservers.getBroadcastItem(i));
-                } catch (RemoteException | RuntimeException e) {
-                }
-            }
-        } finally {
-            mObservers.finishBroadcast();
+    private class NMSNetworkObserverRegistry extends NetworkObserverRegistry {
+        NMSNetworkObserverRegistry(Context context, Handler handler, INetd netd)
+                throws RemoteException {
+            super(context, handler, netd);
         }
-    }
 
-    /**
-     * Notify our observers of an interface status change
-     */
-    private void notifyInterfaceStatusChanged(String iface, boolean up) {
-        invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up));
-    }
+        /**
+         * Notify our observers of a change in the data activity state of the interface
+         */
+        @Override
+        public void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos,
+                int uid, boolean fromRadio) {
+            final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type);
+            int powerState = isActive
+                    ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+                    : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
 
-    /**
-     * Notify our observers of an interface link state change
-     * (typically, an Ethernet cable has been plugged-in or unplugged).
-     */
-    private void notifyInterfaceLinkStateChanged(String iface, boolean up) {
-        invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up));
-    }
-
-    /**
-     * Notify our observers of an interface addition.
-     */
-    private void notifyInterfaceAdded(String iface) {
-        invokeForAllObservers(o -> o.interfaceAdded(iface));
-    }
-
-    /**
-     * Notify our observers of an interface removal.
-     */
-    private void notifyInterfaceRemoved(String iface) {
-        // netd already clears out quota and alerts for removed ifaces; update
-        // our sanity-checking state.
-        mActiveAlerts.remove(iface);
-        mActiveQuotas.remove(iface);
-        invokeForAllObservers(o -> o.interfaceRemoved(iface));
-    }
-
-    /**
-     * Notify our observers of a limit reached.
-     */
-    private void notifyLimitReached(String limitName, String iface) {
-        invokeForAllObservers(o -> o.limitReached(limitName, iface));
-    }
-
-    /**
-     * Notify our observers of a change in the data activity state of the interface
-     */
-    private void notifyInterfaceClassActivity(int type, int powerState, long tsNanos,
-            int uid, boolean fromRadio) {
-        final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type);
-        if (isMobile) {
-            if (!fromRadio) {
-                if (mMobileActivityFromRadio) {
-                    // If this call is not coming from a report from the radio itself, but we
-                    // have previously received reports from the radio, then we will take the
-                    // power state to just be whatever the radio last reported.
-                    powerState = mLastPowerStateFromRadio;
+            if (isMobile) {
+                if (!fromRadio) {
+                    if (mMobileActivityFromRadio) {
+                        // If this call is not coming from a report from the radio itself, but we
+                        // have previously received reports from the radio, then we will take the
+                        // power state to just be whatever the radio last reported.
+                        powerState = mLastPowerStateFromRadio;
+                    }
+                } else {
+                    mMobileActivityFromRadio = true;
                 }
-            } else {
-                mMobileActivityFromRadio = true;
-            }
-            if (mLastPowerStateFromRadio != powerState) {
-                mLastPowerStateFromRadio = powerState;
-                try {
-                    getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid);
-                } catch (RemoteException e) {
+                if (mLastPowerStateFromRadio != powerState) {
+                    mLastPowerStateFromRadio = powerState;
+                    try {
+                        getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid);
+                    } catch (RemoteException e) {
+                    }
                 }
                 StatsLog.write_non_chained(StatsLog.MOBILE_RADIO_POWER_STATE_CHANGED, uid, null,
                         powerState);
             }
-        }
 
-        if (ConnectivityManager.isNetworkTypeWifi(type)) {
-            if (mLastPowerStateFromWifi != powerState) {
-                mLastPowerStateFromWifi = powerState;
-                try {
-                    getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid);
-                } catch (RemoteException e) {
+            if (ConnectivityManager.isNetworkTypeWifi(type)) {
+                if (mLastPowerStateFromWifi != powerState) {
+                    mLastPowerStateFromWifi = powerState;
+                    try {
+                        getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid);
+                    } catch (RemoteException e) {
+                    }
                 }
                 StatsLog.write_non_chained(StatsLog.WIFI_RADIO_POWER_STATE_CHANGED, uid, null,
                         powerState);
             }
-        }
 
-        boolean isActive = powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
-                || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
-
-        if (!isMobile || fromRadio || !mMobileActivityFromRadio) {
-            // Report the change in data activity.  We don't do this if this is a change
-            // on the mobile network, that is not coming from the radio itself, and we
-            // have previously seen change reports from the radio.  In that case only
-            // the radio is the authority for the current state.
-            final boolean active = isActive;
-            invokeForAllObservers(o -> o.interfaceClassDataActivityChanged(
-                    Integer.toString(type), active, tsNanos));
-        }
-
-        boolean report = false;
-        synchronized (mIdleTimerLock) {
-            if (mActiveIdleTimers.isEmpty()) {
-                // If there are no idle timers, we are not monitoring activity, so we
-                // are always considered active.
-                isActive = true;
+            if (!isMobile || fromRadio || !mMobileActivityFromRadio) {
+                // Report the change in data activity.  We don't do this if this is a change
+                // on the mobile network, that is not coming from the radio itself, and we
+                // have previously seen change reports from the radio.  In that case only
+                // the radio is the authority for the current state.
+                final boolean active = isActive;
+                super.notifyInterfaceClassActivity(type, isActive, tsNanos, uid, fromRadio);
             }
-            if (mNetworkActive != isActive) {
-                mNetworkActive = isActive;
-                report = isActive;
+
+            boolean report = false;
+            synchronized (mIdleTimerLock) {
+                if (mActiveIdleTimers.isEmpty()) {
+                    // If there are no idle timers, we are not monitoring activity, so we
+                    // are always considered active.
+                    isActive = true;
+                }
+                if (mNetworkActive != isActive) {
+                    mNetworkActive = isActive;
+                    report = isActive;
+                }
+            }
+            if (report) {
+                reportNetworkActive();
             }
         }
-        if (report) {
-            reportNetworkActive();
+
+        /**
+         * Notify our observers of an interface removal.
+         */
+        @Override
+        public void notifyInterfaceRemoved(String iface) {
+            // netd already clears out quota and alerts for removed ifaces; update
+            // our sanity-checking state.
+            mActiveAlerts.remove(iface);
+            mActiveQuotas.remove(iface);
+            super.notifyInterfaceRemoved(iface);
+        }
+
+        @Override
+        public void onStrictCleartextDetected(int uid, String hex) throws RemoteException {
+            // Don't need to post to mDaemonHandler because the only thing
+            // that notifyCleartextNetwork does is post to a handler
+            ActivityManager.getService().notifyCleartextNetwork(uid,
+                    HexDump.hexStringToByteArray(hex));
         }
     }
 
@@ -558,7 +524,8 @@
                 return;
             }
             // No current code examines the interface parameter in a global alert. Just pass null.
-            mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null));
+            mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyLimitReached(
+                    LIMIT_GLOBAL_ALERT, null));
         }
     }
 
@@ -590,10 +557,11 @@
     private void connectNativeNetdService() {
         mNetdService = mServices.getNetd();
         try {
-            mNetdService.registerUnsolicitedEventListener(mNetdUnsolicitedEventListener);
-            if (DBG) Slog.d(TAG, "Register unsolicited event listener");
+            mNetworkObserverRegistry = new NMSNetworkObserverRegistry(
+                    mContext, mDaemonHandler, mNetdService);
+            if (DBG) Slog.d(TAG, "Registered NetworkObserverRegistry");
         } catch (RemoteException | ServiceSpecificException e) {
-            Slog.e(TAG, "Failed to set Netd unsolicited event listener " + e);
+            Slog.wtf(TAG, "Failed to register NetworkObserverRegistry: " + e);
         }
     }
 
@@ -697,120 +665,6 @@
 
     }
 
-    /**
-     * Notify our observers of a new or updated interface address.
-     */
-    private void notifyAddressUpdated(String iface, LinkAddress address) {
-        invokeForAllObservers(o -> o.addressUpdated(iface, address));
-    }
-
-    /**
-     * Notify our observers of a deleted interface address.
-     */
-    private void notifyAddressRemoved(String iface, LinkAddress address) {
-        invokeForAllObservers(o -> o.addressRemoved(iface, address));
-    }
-
-    /**
-     * Notify our observers of DNS server information received.
-     */
-    private void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
-        invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses));
-    }
-
-    /**
-     * Notify our observers of a route change.
-     */
-    private void notifyRouteChange(boolean updated, RouteInfo route) {
-        if (updated) {
-            invokeForAllObservers(o -> o.routeUpdated(route));
-        } else {
-            invokeForAllObservers(o -> o.routeRemoved(route));
-        }
-    }
-
-    private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub {
-        @Override
-        public void onInterfaceClassActivityChanged(boolean isActive,
-                int label, long timestamp, int uid) throws RemoteException {
-            final long timestampNanos;
-            if (timestamp <= 0) {
-                timestampNanos = SystemClock.elapsedRealtimeNanos();
-            } else {
-                timestampNanos = timestamp;
-            }
-            mDaemonHandler.post(() -> notifyInterfaceClassActivity(label,
-                    isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
-                    : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
-                    timestampNanos, uid, false));
-        }
-
-        @Override
-        public void onQuotaLimitReached(String alertName, String ifName)
-                throws RemoteException {
-            mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName));
-        }
-
-        @Override
-        public void onInterfaceDnsServerInfo(String ifName,
-                long lifetime, String[] servers) throws RemoteException {
-            mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers));
-        }
-
-        @Override
-        public void onInterfaceAddressUpdated(String addr,
-                String ifName, int flags, int scope) throws RemoteException {
-            final LinkAddress address = new LinkAddress(addr, flags, scope);
-            mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address));
-        }
-
-        @Override
-        public void onInterfaceAddressRemoved(String addr,
-                String ifName, int flags, int scope) throws RemoteException {
-            final LinkAddress address = new LinkAddress(addr, flags, scope);
-            mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address));
-        }
-
-        @Override
-        public void onInterfaceAdded(String ifName) throws RemoteException {
-            mDaemonHandler.post(() -> notifyInterfaceAdded(ifName));
-        }
-
-        @Override
-        public void onInterfaceRemoved(String ifName) throws RemoteException {
-            mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName));
-        }
-
-        @Override
-        public void onInterfaceChanged(String ifName, boolean up)
-                throws RemoteException {
-            mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up));
-        }
-
-        @Override
-        public void onInterfaceLinkStateChanged(String ifName, boolean up)
-                throws RemoteException {
-            mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up));
-        }
-
-        @Override
-        public void onRouteChanged(boolean updated,
-                String route, String gateway, String ifName) throws RemoteException {
-            final RouteInfo processRoute = new RouteInfo(new IpPrefix(route),
-                    ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway),
-                    ifName);
-            mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute));
-        }
-
-        @Override
-        public void onStrictCleartextDetected(int uid, String hex) throws RemoteException {
-            // Don't need to post to mDaemonHandler because the only thing
-            // that notifyCleartextNetwork does is post to a handler
-            ActivityManager.getService().notifyCleartextNetwork(uid,
-                    HexDump.hexStringToByteArray(hex));
-        }
-    }
-
     //
     // Netd Callback handling
     //
@@ -859,16 +713,18 @@
                         throw new IllegalStateException(errorMessage);
                     }
                     if (cooked[2].equals("added")) {
-                        notifyInterfaceAdded(cooked[3]);
+                        mNetworkObserverRegistry.notifyInterfaceAdded(cooked[3]);
                         return true;
                     } else if (cooked[2].equals("removed")) {
-                        notifyInterfaceRemoved(cooked[3]);
+                        mNetworkObserverRegistry.notifyInterfaceRemoved(cooked[3]);
                         return true;
                     } else if (cooked[2].equals("changed") && cooked.length == 5) {
-                        notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up"));
+                        mNetworkObserverRegistry.notifyInterfaceStatusChanged(
+                                cooked[3], cooked[4].equals("up"));
                         return true;
                     } else if (cooked[2].equals("linkstate") && cooked.length == 5) {
-                        notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up"));
+                        mNetworkObserverRegistry.notifyInterfaceLinkStateChanged(
+                                cooked[3], cooked[4].equals("up"));
                         return true;
                     }
                     throw new IllegalStateException(errorMessage);
@@ -882,7 +738,7 @@
                         throw new IllegalStateException(errorMessage);
                     }
                     if (cooked[2].equals("alert")) {
-                        notifyLimitReached(cooked[3], cooked[4]);
+                        mNetworkObserverRegistry.notifyLimitReached(cooked[3], cooked[4]);
                         return true;
                     }
                     throw new IllegalStateException(errorMessage);
@@ -908,9 +764,8 @@
                         timestampNanos = SystemClock.elapsedRealtimeNanos();
                     }
                     boolean isActive = cooked[2].equals("active");
-                    notifyInterfaceClassActivity(Integer.parseInt(cooked[3]),
-                            isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
-                            : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                    mNetworkObserverRegistry.notifyInterfaceClassActivity(
+                            Integer.parseInt(cooked[3]), isActive,
                             timestampNanos, processUid, false);
                     return true;
                     // break;
@@ -937,9 +792,9 @@
                     }
 
                     if (cooked[2].equals("updated")) {
-                        notifyAddressUpdated(iface, address);
+                        mNetworkObserverRegistry.notifyAddressUpdated(iface, address);
                     } else {
-                        notifyAddressRemoved(iface, address);
+                        mNetworkObserverRegistry.notifyAddressRemoved(iface, address);
                     }
                     return true;
                     // break;
@@ -959,7 +814,8 @@
                             throw new IllegalStateException(errorMessage);
                         }
                         String[] servers = cooked[5].split(",");
-                        notifyInterfaceDnsServerInfo(cooked[3], lifetime, servers);
+                        mNetworkObserverRegistry.notifyInterfaceDnsServerInfo(
+                                cooked[3], lifetime, servers);
                     }
                     return true;
                     // break;
@@ -998,7 +854,8 @@
                             InetAddress gateway = null;
                             if (via != null) gateway = InetAddress.parseNumericAddress(via);
                             RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev);
-                            notifyRouteChange(cooked[2].equals("updated"), route);
+                            mNetworkObserverRegistry.notifyRouteChange(
+                                    cooked[2].equals("updated"), route);
                             return true;
                         } catch (IllegalArgumentException e) {}
                     }
@@ -1461,9 +1318,8 @@
             if (ConnectivityManager.isNetworkTypeMobile(type)) {
                 mNetworkActive = false;
             }
-            mDaemonHandler.post(() -> notifyInterfaceClassActivity(type,
-                    DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
-                    SystemClock.elapsedRealtimeNanos(), -1, false));
+            mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity(
+                    type, true /* isActive */, SystemClock.elapsedRealtimeNanos(), -1, false));
         }
     }
 
@@ -1486,9 +1342,9 @@
                 throw new IllegalStateException(e);
             }
             mActiveIdleTimers.remove(iface);
-            mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type,
-                    DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
-                    SystemClock.elapsedRealtimeNanos(), -1, false));
+            mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity(
+                    params.type, false /* isActive */, SystemClock.elapsedRealtimeNanos(), -1,
+                    false));
         }
     }
 
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 9d810cd..cec825f 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -112,6 +112,7 @@
 import android.os.storage.StorageVolume;
 import android.os.storage.VolumeInfo;
 import android.os.storage.VolumeRecord;
+import android.provider.DeviceConfig;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.sysprop.VoldProperties;
@@ -463,6 +464,7 @@
         = { "password", "default", "pattern", "pin" };
 
     private final Context mContext;
+    private final ContentResolver mResolver;
 
     private volatile IVold mVold;
     private volatile IStoraged mStoraged;
@@ -797,6 +799,14 @@
                     refreshIsolatedStorageSettings();
                 }
             });
+        // For now, simply clone property when it changes
+        DeviceConfig.addOnPropertyChangedListener(DeviceConfig.Storage.NAMESPACE,
+                mContext.getMainExecutor(), (namespace, name, value) -> {
+                    if (DeviceConfig.Storage.ISOLATED_STORAGE_ENABLED.equals(name)) {
+                        Settings.Global.putString(mResolver,
+                                Settings.Global.ISOLATED_STORAGE_REMOTE, value);
+                    }
+                });
         refreshIsolatedStorageSettings();
     }
 
@@ -1523,6 +1533,8 @@
                 SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)));
 
         mContext = context;
+        mResolver = mContext.getContentResolver();
+
         mCallbacks = new Callbacks(FgThread.get().getLooper());
         mLockPatternUtils = new LockPatternUtils(mContext);
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a96676e..5d6c2f0 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -677,6 +677,7 @@
 
             final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, r.packageName);
             intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
@@ -1649,6 +1650,7 @@
 
             final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, s.packageName);
             intent.putExtra(Intent.EXTRA_REMOTE_CALLBACK, callback);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 353749f..cdf6e0e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -776,6 +776,7 @@
 
             final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName);
             intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 2508844..62a1b03 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -151,7 +151,7 @@
                 .divide(BigInteger.valueOf(100));
     }
     // How many routes to evaluate before bailing and declaring this Vpn should provide
-    // the INTERNET capability. This is necessary because computing the address space is
+    // the INTERNET capability. This is necessary because computing the adress space is
     // O(n²) and this is running in the system service, so a limit is needed to alleviate
     // the risk of attack.
     // This is taken as a total of IPv4 + IPV6 routes for simplicity, but the algorithm
@@ -194,12 +194,6 @@
     private boolean mLockdown = false;
 
     /**
-     * Set of packages in addition to the VPN app itself that can access the network directly when
-     * VPN is not connected even if {@code mLockdown} is set.
-     */
-    private @NonNull List<String> mLockdownWhitelist = Collections.emptyList();
-
-    /**
      * List of UIDs for which networking should be blocked until VPN is ready, during brief periods
      * when VPN is not running. For example, during system startup or after a crash.
      * @see mLockdown
@@ -326,9 +320,9 @@
      *
      * Used to enable/disable legacy VPN lockdown.
      *
-     * This uses the same ip rule mechanism as
-     * {@link #setAlwaysOnPackage(String, boolean, List<String>)}; previous settings from calling
-     * that function will be replaced and saved with the always-on state.
+     * This uses the same ip rule mechanism as {@link #setAlwaysOnPackage(String, boolean)};
+     * previous settings from calling that function will be replaced and saved with the
+     * always-on state.
      *
      * @param lockdown whether to prevent all traffic outside of a VPN.
      */
@@ -425,14 +419,12 @@
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
-     * @param lockdownWhitelist packages to be whitelisted from lockdown.
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
-    public synchronized boolean setAlwaysOnPackage(
-            String packageName, boolean lockdown, List<String> lockdownWhitelist) {
+    public synchronized boolean setAlwaysOnPackage(String packageName, boolean lockdown) {
         enforceControlPermissionOrInternalCaller();
 
-        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist)) {
+        if (setAlwaysOnPackageInternal(packageName, lockdown)) {
             saveAlwaysOnPackage();
             return true;
         }
@@ -447,27 +439,15 @@
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
-     * @param lockdownWhitelist packages to be whitelisted from lockdown. This is only used if
-     *        {@code lockdown} is {@code true}. Packages must not contain commas.
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
     @GuardedBy("this")
-    private boolean setAlwaysOnPackageInternal(
-            String packageName, boolean lockdown, List<String> lockdownWhitelist) {
+    private boolean setAlwaysOnPackageInternal(String packageName, boolean lockdown) {
         if (VpnConfig.LEGACY_VPN.equals(packageName)) {
             Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
             return false;
         }
 
-        if (lockdownWhitelist != null) {
-            for (String pkg : lockdownWhitelist) {
-                if (pkg.contains(",")) {
-                    Log.w(TAG, "Not setting always-on vpn, invalid whitelisted package: " + pkg);
-                    return false;
-                }
-            }
-        }
-
         if (packageName != null) {
             // Pre-authorize new always-on VPN package.
             if (!setPackageAuthorization(packageName, true)) {
@@ -480,18 +460,13 @@
         }
 
         mLockdown = (mAlwaysOn && lockdown);
-        mLockdownWhitelist = (mLockdown && lockdownWhitelist != null)
-                ? Collections.unmodifiableList(new ArrayList<>(lockdownWhitelist))
-                : Collections.emptyList();
-
         if (isCurrentPreparedPackage(packageName)) {
             updateAlwaysOnNotification(mNetworkInfo.getDetailedState());
-            setVpnForcedLocked(mLockdown);
         } else {
             // Prepare this app. The notification will update as a side-effect of updateState().
-            // It also calls setVpnForcedLocked().
             prepareInternal(packageName);
         }
+        setVpnForcedLocked(mLockdown);
         return true;
     }
 
@@ -503,6 +478,7 @@
      * @return the package name of the VPN controller responsible for always-on VPN,
      *         or {@code null} if none is set or always-on VPN is controlled through
      *         lockdown instead.
+     * @hide
      */
     public synchronized String getAlwaysOnPackage() {
         enforceControlPermissionOrInternalCaller();
@@ -510,13 +486,6 @@
     }
 
     /**
-     * @return an immutable list of packages whitelisted from always-on VPN lockdown.
-     */
-    public synchronized List<String> getLockdownWhitelist() {
-        return mLockdown ? mLockdownWhitelist : null;
-    }
-
-    /**
      * Save the always-on package and lockdown config into Settings.Secure
      */
     @GuardedBy("this")
@@ -527,9 +496,6 @@
                     getAlwaysOnPackage(), mUserHandle);
             mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
                     (mAlwaysOn && mLockdown ? 1 : 0), mUserHandle);
-            mSystemServices.settingsSecurePutStringForUser(
-                    Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST,
-                    String.join(",", mLockdownWhitelist), mUserHandle);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -546,11 +512,7 @@
                     Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle);
             final boolean alwaysOnLockdown = mSystemServices.settingsSecureGetIntForUser(
                     Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0 /*default*/, mUserHandle) != 0;
-            final String whitelistString = mSystemServices.settingsSecureGetStringForUser(
-                    Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, mUserHandle);
-            final List<String> whitelistedPackages = TextUtils.isEmpty(whitelistString)
-                    ? Collections.emptyList() : Arrays.asList(whitelistString.split(","));
-            setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown, whitelistedPackages);
+            setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -570,7 +532,7 @@
             }
             // Remove always-on VPN if it's not supported.
             if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
-                setAlwaysOnPackage(null, false, null);
+                setAlwaysOnPackage(null, false);
                 return false;
             }
             // Skip if the service is already established. This isn't bulletproof: it's not bound
@@ -1287,10 +1249,9 @@
     }
 
     /**
-     * Restricts network access from all UIDs affected by this {@link Vpn}, apart from the VPN
-     * service app itself and whitelisted packages, to only sockets that have had {@code protect()}
-     * called on them. All non-VPN traffic is blocked via a {@code PROHIBIT} response from the
-     * kernel.
+     * Restrict network access from all UIDs affected by this {@link Vpn}, apart from the VPN
+     * service app itself, to only sockets that have had {@code protect()} called on them. All
+     * non-VPN traffic is blocked via a {@code PROHIBIT} response from the kernel.
      *
      * The exception for the VPN UID isn't technically necessary -- setup should use protected
      * sockets -- but in practice it saves apps that don't protect their sockets from breaking.
@@ -1306,13 +1267,8 @@
      */
     @GuardedBy("this")
     private void setVpnForcedLocked(boolean enforce) {
-        final List<String> exemptedPackages;
-        if (isNullOrLegacyVpn(mPackage)) {
-            exemptedPackages = null;
-        } else {
-            exemptedPackages = new ArrayList<>(mLockdownWhitelist);
-            exemptedPackages.add(mPackage);
-        }
+        final List<String> exemptedPackages =
+                isNullOrLegacyVpn(mPackage) ? null : Collections.singletonList(mPackage);
         final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers);
 
         Set<UidRange> addedRanges = Collections.emptySet();
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 5ed6263..e268e44 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -48,7 +48,6 @@
 import android.os.Bundle;
 import android.os.FactoryTest;
 import android.os.IBinder;
-import android.os.Parcel;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -255,21 +254,6 @@
         }
     }
 
-    @Override
-    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
-            throws RemoteException {
-        try {
-            return super.onTransact(code, data, reply, flags);
-        } catch (RuntimeException e) {
-            // The content service only throws security exceptions, so let's
-            // log all others.
-            if (!(e instanceof SecurityException)) {
-                Slog.wtf(TAG, "Content Service Crash", e);
-            }
-            throw e;
-        }
-    }
-
     /*package*/ ContentService(Context context, boolean factoryTest) {
         mContext = context;
         mFactoryTest = factoryTest;
diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java
index 58c88b3..eb0ed0a 100644
--- a/services/core/java/com/android/server/display/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/ColorDisplayService.java
@@ -19,7 +19,10 @@
 import static android.hardware.display.ColorDisplayManager.AUTO_MODE_CUSTOM_TIME;
 import static android.hardware.display.ColorDisplayManager.AUTO_MODE_DISABLED;
 import static android.hardware.display.ColorDisplayManager.AUTO_MODE_TWILIGHT;
-
+import static android.hardware.display.ColorDisplayManager.COLOR_MODE_AUTOMATIC;
+import static android.hardware.display.ColorDisplayManager.COLOR_MODE_BOOSTED;
+import static android.hardware.display.ColorDisplayManager.COLOR_MODE_NATURAL;
+import static android.hardware.display.ColorDisplayManager.COLOR_MODE_SATURATED;
 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION;
@@ -45,6 +48,7 @@
 import android.graphics.ColorSpace;
 import android.hardware.display.ColorDisplayManager;
 import android.hardware.display.ColorDisplayManager.AutoMode;
+import android.hardware.display.ColorDisplayManager.ColorMode;
 import android.hardware.display.IColorDisplayManager;
 import android.hardware.display.Time;
 import android.net.Uri;
@@ -53,6 +57,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings.Secure;
 import android.provider.Settings.System;
@@ -61,7 +66,6 @@
 import android.view.SurfaceControl;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AnimationUtils;
-
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ColorDisplayController;
@@ -71,7 +75,6 @@
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
 import com.android.server.twilight.TwilightState;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -162,7 +165,7 @@
             }
 
             float[] displayRedGreenBlueXYZ =
-                new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY];
+                    new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY];
             float[] displayWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
             for (int i = 0; i < displayRedGreenBlueXYZ.length; i++) {
                 displayRedGreenBlueXYZ[i] = Float.parseFloat(displayPrimariesValues[i]);
@@ -173,10 +176,10 @@
             }
 
             final ColorSpace.Rgb displayColorSpaceRGB = new ColorSpace.Rgb(
-                "Display Color Space",
-                displayRedGreenBlueXYZ,
-                displayWhiteXYZ,
-                2.2f // gamma, unused for display white balance
+                    "Display Color Space",
+                    displayRedGreenBlueXYZ,
+                    displayWhiteXYZ,
+                    2.2f // gamma, unused for display white balance
             );
 
             float[] displayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
@@ -242,8 +245,8 @@
                 mCurrentColorTemperatureXYZ = ColorSpace.cctToIlluminantdXyz(cct);
 
                 mChromaticAdaptationMatrix =
-                    ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
-                            mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
+                        ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
+                                mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
 
                 // Convert the adaptation matrix to RGB space
                 float[] result = ColorSpace.mul3x3(mChromaticAdaptationMatrix,
@@ -385,7 +388,7 @@
      * subtraction from 1. The last row represents a non-multiplied addition, see surfaceflinger's
      * ProgramCache for full implementation details.
      */
-    private static final float[] MATRIX_INVERT_COLOR = new float[] {
+    private static final float[] MATRIX_INVERT_COLOR = new float[]{
             0.402f, -0.598f, -0.599f, 0f,
             -1.174f, -0.174f, -1.175f, 0f,
             -0.228f, -0.228f, 0.772f, 0f,
@@ -944,6 +947,89 @@
                 .setSaturationLevel(packageName, mCurrentUser, saturationLevel);
     }
 
+    private void setColorModeInternal(@ColorMode int colorMode) {
+        if (!isColorModeAvailable(colorMode)) {
+            throw new IllegalArgumentException("Invalid colorMode: " + colorMode);
+        }
+        System.putIntForUser(getContext().getContentResolver(), System.DISPLAY_COLOR_MODE,
+                colorMode,
+                mCurrentUser);
+    }
+
+    private @ColorMode
+    int getColorModeInternal() {
+        final ContentResolver cr = getContext().getContentResolver();
+        if (Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+                0, mCurrentUser) == 1
+                || Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+                0, mCurrentUser) == 1) {
+            // There are restrictions on the available color modes combined with a11y transforms.
+            if (isColorModeAvailable(COLOR_MODE_SATURATED)) {
+                return COLOR_MODE_SATURATED;
+            } else if (isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
+                return COLOR_MODE_AUTOMATIC;
+            }
+        }
+
+        int colorMode = System.getIntForUser(cr, System.DISPLAY_COLOR_MODE, -1, mCurrentUser);
+        if (colorMode == -1) {
+            // There might be a system property controlling color mode that we need to respect; if
+            // not, this will set a suitable default.
+            colorMode = getCurrentColorModeFromSystemProperties();
+        }
+
+        // This happens when a color mode is no longer available (e.g., after system update or B&R)
+        // or the device does not support any color mode.
+        if (!isColorModeAvailable(colorMode)) {
+            if (colorMode == COLOR_MODE_BOOSTED && isColorModeAvailable(COLOR_MODE_NATURAL)) {
+                colorMode = COLOR_MODE_NATURAL;
+            } else if (colorMode == COLOR_MODE_SATURATED
+                    && isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
+                colorMode = COLOR_MODE_AUTOMATIC;
+            } else if (colorMode == COLOR_MODE_AUTOMATIC
+                    && isColorModeAvailable(COLOR_MODE_SATURATED)) {
+                colorMode = COLOR_MODE_SATURATED;
+            } else {
+                colorMode = -1;
+            }
+        }
+
+        return colorMode;
+    }
+
+    /**
+     * Get the current color mode from system properties, or return -1 if invalid.
+     *
+     * See {@link com.android.server.display.DisplayTransformManager}
+     */
+    private @ColorMode
+    int getCurrentColorModeFromSystemProperties() {
+        final int displayColorSetting = SystemProperties.getInt("persist.sys.sf.native_mode", 0);
+        if (displayColorSetting == 0) {
+            return "1.0".equals(SystemProperties.get("persist.sys.sf.color_saturation"))
+                    ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
+        } else if (displayColorSetting == 1) {
+            return COLOR_MODE_SATURATED;
+        } else if (displayColorSetting == 2) {
+            return COLOR_MODE_AUTOMATIC;
+        } else {
+            return -1;
+        }
+    }
+
+    private boolean isColorModeAvailable(@ColorMode int colorMode) {
+        final int[] availableColorModes = getContext().getResources().getIntArray(
+                R.array.config_availableColorModes);
+        if (availableColorModes != null) {
+            for (int mode : availableColorModes) {
+                if (mode == colorMode) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private void dumpInternal(PrintWriter pw) {
         pw.println("COLOR DISPLAY MANAGER dumpsys (color_display)");
         pw.println("Night Display:");
@@ -1445,7 +1531,9 @@
      */
     public interface ColorTransformController {
 
-        /** Apply the given saturation (grayscale) matrix to the associated AppWindow. */
+        /**
+         * Apply the given saturation (grayscale) matrix to the associated AppWindow.
+         */
         void applyAppSaturation(@Size(9) float[] matrix, @Size(3) float[] translation);
     }
 
@@ -1453,6 +1541,29 @@
     final class BinderService extends IColorDisplayManager.Stub {
 
         @Override
+        public void setColorMode(int colorMode) {
+            getContext().enforceCallingOrSelfPermission(
+                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+                    "Permission required to set display color mode");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                setColorModeInternal(colorMode);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public int getColorMode() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return getColorModeInternal();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public boolean isDeviceColorManaged() {
             final long token = Binder.clearCallingIdentity();
             try {
@@ -1640,7 +1751,9 @@
 
         @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
+            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+                return;
+            }
 
             final long token = Binder.clearCallingIdentity();
             try {
diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java
index a5e9728..b1b7d3c 100644
--- a/services/core/java/com/android/server/display/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/DisplayTransformManager.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import android.app.ActivityTaskManager;
+import android.hardware.display.ColorDisplayManager;
 import android.opengl.Matrix;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -27,7 +28,6 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.ColorDisplayController;
 
 import java.util.Arrays;
 
@@ -248,20 +248,20 @@
      * work in linear space.
      */
     public static boolean needsLinearColorMatrix(int colorMode) {
-        return colorMode != ColorDisplayController.COLOR_MODE_SATURATED;
+        return colorMode != ColorDisplayManager.COLOR_MODE_SATURATED;
     }
 
     public boolean setColorMode(int colorMode, float[] nightDisplayMatrix) {
-        if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) {
+        if (colorMode == ColorDisplayManager.COLOR_MODE_NATURAL) {
             applySaturation(COLOR_SATURATION_NATURAL);
             setDisplayColor(DISPLAY_COLOR_MANAGED);
-        } else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) {
+        } else if (colorMode == ColorDisplayManager.COLOR_MODE_BOOSTED) {
             applySaturation(COLOR_SATURATION_BOOSTED);
             setDisplayColor(DISPLAY_COLOR_MANAGED);
-        } else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) {
+        } else if (colorMode == ColorDisplayManager.COLOR_MODE_SATURATED) {
             applySaturation(COLOR_SATURATION_NATURAL);
             setDisplayColor(DISPLAY_COLOR_UNMANAGED);
-        } else if (colorMode == ColorDisplayController.COLOR_MODE_AUTOMATIC) {
+        } else if (colorMode == ColorDisplayManager.COLOR_MODE_AUTOMATIC) {
             applySaturation(COLOR_SATURATION_NATURAL);
             setDisplayColor(DISPLAY_COLOR_ENHANCED);
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3fd3945..4db541c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -288,6 +288,8 @@
     private static final class DebugFlags {
         static final DebugFlag FLAG_OPTIMIZE_START_INPUT =
                 new DebugFlag("debug.optimize_startinput", false);
+        static final DebugFlag FLAG_PRE_RENDER_IME_VIEWS =
+                new DebugFlag("persist.pre_render_ime_views", false);
     }
 
     @UserIdInt
@@ -304,6 +306,7 @@
     final boolean mHasFeature;
     private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
             new ArrayMap<>();
+    private final boolean mIsLowRam;
     private final HardKeyboardListener mHardKeyboardListener;
     private final AppOpsManager mAppOpsManager;
     private final UserManager mUserManager;
@@ -403,6 +406,10 @@
         final ClientDeathRecipient clientDeathRecipient;
 
         boolean sessionRequested;
+        // Determines if IMEs should be pre-rendered.
+        // DebugFlag can be flipped anytime. This flag is kept per-client to maintain behavior
+        // through the life of the current client.
+        boolean shouldPreRenderIme;
         SessionState curSession;
 
         @Override
@@ -615,6 +622,10 @@
      * <dd>
      *   If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
      * </dd>
+     * dt>{@link InputMethodService#IME_INVISIBLE}</dt>
+     * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
+     *    currently invisible.
+     * </dd>
      * </dl>
      * <em>Do not update this value outside of setImeWindowStatus.</em>
      */
@@ -1361,6 +1372,7 @@
         mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
         mHardKeyboardBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_externalHardKeyboardBehavior);
+        mIsLowRam = ActivityManager.isLowRamDeviceStatic();
 
         Bundle extras = new Bundle();
         extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true);
@@ -1393,7 +1405,7 @@
 
         // mSettings should be created before buildInputMethodListLocked
         mSettings = new InputMethodSettings(
-                mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady);
+                mRes, context.getContentResolver(), mMethodMap, userId, !mSystemReady);
 
         updateCurrentProfileIds();
         AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
@@ -1682,7 +1694,7 @@
         queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
                 methodList);
         final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
-                mContext.getContentResolver(), methodMap, methodList, userId, true);
+                mContext.getContentResolver(), methodMap, userId, true);
         return settings.getEnabledInputMethodListLocked();
     }
 
@@ -1738,7 +1750,7 @@
             return Collections.emptyList();
         }
         final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
-                mContext.getContentResolver(), methodMap, methodList, userId, true);
+                mContext.getContentResolver(), methodMap, userId, true);
         return settings.getEnabledInputMethodSubtypeListLocked(
                 mContext, imi, allowsImplicitlySelectedSubtypes);
     }
@@ -2264,7 +2276,10 @@
         if (mSwitchingDialog != null) return false;
         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
                 && mKeyguardManager != null && mKeyguardManager.isKeyguardSecure()) return false;
-        if ((visibility & InputMethodService.IME_ACTIVE) == 0) return false;
+        if ((visibility & InputMethodService.IME_ACTIVE) == 0
+                || (visibility & InputMethodService.IME_INVISIBLE) != 0) {
+            return false;
+        }
         if (mWindowManagerInternal.isHardKeyboardAvailable()) {
             if (mHardKeyboardBehavior == HardKeyboardBehavior.WIRELESS_AFFORDANCE) {
                 // When physical keyboard is attached, we show the ime switcher (or notification if
@@ -2372,6 +2387,12 @@
         if (mCurToken == null) {
             return;
         }
+        if (DEBUG) {
+            Slog.d(TAG, "IME window vis: " + vis
+                    + " active: " + (vis & InputMethodService.IME_ACTIVE)
+                    + " inv: " + (vis & InputMethodService.IME_INVISIBLE));
+        }
+
         // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
         // all updateSystemUi happens on system previlege.
         final long ident = Binder.clearCallingIdentity();
@@ -2830,6 +2851,14 @@
                 if (PER_PROFILE_IME_ENABLED && userId != mSettings.getCurrentUserId()) {
                     switchUserLocked(userId);
                 }
+                // Master feature flag that overrides other conditions and forces IME preRendering.
+                if (DEBUG) {
+                    Slog.v(TAG, "IME PreRendering MASTER flag: "
+                            + DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value()
+                            + ", LowRam: " + mIsLowRam);
+                }
+                // pre-rendering not supported on low-ram devices.
+                cs.shouldPreRenderIme = DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() && !mIsLowRam;
 
                 if (mCurFocusedWindow == windowToken) {
                     if (DEBUG) {
@@ -3493,7 +3522,7 @@
                 try {
                     setEnabledSessionInMainThread(session);
                     session.method.startInput(startInputToken, inputContext, missingMethods,
-                            editorInfo, restarting);
+                            editorInfo, restarting, session.client.shouldPreRenderIme);
                 } catch (RemoteException e) {
                 }
                 args.recycle();
@@ -4421,6 +4450,7 @@
         @ShellCommandResult
         private int refreshDebugProperties() {
             DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh();
+            DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.refresh();
             return ShellCommandResult.SUCCESS;
         }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 88d1a9c..cfc85da 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -66,9 +66,9 @@
  */
 final class InputMethodUtils {
     public static final boolean DEBUG = false;
-    public static final int NOT_A_SUBTYPE_ID = -1;
-    public static final String SUBTYPE_MODE_ANY = null;
-    public static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+    static final int NOT_A_SUBTYPE_ID = -1;
+    private static final String SUBTYPE_MODE_ANY = null;
+    static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
     private static final String TAG = "InputMethodUtils";
     private static final Locale ENGLISH_LOCALE = new Locale("en");
     private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
@@ -108,7 +108,7 @@
 
     // ----------------------------------------------------------------------
     // Utilities for debug
-    public static String getApiCallStack() {
+    static String getApiCallStack() {
         String apiCallStack = "";
         try {
             throw new RuntimeException();
@@ -131,10 +131,9 @@
     }
     // ----------------------------------------------------------------------
 
-    public static boolean isSystemImeThatHasSubtypeOf(final InputMethodInfo imi,
-            final Context context, final boolean checkDefaultAttribute,
-            @Nullable final Locale requiredLocale, final boolean checkCountry,
-            final String requiredSubtypeMode) {
+    private static boolean isSystemImeThatHasSubtypeOf(InputMethodInfo imi, Context context,
+            boolean checkDefaultAttribute, @Nullable Locale requiredLocale, boolean checkCountry,
+            String requiredSubtypeMode) {
         if (!imi.isSystem()) {
             return false;
         }
@@ -148,8 +147,8 @@
     }
 
     @Nullable
-    public static Locale getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis,
-            final Context context) {
+    private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis,
+            Context context) {
         // At first, find the fallback locale from the IMEs that are declared as "default" in the
         // current locale.  Note that IME developers can declare an IME as "default" only for
         // some particular locales but "not default" for other locales.
@@ -177,8 +176,8 @@
         return null;
     }
 
-    private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(final InputMethodInfo imi,
-            final Context context, final boolean checkDefaultAttribute) {
+    private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi,
+            Context context, boolean checkDefaultAttribute) {
         if (!imi.isSystem()) {
             return false;
         }
@@ -198,7 +197,7 @@
         return false;
     }
 
-    public static Locale getSystemLocaleFromContext(final Context context) {
+    private static Locale getSystemLocaleFromContext(Context context) {
         try {
             return context.getResources().getConfiguration().locale;
         } catch (Resources.NotFoundException ex) {
@@ -212,10 +211,9 @@
         @NonNull
         private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>();
 
-        public InputMethodListBuilder fillImes(final ArrayList<InputMethodInfo> imis,
-                final Context context, final boolean checkDefaultAttribute,
-                @Nullable final Locale locale, final boolean checkCountry,
-                final String requiredSubtypeMode) {
+        InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context,
+                boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry,
+                String requiredSubtypeMode) {
             for (int i = 0; i < imis.size(); ++i) {
                 final InputMethodInfo imi = imis.get(i);
                 if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale,
@@ -228,8 +226,7 @@
 
         // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
         // documented more clearly.
-        public InputMethodListBuilder fillAuxiliaryImes(final ArrayList<InputMethodInfo> imis,
-                final Context context) {
+        InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) {
             // If one or more auxiliary input methods are available, OK to stop populating the list.
             for (final InputMethodInfo imi : mInputMethodSet) {
                 if (imi.isAuxiliaryIme()) {
@@ -269,8 +266,8 @@
     }
 
     private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
-            final ArrayList<InputMethodInfo> imis, final Context context,
-            @Nullable final Locale systemLocale, @Nullable final Locale fallbackLocale) {
+            ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale,
+            @Nullable Locale fallbackLocale) {
         // Once the system becomes ready, we pick up at least one keyboard in the following order.
         // Secondary users fall into this category in general.
         // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true
@@ -317,7 +314,7 @@
         return builder;
     }
 
-    public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
+    static ArrayList<InputMethodInfo> getDefaultEnabledImes(
             Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) {
         final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
         // We will primarily rely on the system locale, but also keep relying on the fallback locale
@@ -336,13 +333,13 @@
         return builder.build();
     }
 
-    public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
+    static ArrayList<InputMethodInfo> getDefaultEnabledImes(
             Context context, ArrayList<InputMethodInfo> imis) {
         return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
     }
 
-    public static boolean containsSubtypeOf(final InputMethodInfo imi,
-            @Nullable final Locale locale, final boolean checkCountry, final String mode) {
+    static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale,
+            boolean checkCountry, String mode) {
         if (locale == null) {
             return false;
         }
@@ -371,7 +368,7 @@
         return false;
     }
 
-    public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
+    static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
         ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
         final int subtypeCount = imi.getSubtypeCount();
         for (int i = 0; i < subtypeCount; ++i) {
@@ -380,7 +377,7 @@
         return subtypes;
     }
 
-    public static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
+    static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
         if (enabledImes == null || enabledImes.isEmpty()) {
             return null;
         }
@@ -404,11 +401,11 @@
         return enabledImes.get(Math.max(firstFoundSystemIme, 0));
     }
 
-    public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
+    static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
         return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
     }
 
-    public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
+    static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
         if (imi != null) {
             final int subtypeCount = imi.getSubtypeCount();
             for (int i = 0; i < subtypeCount; ++i) {
@@ -430,7 +427,7 @@
             };
 
     @VisibleForTesting
-    public static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
+    static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
             Resources res, InputMethodInfo imi) {
         final LocaleList systemLocales = res.getConfiguration().getLocales();
 
@@ -564,7 +561,7 @@
      * it will return the first subtype matched with mode
      * @return the most applicable subtypeId
      */
-    public static InputMethodSubtype findLastResortApplicableSubtypeLocked(
+    static InputMethodSubtype findLastResortApplicableSubtypeLocked(
             Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
             boolean canIgnoreLocaleAsLastResort) {
         if (subtypes == null || subtypes.size() == 0) {
@@ -615,14 +612,13 @@
         return applicableSubtype;
     }
 
-    public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
+    static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
         if (subtype == null) return true;
         return !subtype.isAuxiliary();
     }
 
-    public static void setNonSelectedSystemImesDisabledUntilUsed(
-            IPackageManager packageManager, List<InputMethodInfo> enabledImis,
-            int userId, String callingPackage) {
+    static void setNonSelectedSystemImesDisabledUntilUsed(IPackageManager packageManager,
+            List<InputMethodInfo> enabledImis, @UserIdInt int userId, String callingPackage) {
         if (DEBUG) {
             Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
         }
@@ -710,7 +706,7 @@
         }
     }
 
-    public static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
+    static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
             InputMethodSubtype subtype) {
         final CharSequence imiLabel = imi.loadLabel(context.getPackageManager());
         return subtype != null
@@ -730,8 +726,8 @@
      * @param packageName the package name.
      * @return {@code true} if the package name belongs to the UID.
      */
-    public static boolean checkIfPackageBelongsToUid(final AppOpsManager appOpsManager,
-            final int uid, final String packageName) {
+    static boolean checkIfPackageBelongsToUid(AppOpsManager appOpsManager,
+            @UserIdInt int uid, String packageName) {
         try {
             appOpsManager.checkPackage(uid, packageName);
             return true;
@@ -802,10 +798,9 @@
             return imsList;
         }
 
-        public InputMethodSettings(
-                Resources res, ContentResolver resolver,
-                ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
-                @UserIdInt int userId, boolean copyOnWrite) {
+        InputMethodSettings(Resources res, ContentResolver resolver,
+                ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId,
+                boolean copyOnWrite) {
             mRes = res;
             mResolver = resolver;
             mMethodMap = methodMap;
@@ -820,7 +815,7 @@
          * (e.g. {@link Settings.Secure#ACTION_INPUT_METHOD_SUBTYPE_SETTINGS}) we use the actual
          * settings on the {@link Settings.Secure} until we do the first write operation.
          */
-        public void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
+        void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
             if (DEBUG) {
                 Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId);
             }
@@ -834,7 +829,7 @@
             // TODO: mCurrentProfileIds should be updated here.
         }
 
-        private void putString(@NonNull final String key, @Nullable final String str) {
+        private void putString(@NonNull String key, @Nullable String str) {
             if (mCopyOnWrite) {
                 mCopyOnWriteDataStore.put(key, str);
             } else {
@@ -843,7 +838,7 @@
         }
 
         @Nullable
-        private String getString(@NonNull final String key, @Nullable final String defaultValue) {
+        private String getString(@NonNull String key, @Nullable String defaultValue) {
             final String result;
             if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
                 result = mCopyOnWriteDataStore.get(key);
@@ -853,7 +848,7 @@
             return result != null ? result : defaultValue;
         }
 
-        private void putInt(final String key, final int value) {
+        private void putInt(String key, int value) {
             if (mCopyOnWrite) {
                 mCopyOnWriteDataStore.put(key, String.valueOf(value));
             } else {
@@ -861,7 +856,7 @@
             }
         }
 
-        private int getInt(final String key, final int defaultValue) {
+        private int getInt(String key, int defaultValue) {
             if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
                 final String result = mCopyOnWriteDataStore.get(key);
                 return result != null ? Integer.parseInt(result) : 0;
@@ -869,11 +864,11 @@
             return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId);
         }
 
-        private void putBoolean(final String key, final boolean value) {
+        private void putBoolean(String key, boolean value) {
             putInt(key, value ? 1 : 0);
         }
 
-        private boolean getBoolean(final String key, final boolean defaultValue) {
+        private boolean getBoolean(String key, boolean defaultValue) {
             return getInt(key, defaultValue ? 1 : 0) == 1;
         }
 
@@ -893,12 +888,12 @@
             }
         }
 
-        public ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
+        ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
             return createEnabledInputMethodListLocked(
                     getEnabledInputMethodsAndSubtypeListLocked());
         }
 
-        public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
+        List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
                 Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
             List<InputMethodSubtype> enabledSubtypes =
                     getEnabledInputMethodSubtypeListLocked(imi);
@@ -909,8 +904,7 @@
             return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
         }
 
-        public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
-                InputMethodInfo imi) {
+        List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
             List<Pair<String, ArrayList<String>>> imsList =
                     getEnabledInputMethodsAndSubtypeListLocked();
             ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
@@ -934,13 +928,13 @@
             return enabledSubtypes;
         }
 
-        public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
+        List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
             return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
                     mInputMethodSplitter,
                     mSubtypeSplitter);
         }
 
-        public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
+        void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
             if (reloadInputMethodStr) {
                 getEnabledInputMethodsStr();
             }
@@ -957,7 +951,7 @@
          * Build and put a string of EnabledInputMethods with removing specified Id.
          * @return the specified id was removed or not.
          */
-        public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+        boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
                 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
             boolean isRemoved = false;
             boolean needsAppendSeparator = false;
@@ -1012,7 +1006,7 @@
         }
 
         @NonNull
-        public String getEnabledInputMethodsStr() {
+        String getEnabledInputMethodsStr() {
             mEnabledInputMethodsStrCache = getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
             if (DEBUG) {
                 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
@@ -1080,12 +1074,12 @@
             }
         }
 
-        public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
+        Pair<String, String> getLastInputMethodAndSubtypeLocked() {
             // Gets the first one from the history
             return getLastSubtypeForInputMethodLockedInternal(null);
         }
 
-        public String getLastSubtypeForInputMethodLocked(String imeId) {
+        String getLastSubtypeForInputMethodLocked(String imeId) {
             Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
             if (ime != null) {
                 return ime.second;
@@ -1203,7 +1197,7 @@
             return history;
         }
 
-        public void putSelectedInputMethod(String imeId) {
+        void putSelectedInputMethod(String imeId) {
             if (DEBUG) {
                 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
                         + mCurrentUserId);
@@ -1211,7 +1205,7 @@
             putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
         }
 
-        public void putSelectedSubtype(int subtypeId) {
+        void putSelectedSubtype(int subtypeId) {
             if (DEBUG) {
                 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
                         + mCurrentUserId);
@@ -1220,7 +1214,7 @@
         }
 
         @Nullable
-        public String getSelectedInputMethod() {
+        String getSelectedInputMethod() {
             final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
             if (DEBUG) {
                 Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
@@ -1228,7 +1222,7 @@
             return imi;
         }
 
-        public boolean isSubtypeSelected() {
+        boolean isSubtypeSelected() {
             return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
         }
 
@@ -1236,11 +1230,11 @@
             return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID);
         }
 
-        public boolean isShowImeWithHardKeyboardEnabled() {
+        boolean isShowImeWithHardKeyboardEnabled() {
             return getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, false);
         }
 
-        public void setShowImeWithHardKeyboard(boolean show) {
+        void setShowImeWithHardKeyboard(boolean show) {
             putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show);
         }
 
@@ -1249,7 +1243,7 @@
             return mCurrentUserId;
         }
 
-        public int getSelectedInputMethodSubtypeId(String selectedImiId) {
+        int getSelectedInputMethodSubtypeId(String selectedImiId) {
             final InputMethodInfo imi = mMethodMap.get(selectedImiId);
             if (imi == null) {
                 return NOT_A_SUBTYPE_ID;
@@ -1258,8 +1252,8 @@
             return getSubtypeIdFromHashCode(imi, subtypeHashCode);
         }
 
-        public void saveCurrentInputMethodAndSubtypeToHistory(
-                String curMethodId, InputMethodSubtype currentSubtype) {
+        void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
+                InputMethodSubtype currentSubtype) {
             String subtypeId = NOT_A_SUBTYPE_ID_STR;
             if (currentSubtype != null) {
                 subtypeId = String.valueOf(currentSubtype.hashCode());
@@ -1277,8 +1271,8 @@
         }
     }
 
-    public static boolean isSoftInputModeStateVisibleAllowed(
-            int targetSdkVersion, @StartInputFlags int startInputFlags) {
+    static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion,
+            @StartInputFlags int startInputFlags) {
         if (targetSdkVersion < Build.VERSION_CODES.P) {
             // for compatibility.
             return true;
diff --git a/services/core/java/com/android/server/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java
index 591889d..ca9c0e0 100644
--- a/services/core/java/com/android/server/location/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java
@@ -19,9 +19,15 @@
 import android.annotation.SuppressLint;
 import android.app.AppOpsManager;
 import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
@@ -55,6 +61,8 @@
     private static final String LOCATION_PERMISSION_NAME =
             "android.permission.ACCESS_FINE_LOCATION";
 
+    private static final String[] NO_LOCATION_ENABLED_PROXY_APPS = new String[0];
+
     // Wakelocks
     private static final String WAKELOCK_KEY = TAG;
     private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
@@ -66,13 +74,16 @@
     private final Handler mHandler;
     private final Context mContext;
 
+    private boolean mIsMasterLocationSettingsEnabled = true;
+    private boolean mIsOnRoamingNetwork = false;
+
     // Number of non-framework location access proxy apps is expected to be small (< 5).
     private static final int HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS = 7;
     private HashMap<String, Boolean> mProxyAppToLocationPermissions = new HashMap<>(
             HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS);
 
     private PackageManager.OnPermissionsChangedListener mOnPermissionsChangedListener =
-            uid -> postEvent(() -> handlePermissionsChanged(uid));
+            uid -> runOnHandler(() -> handlePermissionsChanged(uid));
 
     GnssVisibilityControl(Context context, Looper looper) {
         mContext = context;
@@ -81,8 +92,15 @@
         mHandler = new Handler(looper);
         mAppOps = mContext.getSystemService(AppOpsManager.class);
         mPackageManager = mContext.getPackageManager();
+
+        // Set to empty proxy app list initially until the configuration properties are loaded.
+        updateNfwLocationAccessProxyAppsInGnssHal();
+
+        // Listen for proxy app package installation, removal events.
+        listenForProxyAppsPackageUpdates();
+        listenForRoamingNetworkUpdate();
+
         // TODO(b/122855984): Handle global location settings on/off.
-        // TODO(b/122856189): Handle roaming case.
     }
 
     void updateProxyApps(List<String> nfwLocationAccessProxyApps) {
@@ -90,18 +108,68 @@
         //       but rather piggy backs on the GnssLocationProvider SIM_STATE_CHANGED handling
         //       so that the order of processing is preserved. GnssLocationProvider should
         //       first load the new config parameters for the new SIM and then call this method.
-        postEvent(() -> handleSubscriptionOrSimChanged(nfwLocationAccessProxyApps));
+        runOnHandler(() -> handleUpdateProxyApps(nfwLocationAccessProxyApps));
+    }
+
+    void masterLocationSettingsUpdated(boolean enabled) {
+        runOnHandler(() -> handleMasterLocationSettingsUpdated(enabled));
     }
 
     void reportNfwNotification(String proxyAppPackageName, byte protocolStack,
             String otherProtocolStackName, byte requestor, String requestorId, byte responseType,
             boolean inEmergencyMode, boolean isCachedLocation) {
-        postEvent(() -> handleNfwNotification(
+        runOnHandler(() -> handleNfwNotification(
                 new NfwNotification(proxyAppPackageName, protocolStack, otherProtocolStackName,
                         requestor, requestorId, responseType, inEmergencyMode, isCachedLocation)));
     }
 
-    private void handleSubscriptionOrSimChanged(List<String> nfwLocationAccessProxyApps) {
+    private void listenForProxyAppsPackageUpdates() {
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        intentFilter.addDataScheme("package");
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action == null) {
+                    return;
+                }
+
+                switch (action) {
+                    case Intent.ACTION_PACKAGE_ADDED:
+                    case Intent.ACTION_PACKAGE_REMOVED:
+                    case Intent.ACTION_PACKAGE_REPLACED:
+                        String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+                        handleProxyAppPackageUpdate(pkgName, action);
+                        break;
+                }
+            }
+        }, UserHandle.ALL, intentFilter, null, mHandler);
+    }
+
+    private void handleProxyAppPackageUpdate(String pkgName, String action) {
+        final Boolean locationPermission = mProxyAppToLocationPermissions.get(pkgName);
+        // pkgName is not one of the proxy apps in our list.
+        if (locationPermission == null) {
+            return;
+        }
+
+        Log.i(TAG, "Proxy app " + pkgName + " package changed: " + action);
+        final boolean updatedLocationPermission = hasLocationPermission(pkgName);
+        if (locationPermission != updatedLocationPermission) {
+            // Permission changed. So, update the GNSS HAL with the updated list.
+            mProxyAppToLocationPermissions.put(pkgName, updatedLocationPermission);
+            updateNfwLocationAccessProxyAppsInGnssHal();
+        }
+    }
+
+    private void handleUpdateProxyApps(List<String> nfwLocationAccessProxyApps) {
+        if (!isProxyAppListUpdated(nfwLocationAccessProxyApps)) {
+            return;
+        }
+
         if (nfwLocationAccessProxyApps.isEmpty()) {
             // Stop listening for app permission changes. Clear the app list in GNSS HAL.
             if (!mProxyAppToLocationPermissions.isEmpty()) {
@@ -125,6 +193,27 @@
         updateNfwLocationAccessProxyAppsInGnssHal();
     }
 
+    private boolean isProxyAppListUpdated(List<String> nfwLocationAccessProxyApps) {
+        if (nfwLocationAccessProxyApps.size() != mProxyAppToLocationPermissions.size()) {
+            return true;
+        }
+
+        for (String nfwLocationAccessProxyApp : nfwLocationAccessProxyApps) {
+            if (!mProxyAppToLocationPermissions.containsKey(nfwLocationAccessProxyApp)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private void handleMasterLocationSettingsUpdated(boolean enabled) {
+        mIsMasterLocationSettingsEnabled = enabled;
+        Log.i(TAG, "Master location settings switch changed to "
+                + (enabled ? "enabled" : "disabled"));
+        updateNfwLocationAccessProxyAppsInGnssHal();
+    }
+
     // Represents NfwNotification structure in IGnssVisibilityControlCallback.hal
     private static class NfwNotification {
         private static final String KEY_PROTOCOL_STACK = "ProtocolStack";
@@ -149,7 +238,7 @@
         private final boolean mInEmergencyMode;
         private final boolean mIsCachedLocation;
 
-        NfwNotification(String proxyAppPackageName, byte protocolStack,
+        private NfwNotification(String proxyAppPackageName, byte protocolStack,
                 String otherProtocolStackName, byte requestor, String requestorId,
                 byte responseType, boolean inEmergencyMode, boolean isCachedLocation) {
             mProxyAppPackageName = proxyAppPackageName;
@@ -162,7 +251,7 @@
             mIsCachedLocation = isCachedLocation;
         }
 
-        void copyFieldsToIntent(Intent intent) {
+        private void copyFieldsToIntent(Intent intent) {
             intent.putExtra(KEY_PROTOCOL_STACK, mProtocolStack);
             if (!TextUtils.isEmpty(mOtherProtocolStackName)) {
                 intent.putExtra(KEY_OTHER_PROTOCOL_STACK_NAME, mOtherProtocolStackName);
@@ -188,7 +277,7 @@
                     mRequestor, mRequestorId, mResponseType, mInEmergencyMode, mIsCachedLocation);
         }
 
-        String getResponseTypeAsString() {
+        private String getResponseTypeAsString() {
             switch (mResponseType) {
                 case NFW_RESPONSE_TYPE_REJECTED:
                     return "REJECTED";
@@ -246,6 +335,24 @@
     }
 
     private void updateNfwLocationAccessProxyAppsInGnssHal() {
+        final String[] locationPermissionEnabledProxyApps = shouldDisableNfwLocationAccess()
+                ? NO_LOCATION_ENABLED_PROXY_APPS : getLocationPermissionEnabledProxyApps();
+        final String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps);
+        Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: "
+                + proxyAppsStr);
+        boolean result = native_enable_nfw_location_access(locationPermissionEnabledProxyApps);
+        if (!result) {
+            Log.e(TAG, "Failed to update non-framework location access proxy apps in the"
+                    + " GNSS HAL to: " + proxyAppsStr);
+        }
+    }
+
+    private boolean shouldDisableNfwLocationAccess() {
+        // TODO(b/122856189): Add disableWhenRoaming configuration per proxy app.
+        return mIsOnRoamingNetwork || !mIsMasterLocationSettingsEnabled;
+    }
+
+    private String[] getLocationPermissionEnabledProxyApps() {
         // Get a count of proxy apps with location permission enabled to array creation size.
         int countLocationPermissionEnabledProxyApps = 0;
         for (Boolean hasLocationPermissionEnabled : mProxyAppToLocationPermissions.values()) {
@@ -264,15 +371,7 @@
                 locationPermissionEnabledProxyApps[i++] = proxyApp;
             }
         }
-
-        String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps);
-        Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: "
-                + proxyAppsStr);
-        boolean result = native_enable_nfw_location_access(locationPermissionEnabledProxyApps);
-        if (!result) {
-            Log.e(TAG, "Failed to update non-framework location access proxy apps in the"
-                    + " GNSS HAL to: " + proxyAppsStr);
-        }
+        return locationPermissionEnabledProxyApps;
     }
 
     private void handleNfwNotification(NfwNotification nfwNotification) {
@@ -360,7 +459,31 @@
                 isPermissionMismatched);
     }
 
-    private void postEvent(Runnable event) {
+    private void listenForRoamingNetworkUpdate() {
+        // Register for network capabilities changes to monitor roaming changes.
+        ConnectivityManager mConnMgr = (ConnectivityManager) mContext.getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+        NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+        networkRequestBuilder.addCapability(NetworkCapabilities.TRANSPORT_CELLULAR);
+        NetworkRequest networkRequest = networkRequestBuilder.build();
+        mConnMgr.registerNetworkCallback(networkRequest,
+                new ConnectivityManager.NetworkCallback() {
+                    @Override
+                    public void onCapabilitiesChanged(Network network,
+                            NetworkCapabilities capabilities) {
+                        boolean isRoaming = !capabilities.hasTransport(
+                                NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
+                        // No locking required for this test and set because the callback
+                        // runs in mHandler thread.
+                        if (mIsOnRoamingNetwork != isRoaming) {
+                            mIsOnRoamingNetwork = isRoaming;
+                            updateNfwLocationAccessProxyAppsInGnssHal();
+                        }
+                    }
+                }, mHandler);
+    }
+
+    private void runOnHandler(Runnable event) {
         // Hold a wake lock until this message is delivered.
         // Note that this assumes the message will not be removed from the queue before
         // it is handled (otherwise the wake lock would be leaked).
diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
index 1541b1d..dd26a29 100644
--- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
@@ -42,8 +42,6 @@
 import android.media.AudioSystem;
 import android.media.IAudioService;
 import android.media.IRemoteVolumeController;
-import android.media.MediaController2;
-import android.media.Session2CommandGroup;
 import android.media.Session2Token;
 import android.media.session.ControllerLink;
 import android.media.session.IActiveSessionsListener;
@@ -60,7 +58,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.PowerManager;
@@ -1007,17 +1004,50 @@
                 if (DEBUG) {
                     Log.d(TAG, "Session2 is created " + sessionToken);
                 }
+                if (pid != sessionToken.getPid()) {
+                    throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid
+                            + " but actually=" + sessionToken.getPid());
+                }
                 if (uid != sessionToken.getUid()) {
                     throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
                             + " but actually=" + sessionToken.getUid());
                 }
-                Controller2Callback callback = new Controller2Callback(sessionToken);
-                // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
-                //       it's closed.
-                // TODO: Keep controller as well for better readability
-                //       because the GC behavior isn't straightforward.
-                MediaController2 controller = new MediaController2(mContext, sessionToken,
-                        new HandlerExecutor(mHandler), callback);
+                int userId = UserHandle.getUserId(uid);
+                List<Session2Token> session2Tokens = mSession2TokensPerUser.get(userId);
+                if (session2Tokens.contains(sessionToken)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "notifySession2Created(): Ignoring already existing token "
+                                + sessionToken);
+                    }
+                    return;
+                }
+                session2Tokens.add(sessionToken);
+                pushSession2TokensChangedLocked(userId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void notifySession2Destroyed(Session2Token sessionToken) throws RemoteException {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (DEBUG) {
+                    Log.d(TAG, "Session2 is destroyed " + sessionToken);
+                }
+                if (pid != sessionToken.getPid()) {
+                    throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid
+                            + " but actually=" + sessionToken.getPid());
+                }
+                if (uid != sessionToken.getUid()) {
+                    throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
+                            + " but actually=" + sessionToken.getUid());
+                }
+                int userId = UserHandle.getUserId(uid);
+                mSession2TokensPerUser.get(userId).remove(sessionToken);
+                pushSession2TokensChangedLocked(userId);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -2114,30 +2144,4 @@
             obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
         }
     }
-
-    private class Controller2Callback extends MediaController2.ControllerCallback {
-        private final Session2Token mToken;
-
-        Controller2Callback(Session2Token token) {
-            mToken = token;
-        }
-
-        @Override
-        public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
-            synchronized (mLock) {
-                int userId = UserHandle.getUserId(mToken.getUid());
-                mSession2TokensPerUser.get(userId).add(mToken);
-                pushSession2TokensChangedLocked(userId);
-            }
-        }
-
-        @Override
-        public void onDisconnected(MediaController2 controller) {
-            synchronized (mLock) {
-                int userId = UserHandle.getUserId(mToken.getUid());
-                mSession2TokensPerUser.get(userId).remove(mToken);
-                pushSession2TokensChangedLocked(userId);
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 02fc51f..ac965fa 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -690,6 +690,8 @@
                                     importance));
                 }
             }
+            // We have now gotten all the information out of the adjustments and can forget them.
+            mAdjustments.clear();
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0ab2a73..eab5c8f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -537,7 +537,8 @@
         session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                 mInstallThread.getLooper(), mStagingManager, sessionId, userId,
                 installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
-                false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR);
+                false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR,
+                "");
 
         synchronized (mSessions) {
             mSessions.put(sessionId, session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index b8825bb..494ec3f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -155,6 +155,7 @@
     private static final String ATTR_IS_FAILED = "isFailed";
     private static final String ATTR_IS_APPLIED = "isApplied";
     private static final String ATTR_STAGED_SESSION_ERROR_CODE = "errorCode";
+    private static final String ATTR_STAGED_SESSION_ERROR_MESSAGE = "errorMessage";
     private static final String ATTR_MODE = "mode";
     private static final String ATTR_INSTALL_FLAGS = "installFlags";
     private static final String ATTR_INSTALL_LOCATION = "installLocation";
@@ -267,6 +268,8 @@
     private boolean mStagedSessionFailed;
     @GuardedBy("mLock")
     private int mStagedSessionErrorCode = SessionInfo.NO_ERROR;
+    @GuardedBy("mLock")
+    private String mStagedSessionErrorMessage;
 
     /**
      * Path to the validated base APK for this session, which may point at an
@@ -413,7 +416,8 @@
             String installerPackageName, int installerUid, SessionParams params, long createdMillis,
             File stageDir, String stageCid, boolean prepared, boolean sealed,
             @Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
-            boolean isFailed, boolean isApplied, int stagedSessionErrorCode) {
+            boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
+            String stagedSessionErrorMessage) {
         mCallback = callback;
         mContext = context;
         mPm = pm;
@@ -447,6 +451,8 @@
         mStagedSessionFailed = isFailed;
         mStagedSessionApplied = isApplied;
         mStagedSessionErrorCode = stagedSessionErrorCode;
+        mStagedSessionErrorMessage =
+                stagedSessionErrorMessage != null ? stagedSessionErrorMessage : "";
         if (sealed) {
             synchronized (mLock) {
                 try {
@@ -499,7 +505,7 @@
             info.isSessionApplied = mStagedSessionApplied;
             info.isSessionReady = mStagedSessionReady;
             info.isSessionFailed = mStagedSessionFailed;
-            info.setStagedSessionErrorCode(mStagedSessionErrorCode);
+            info.setStagedSessionErrorCode(mStagedSessionErrorCode, mStagedSessionErrorMessage);
         }
         return info;
     }
@@ -1971,17 +1977,21 @@
             mStagedSessionApplied = false;
             mStagedSessionFailed = false;
             mStagedSessionErrorCode = SessionInfo.NO_ERROR;
+            mStagedSessionErrorMessage = "";
         }
         mCallback.onStagedSessionChanged(this);
     }
 
     /** {@hide} */
-    void setStagedSessionFailed(@StagedSessionErrorCode int errorCode) {
+    void setStagedSessionFailed(@StagedSessionErrorCode int errorCode,
+                                String errorMessage) {
         synchronized (mLock) {
             mStagedSessionReady = false;
             mStagedSessionApplied = false;
             mStagedSessionFailed = true;
             mStagedSessionErrorCode = errorCode;
+            mStagedSessionErrorMessage = errorMessage;
+            Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage);
         }
         mCallback.onStagedSessionChanged(this);
     }
@@ -1993,6 +2003,7 @@
             mStagedSessionApplied = true;
             mStagedSessionFailed = false;
             mStagedSessionErrorCode = SessionInfo.NO_ERROR;
+            mStagedSessionErrorMessage = "";
         }
         mCallback.onStagedSessionChanged(this);
     }
@@ -2017,6 +2028,11 @@
         return mStagedSessionErrorCode;
     }
 
+    /** {@hide} */
+    String getStagedSessionErrorMessage() {
+        return mStagedSessionErrorMessage;
+    }
+
     private void destroyInternal() {
         synchronized (mLock) {
             mSealed = true;
@@ -2133,6 +2149,8 @@
             writeBooleanAttribute(out, ATTR_IS_FAILED, mStagedSessionFailed);
             writeBooleanAttribute(out, ATTR_IS_APPLIED, mStagedSessionApplied);
             writeIntAttribute(out, ATTR_STAGED_SESSION_ERROR_CODE, mStagedSessionErrorCode);
+            writeStringAttribute(out, ATTR_STAGED_SESSION_ERROR_MESSAGE,
+                    mStagedSessionErrorMessage);
             // TODO(patb,109941548): avoid writing to xml and instead infer / validate this after
             //                       we've read all sessions.
             writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId);
@@ -2253,6 +2271,8 @@
         final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED);
         final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE,
                 SessionInfo.NO_ERROR);
+        final String stagedSessionErrorMessage = readStringAttribute(in,
+                ATTR_STAGED_SESSION_ERROR_MESSAGE);
 
         if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) {
             throw new IllegalArgumentException("Can't restore staged session with invalid state.");
@@ -2296,7 +2316,7 @@
                 installerThread, stagingManager, sessionId, userId, installerPackageName,
                 installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed,
                 childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
-                stagedSessionErrorCode);
+                stagedSessionErrorCode, stagedSessionErrorMessage);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9100f6a..6eff815 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -449,8 +449,7 @@
 
     private static final long BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60);
 
-    private static final boolean PRECOMPILED_LAYOUT_ENABLED =
-            SystemProperties.getBoolean("view.precompiled_layout_enabled", false);
+    private static final String PRECOMPILE_LAYOUTS = "pm.precompile_layouts";
 
     private static final int RADIO_UID = Process.PHONE_UID;
     private static final int LOG_UID = Process.LOG_UID;
@@ -9119,7 +9118,7 @@
                 pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
             }
 
-            if (PRECOMPILED_LAYOUT_ENABLED) {
+            if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
                 mArtManagerService.compileLayouts(pkg);
             }
 
@@ -16211,7 +16210,7 @@
 
             if (performDexopt) {
                 // Compile the layout resources.
-                if (PRECOMPILED_LAYOUT_ENABLED) {
+                if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts");
                     mViewCompiler.compileLayouts(pkg);
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 563fd7f..84c8b60 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -690,6 +690,10 @@
         return result;
     }
 
+    public boolean hasShareTargets() {
+        return !mShareTargets.isEmpty();
+    }
+
     /**
      * Return the filenames (excluding path names) of icon bitmap files from this package.
      */
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index fdbaba2..792b34c 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2167,6 +2167,19 @@
         }
     }
 
+    @Override
+    public boolean hasShareTargets(String packageName, String packageToCheck,
+            @UserIdInt int userId) {
+        verifyCaller(packageName, userId);
+        enforceSystem();
+
+        synchronized (mLock) {
+            throwIfUserLockedL(userId);
+
+            return getPackageShortcutsLocked(packageToCheck, userId).hasShareTargets();
+        }
+    }
+
     @GuardedBy("mLock")
     private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
             @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 5311c2a..c4d27e5 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -153,6 +153,19 @@
         return success;
     }
 
+    private static boolean sendMarkStagedSessionReadyRequest(int sessionId) {
+        final IApexService apex = IApexService.Stub.asInterface(
+                ServiceManager.getService("apexservice"));
+        boolean success;
+        try {
+            success = apex.markStagedSessionReady(sessionId);
+        } catch (RemoteException re) {
+            Slog.e(TAG, "Unable to contact apexservice", re);
+            return false;
+        }
+        return success;
+    }
+
     private static boolean isApexSession(@NonNull PackageInstallerSession session) {
         return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
     }
@@ -166,6 +179,7 @@
         if (!session.isMultiPackage()
                 && isApexSession(session)) {
             success = submitSessionToApexService(session, null, apexInfoList);
+
         } else if (session.isMultiPackage()) {
             List<PackageInstallerSession> childSessions =
                     Arrays.stream(session.getChildSessionIds())
@@ -179,7 +193,13 @@
             } // else this is a staged multi-package session with no APEX files.
         }
 
-        if (success && (apexInfoList.apexInfos.length > 0)) {
+        if (!success) {
+            session.setStagedSessionFailed(
+                    SessionInfo.VERIFICATION_FAILED,
+                    "APEX staging failed, check logcat messages from apexd for more details.");
+        }
+
+        if (apexInfoList.apexInfos.length > 0) {
             // For APEXes, we validate the signature here before we mark the session as ready,
             // so we fail the session early if there is a signature mismatch. For APKs, the
             // signature verification will be done by the package manager at the point at which
@@ -190,16 +210,22 @@
             for (ApexInfo apexPackage : apexInfoList.apexInfos) {
                 if (!validateApexSignatureLocked(apexPackage.packagePath,
                         apexPackage.packageName)) {
-                    success = false;
-                    break;
+                    session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED,
+                            "APK-container signature verification failed for package "
+                                    + apexPackage.packageName + ". Signature of file "
+                                    + apexPackage.packagePath + " does not match the signature of "
+                                    + " the package already installed.");
+                    // TODO(b/118865310): abort the session on apexd.
+                    return;
                 }
             }
         }
 
-        if (success) {
-            session.setStagedSessionReady();
-        } else {
-            session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED);
+        session.setStagedSessionReady();
+        if (!sendMarkStagedSessionReadyRequest(session.sessionId)) {
+            session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED,
+                            "APEX staging failed, check logcat messages from apexd for more "
+                            + "details.");
         }
     }
 
@@ -217,13 +243,20 @@
             return;
         }
         if (apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown) {
-            session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED);
+            session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED,
+                    "APEX activation failed. Check logcat messages from apexd for "
+                                  + "more information.");
+        }
+        if (apexSessionInfo.isVerified) {
+            // Session has been previously submitted to apexd, but didn't complete all the
+            // pre-reboot verification, perhaps because the device rebooted in the meantime.
+            // Greedily re-trigger the pre-reboot verification.
+            mBgHandler.post(() -> preRebootVerification(session));
         }
         if (apexSessionInfo.isActivated) {
             session.setStagedSessionApplied();
             // TODO(b/118865310) if multi-package proceed with the installation of APKs.
         }
-        // TODO(b/118865310) if (apexSessionInfo.isVerified) { /* mark this as staged in apexd */ }
         // In every other case apexd will retry to apply the session at next boot.
     }
 
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 8d64b81..2455113 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -16,6 +16,10 @@
 
 package com.android.server.pm;
 
+import com.google.android.collect.Sets;
+
+import com.android.internal.util.Preconditions;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -38,10 +42,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.util.Preconditions;
-
-import com.google.android.collect.Sets;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
 
@@ -666,7 +666,6 @@
 
             case android.provider.Settings.Secure.ALWAYS_ON_VPN_APP:
             case android.provider.Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN:
-            case android.provider.Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST:
                 // Whitelist system uid (ConnectivityService) and root uid to change always-on vpn
                 final int appId = UserHandle.getAppId(callingUid);
                 if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 891c3da..3a754c4 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -2286,7 +2286,7 @@
      * Check if the display to which this stack is attached has
      * {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied.
      */
-    private boolean canShowWithInsecureKeyguard() {
+    boolean canShowWithInsecureKeyguard() {
         final ActivityDisplay activityDisplay = getDisplay();
         if (activityDisplay == null) {
             throw new IllegalStateException("Stack is not attached to any display, stackId="
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3a077b8..38580bc 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -827,6 +827,8 @@
                 final int flags = intent.getFlags();
                 Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
                 newIntent.setFlags(flags
+                        | FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
                         | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                 newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName);
                 newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e5a66cb..5fabde4 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4399,6 +4399,27 @@
         }
     }
 
+    @Override
+    public void registerRemoteAnimationsForDisplay(int displayId,
+            RemoteAnimationDefinition definition) {
+        mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
+                "registerRemoteAnimations");
+        definition.setCallingPid(Binder.getCallingPid());
+        synchronized (mGlobalLock) {
+            final ActivityDisplay display = mRootActivityContainer.getActivityDisplay(displayId);
+            if (display == null) {
+                Slog.e(TAG, "Couldn't find display with id: " + displayId);
+                return;
+            }
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                display.mDisplayContent.registerRemoteAnimations(definition);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
     @Override
     public void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index f3a363a..6dc73bb 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -29,6 +29,7 @@
 import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
 import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
 import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
 import static android.view.WindowManager.TRANSIT_TASK_OPEN;
@@ -1662,6 +1663,15 @@
                     "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS:"
                             + " anim=" + a + " transit=" + appTransitionToString(transit)
                             + " isEntrance=true" + " Callers=" + Debug.getCallers(3));
+        } else if (transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE) {
+            // In the absence of a specific adapter, we just want to keep everything stationary.
+            a = new AlphaAnimation(1.f, 1.f);
+            a.setDuration(WindowChangeAnimationSpec.ANIMATION_DURATION);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+                Slog.v(TAG, "applyAnimation:"
+                        + " anim=" + a + " transit=" + appTransitionToString(transit)
+                        + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
+            }
         } else {
             int animAttr = 0;
             switch (transit) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 49308b8..8f0a7c0 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -76,6 +76,7 @@
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
     private final WallpaperController mWallpaperControllerLocked;
+    private RemoteAnimationDefinition mRemoteAnimationDefinition = null;
 
     private final SparseIntArray mTempTransitionReasons = new SparseIntArray();
 
@@ -85,6 +86,10 @@
         mWallpaperControllerLocked = mDisplayContent.mWallpaperController;
     }
 
+    void registerRemoteAnimations(RemoteAnimationDefinition definition) {
+        mRemoteAnimationDefinition = definition;
+    }
+
     /**
      * Handle application transition for given display.
      */
@@ -216,6 +221,21 @@
         return mainWindow != null ? mainWindow.mAttrs : null;
     }
 
+    RemoteAnimationAdapter getRemoteAnimationOverride(AppWindowToken animLpToken, int transit,
+            ArraySet<Integer> activityTypes) {
+        final RemoteAnimationDefinition definition = animLpToken.getRemoteAnimationDefinition();
+        if (definition != null) {
+            final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes);
+            if (adapter != null) {
+                return adapter;
+            }
+        }
+        if (mRemoteAnimationDefinition == null) {
+            return null;
+        }
+        return mRemoteAnimationDefinition.getAdapter(transit, activityTypes);
+    }
+
     /**
      * Overrides the pending transition with the remote animation defined for the transition in the
      * set of defined remote animations in the app window token.
@@ -229,11 +249,8 @@
         if (animLpToken == null) {
             return;
         }
-        final RemoteAnimationDefinition definition = animLpToken.getRemoteAnimationDefinition();
-        if (definition == null) {
-            return;
-        }
-        final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes);
+        final RemoteAnimationAdapter adapter =
+                getRemoteAnimationOverride(animLpToken, transit, activityTypes);
         if (adapter != null) {
             animLpToken.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(
                     adapter);
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 65b36a0..a52f1af 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -105,6 +105,7 @@
 import android.view.DisplayInfo;
 import android.view.IApplicationToken;
 import android.view.InputApplicationHandle;
+import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -1621,6 +1622,17 @@
         t.reparent(getSurfaceControl(), mTransitChangeLeash);
         onAnimationLeashCreated(t, mTransitChangeLeash);
 
+        // Skip creating snapshot if this transition is controlled by a remote animator which
+        // doesn't need it.
+        ArraySet<Integer> activityTypes = new ArraySet<>();
+        activityTypes.add(getActivityType());
+        RemoteAnimationAdapter adapter =
+                mDisplayContent.mAppTransitionController.getRemoteAnimationOverride(
+                        this, TRANSIT_TASK_CHANGE_WINDOWING_MODE, activityTypes);
+        if (adapter != null && !adapter.getChangeNeedsSnapshot()) {
+            return;
+        }
+
         if (mThumbnail == null && getTask() != null) {
             final TaskSnapshotController snapshotCtrl = mWmService.mTaskSnapshotController;
             final ArraySet<Task> tasks = new ArraySet<>();
@@ -1639,6 +1651,11 @@
         return mTransitChangeLeash != null || isChangeTransition(mTransit);
     }
 
+    @VisibleForTesting
+    AppWindowThumbnail getThumbnail() {
+        return mThumbnail;
+    }
+
     @Override
     void checkAppWindowsReadyToShow() {
         if (allDrawn == mLastAllDrawn) {
@@ -2323,11 +2340,6 @@
         return transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE;
     }
 
-    private int getDefaultChangeTransitionDuration() {
-        return (int) (AppTransition.DEFAULT_APP_TRANSITION_DURATION
-                        * mWmService.getTransitionAnimationScaleLocked());
-    }
-
     boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter,
             boolean isVoiceInteraction) {
 
@@ -2349,7 +2361,7 @@
             AnimationAdapter thumbnailAdapter = null;
             getAnimationBounds(mTmpPoint, mTmpRect);
 
-            boolean isChanging = isChangeTransition(transit) && mThumbnail != null;
+            boolean isChanging = isChangeTransition(transit) && enter;
 
             // Delaying animation start isn't compatible with remote animations at all.
             if (getDisplayContent().mAppTransition.getRemoteAnimationController() != null
@@ -2361,18 +2373,20 @@
                 adapter = adapters.mAdapter;
                 thumbnailAdapter = adapters.mThumbnailAdapter;
             } else if (isChanging) {
-                int duration = getDefaultChangeTransitionDuration();
+                final float durationScale = mWmService.getTransitionAnimationScaleLocked();
                 mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
                 adapter = new LocalAnimationAdapter(
                         new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
-                                getDisplayContent().getDisplayInfo(), duration,
+                                getDisplayContent().getDisplayInfo(), durationScale,
                                 true /* isAppAnimation */, false /* isThumbnail */),
                         mWmService.mSurfaceAnimationRunner);
-                thumbnailAdapter = new LocalAnimationAdapter(
-                        new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
-                                getDisplayContent().getDisplayInfo(), duration,
-                                true /* isAppAnimation */, true /* isThumbnail */),
-                        mWmService.mSurfaceAnimationRunner);
+                if (mThumbnail != null) {
+                    thumbnailAdapter = new LocalAnimationAdapter(
+                            new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
+                                    getDisplayContent().getDisplayInfo(), durationScale,
+                                    true /* isAppAnimation */, true /* isThumbnail */),
+                            mWmService.mSurfaceAnimationRunner);
+                }
                 mTransit = transit;
                 mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
             } else {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 650d0be..ce3fb51 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -41,6 +41,8 @@
 import android.graphics.Rect;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
@@ -522,6 +524,11 @@
         mChangeListeners.remove(listener);
     }
 
+    @VisibleForTesting
+    boolean containsListener(ConfigurationContainerListener listener) {
+        return mChangeListeners.contains(listener);
+    }
+
     /**
      * Must be called when new parent for the container was set.
      */
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8f976e7..7a9ff52 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -162,6 +162,7 @@
 import android.view.InputDevice;
 import android.view.InsetsState.InternalInsetType;
 import android.view.MagnificationSpec;
+import android.view.RemoteAnimationDefinition;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -1056,11 +1057,6 @@
      */
     void setInsetProvider(@InternalInsetType int type, WindowState win,
             @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider) {
-        if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL) {
-            if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) {
-                return;
-            }
-        }
         mInsetsStateController.getSourceProvider(type).setWindow(win, frameProvider);
     }
 
@@ -1091,6 +1087,10 @@
         return mLastWindowForcedOrientation;
     }
 
+    void registerRemoteAnimations(RemoteAnimationDefinition definition) {
+        mAppTransitionController.registerRemoteAnimations(definition);
+    }
+
     /**
      * Temporarily pauses rotation changes until resumed.
      *
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index e49e4c0..e798203 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,7 +16,13 @@
 
 package com.android.server.wm;
 
+import static android.view.InsetsState.TYPE_IME;
+import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.TYPE_TOP_BAR;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.ViewRootImpl.sNewInsetsMode;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -59,6 +65,7 @@
      */
     private boolean mServerVisible;
 
+    private final boolean mControllable;
 
     InsetsSourceProvider(InsetsSource source, InsetsStateController stateController,
             DisplayContent displayContent) {
@@ -66,6 +73,15 @@
         mSource = source;
         mDisplayContent = displayContent;
         mStateController = stateController;
+
+        final int type = source.getType();
+        if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) {
+            mControllable = sNewInsetsMode == NEW_INSETS_MODE_FULL;
+        } else if (type == TYPE_IME) {
+            mControllable = sNewInsetsMode >= NEW_INSETS_MODE_IME;
+        } else {
+            mControllable = false;
+        }
     }
 
     InsetsSource getSource() {
@@ -73,6 +89,13 @@
     }
 
     /**
+     * @return Whether the current flag configuration allows to control this source.
+     */
+    boolean isControllable() {
+        return mControllable;
+    }
+
+    /**
      * Updates the window that currently backs this source.
      *
      * @param win The window that links to this source.
@@ -91,6 +114,9 @@
             mSource.setFrame(new Rect());
         } else {
             mWin.setInsetProvider(this);
+            if (mControllingWin != null) {
+                updateControlForTarget(mControllingWin, true /* force */);
+            }
         }
     }
 
@@ -113,8 +139,12 @@
                 && !mWin.mGivenInsetsPending);
     }
 
-    void updateControlForTarget(@Nullable WindowState target) {
-        if (target == mControllingWin) {
+    void updateControlForTarget(@Nullable WindowState target, boolean force) {
+        if (mWin == null) {
+            mControllingWin = target;
+            return;
+        }
+        if (target == mControllingWin && !force) {
             return;
         }
         if (target == null) {
@@ -161,7 +191,7 @@
     }
 
     boolean isClientVisible() {
-        return ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible;
+        return sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible;
     }
 
     private class ControlAdapter implements AnimationAdapter {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 32dbe96..bb0cbb1 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -163,18 +163,18 @@
     }
 
     private void onControlChanged(int type, @Nullable WindowState win) {
-        if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
-            return;
-        }
         final WindowState previous = mTypeWinControlMap.get(type);
         if (win == previous) {
             return;
         }
-        final InsetsSourceProvider controller = mControllers.get(type);
+        final InsetsSourceProvider controller = getSourceProvider(type);
         if (controller == null) {
             return;
         }
-        controller.updateControlForTarget(win);
+        if (!controller.isControllable()) {
+            return;
+        }
+        controller.updateControlForTarget(win, false /* force */);
         if (previous != null) {
             removeFromControlMaps(previous, type);
             mPendingControlChanged.add(previous);
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 177f244..b5be2ac5 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -91,9 +91,7 @@
      */
     boolean isKeyguardOrAodShowing(int displayId) {
         return (mKeyguardShowing || mAodShowing) && !mKeyguardGoingAway
-                && (displayId == DEFAULT_DISPLAY
-                        ? !isDisplayOccluded(DEFAULT_DISPLAY)
-                        : isShowingOnSecondaryDisplay(displayId));
+                && !isDisplayOccluded(displayId);
     }
 
     /**
@@ -101,10 +99,7 @@
      *         display, false otherwise
      */
     boolean isKeyguardShowing(int displayId) {
-        return mKeyguardShowing && !mKeyguardGoingAway
-                && (displayId == DEFAULT_DISPLAY
-                        ? !isDisplayOccluded(DEFAULT_DISPLAY)
-                        : isShowingOnSecondaryDisplay(displayId));
+        return mKeyguardShowing && !mKeyguardGoingAway && !isDisplayOccluded(displayId);
     }
 
     /**
@@ -152,14 +147,6 @@
         updateKeyguardSleepToken();
     }
 
-    private boolean isShowingOnSecondaryDisplay(int displayId) {
-        if (mSecondaryDisplayIdsShowing == null) return false;
-        for (int showingId : mSecondaryDisplayIdsShowing) {
-            if (displayId == showingId) return true;
-        }
-        return false;
-    }
-
     /**
      * Called when Keyguard is going away.
      *
@@ -473,8 +460,16 @@
                 if (stack.getTopDismissingKeyguardActivity() != null) {
                     mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
                 }
+                // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display.
+                if (mDisplayId != DEFAULT_DISPLAY) {
+                    mOccluded |= stack.canShowWithInsecureKeyguard()
+                            && controller.canDismissKeyguard();
+                }
             }
-            mOccluded |= controller.mWindowManager.isShowingDream();
+            // TODO(b/123372519): isShowingDream can only works on default display.
+            if (mDisplayId == DEFAULT_DISPLAY) {
+                mOccluded |= controller.mWindowManager.isShowingDream();
+            }
 
             // TODO(b/113840485): Handle app transition for individual display, and apply occluded
             // state change to secondary displays.
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index f760b39..5f95691 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -322,8 +322,10 @@
                 mStartBounds = new Rect(startBounds);
                 mTmpRect.set(startBounds);
                 mTmpRect.offsetTo(0, 0);
-                mThumbnailAdapter =
-                        new RemoteAnimationAdapterWrapper(this, new Point(0, 0), mTmpRect);
+                if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) {
+                    mThumbnailAdapter =
+                            new RemoteAnimationAdapterWrapper(this, new Point(0, 0), mTmpRect);
+                }
             } else {
                 mStartBounds = null;
             }
diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
index 7dd7c4f..775d5b2 100644
--- a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
@@ -53,13 +53,15 @@
     private Animation mAnimation;
     private final boolean mIsThumbnail;
 
+    static final int ANIMATION_DURATION = AppTransition.DEFAULT_APP_TRANSITION_DURATION;
+
     public WindowChangeAnimationSpec(Rect startBounds, Rect endBounds, DisplayInfo displayInfo,
-            long duration, boolean isAppAnimation, boolean isThumbnail) {
+            float durationScale, boolean isAppAnimation, boolean isThumbnail) {
         mStartBounds = new Rect(startBounds);
         mEndBounds = new Rect(endBounds);
         mIsAppAnimation = isAppAnimation;
         mIsThumbnail = isThumbnail;
-        createBoundsInterpolator(duration, displayInfo);
+        createBoundsInterpolator((int) (ANIMATION_DURATION * durationScale), displayInfo);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5eff7d8..e6581df 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2817,7 +2817,7 @@
 
     public boolean isShowingDream() {
         synchronized (mGlobalLock) {
-            // TODO: fix this when dream can be shown on non-default display.
+            // TODO(b/123372519): Fix this when dream can be shown on non-default display.
             return getDefaultDisplayContentLocked().getDisplayPolicy().isShowingDreamLw();
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index c38a974..07f26b4 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -52,6 +52,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.HeavyWeightSwitcherActivity;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.Watchdog;
@@ -330,6 +331,13 @@
         return mRequiredAbi;
     }
 
+    /** Returns ID of display overriding the configuration for this process, or
+     *  INVALID_DISPLAY if no display is overriding. */
+    @VisibleForTesting
+    int getDisplayId() {
+        return mDisplayId;
+    }
+
     public void setDebugging(boolean debugging) {
         mDebugging = debugging;
     }
@@ -761,15 +769,15 @@
         activityDisplay.registerConfigurationChangeListener(this);
     }
 
-    private void unregisterDisplayConfigurationListenerLocked() {
+    @VisibleForTesting
+    void unregisterDisplayConfigurationListenerLocked() {
         if (mDisplayId == INVALID_DISPLAY) {
             return;
         }
         final ActivityDisplay activityDisplay =
                 mAtm.mRootActivityContainer.getActivityDisplay(mDisplayId);
         if (activityDisplay != null) {
-            mAtm.mRootActivityContainer.getActivityDisplay(
-                    mDisplayId).unregisterConfigurationChangeListener(this);
+            activityDisplay.unregisterConfigurationChangeListener(this);
         }
         mDisplayId = INVALID_DISPLAY;
     }
@@ -867,6 +875,7 @@
 
     public boolean appNotResponding(String info, Runnable killAppCallback,
             Runnable serviceTimeoutCallback) {
+        Runnable targetRunnable = null;
         synchronized (mAtm.mGlobalLock) {
             if (mAtm.mController == null) {
                 return false;
@@ -877,18 +886,22 @@
                 int res = mAtm.mController.appNotResponding(mName, mPid, info);
                 if (res != 0) {
                     if (res < 0 && mPid != MY_PID) {
-                        killAppCallback.run();
+                        targetRunnable = killAppCallback;
                     } else {
-                        serviceTimeoutCallback.run();
+                        targetRunnable = serviceTimeoutCallback;
                     }
-                    return true;
                 }
             } catch (RemoteException e) {
                 mAtm.mController = null;
                 Watchdog.getInstance().setActivityController(null);
+                return false;
             }
-            return false;
         }
+        if (targetRunnable != null) {
+            targetRunnable.run();
+            return true;
+        }
+        return false;
     }
 
     public void onTopProcChanged() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ce5eb84..4f12010 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4367,6 +4367,12 @@
     }
 
     void startAnimation(Animation anim) {
+
+        // If we are an inset provider, all our animations are driven by the inset client.
+        if (mInsetProvider != null && mInsetProvider.isControllable()) {
+            return;
+        }
+
         final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
         anim.initialize(mWindowFrames.mFrame.width(), mWindowFrames.mFrame.height(),
                 displayInfo.appWidth, displayInfo.appHeight);
@@ -4380,6 +4386,12 @@
     }
 
     private void startMoveAnimation(int left, int top) {
+
+        // If we are an inset provider, all our animations are driven by the inset client.
+        if (mInsetProvider != null && mInsetProvider.isControllable()) {
+            return;
+        }
+
         if (DEBUG_ANIM) Slog.v(TAG, "Setting move animation on " + this);
         final Point oldPosition = new Point();
         final Point newPosition = new Point();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9088820..f79f9bc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -74,14 +74,20 @@
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
+
 import static android.provider.Telephony.Carriers.DPC_URI;
 import static android.provider.Telephony.Carriers.ENFORCE_KEY;
 import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI;
 
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
+        .PROVISIONING_ENTRY_POINT_ADB;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
+        .STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+
 import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER;
 import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER;
+
+
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -234,11 +240,11 @@
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.StatLogger;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
+import com.android.internal.util.StatLogger;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
@@ -1919,11 +1925,7 @@
         }
 
         AlarmManager getAlarmManager() {
-            return mContext.getSystemService(AlarmManager.class);
-        }
-
-        ConnectivityManager getConnectivityManager() {
-            return mContext.getSystemService(ConnectivityManager.class);
+            return (AlarmManager) mContext.getSystemService(AlarmManager.class);
         }
 
         IWindowManager getIWindowManager() {
@@ -6306,8 +6308,7 @@
      * @throws UnsupportedOperationException if the package does not support being set as always-on.
      */
     @Override
-    public boolean setAlwaysOnVpnPackage(ComponentName admin, String vpnPackage, boolean lockdown,
-            List<String> lockdownWhitelist)
+    public boolean setAlwaysOnVpnPackage(ComponentName admin, String vpnPackage, boolean lockdown)
             throws SecurityException {
         enforceProfileOrDeviceOwner(admin);
 
@@ -6315,23 +6316,11 @@
         final long token = mInjector.binderClearCallingIdentity();
         try {
             if (vpnPackage != null && !isPackageInstalledForUser(vpnPackage, userId)) {
-                Slog.w(LOG_TAG, "Non-existent VPN package specified: " + vpnPackage);
-                throw new ServiceSpecificException(
-                        DevicePolicyManager.ERROR_VPN_PACKAGE_NOT_FOUND, vpnPackage);
+                return false;
             }
-
-            if (vpnPackage != null && lockdown && lockdownWhitelist != null) {
-                for (String packageName : lockdownWhitelist) {
-                    if (!isPackageInstalledForUser(packageName, userId)) {
-                        Slog.w(LOG_TAG, "Non-existent package in VPN whitelist: " + packageName);
-                        throw new ServiceSpecificException(
-                                DevicePolicyManager.ERROR_VPN_PACKAGE_NOT_FOUND, packageName);
-                    }
-                }
-            }
-            // If some package is uninstalled after the check above, it will be ignored by CM.
-            if (!mInjector.getConnectivityManager().setAlwaysOnVpnPackageForUser(
-                    userId, vpnPackage, lockdown, lockdownWhitelist)) {
+            ConnectivityManager connectivityManager = (ConnectivityManager)
+                    mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+            if (!connectivityManager.setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdown)) {
                 throw new UnsupportedOperationException();
             }
             DevicePolicyEventLogger
@@ -6348,40 +6337,16 @@
     }
 
     @Override
-    public String getAlwaysOnVpnPackage(ComponentName admin) throws SecurityException {
-        enforceProfileOrDeviceOwner(admin);
-
-        final int userId = mInjector.userHandleGetCallingUserId();
-        final long token = mInjector.binderClearCallingIdentity();
-        try {
-            return mInjector.getConnectivityManager().getAlwaysOnVpnPackageForUser(userId);
-        } finally {
-            mInjector.binderRestoreCallingIdentity(token);
-        }
-    }
-
-    @Override
-    public boolean isAlwaysOnVpnLockdownEnabled(ComponentName admin) throws SecurityException {
-        enforceProfileOrDeviceOwner(admin);
-
-        final int userId = mInjector.userHandleGetCallingUserId();
-        final long token = mInjector.binderClearCallingIdentity();
-        try {
-            return mInjector.getConnectivityManager().isVpnLockdownEnabled(userId);
-        } finally {
-            mInjector.binderRestoreCallingIdentity(token);
-        }
-    }
-
-    @Override
-    public List<String> getAlwaysOnVpnLockdownWhitelist(ComponentName admin)
+    public String getAlwaysOnVpnPackage(ComponentName admin)
             throws SecurityException {
         enforceProfileOrDeviceOwner(admin);
 
         final int userId = mInjector.userHandleGetCallingUserId();
         final long token = mInjector.binderClearCallingIdentity();
-        try {
-            return mInjector.getConnectivityManager().getVpnLockdownWhitelist(userId);
+        try{
+            ConnectivityManager connectivityManager = (ConnectivityManager)
+                    mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+            return connectivityManager.getAlwaysOnVpnPackageForUser(userId);
         } finally {
             mInjector.binderRestoreCallingIdentity(token);
         }
@@ -6854,7 +6819,9 @@
         enforceDeviceOwner(who);
         long token = mInjector.binderClearCallingIdentity();
         try {
-            mInjector.getConnectivityManager().setGlobalProxy(proxyInfo);
+            ConnectivityManager connectivityManager = (ConnectivityManager)
+                    mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+            connectivityManager.setGlobalProxy(proxyInfo);
         } finally {
             mInjector.binderRestoreCallingIdentity(token);
         }
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
deleted file mode 100644
index cddb91f..0000000
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2019 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.dhcp;
-
-/**
- * TODO: remove this class after migrating clients.
- */
-public class DhcpClient {
-    public static final int CMD_PRE_DHCP_ACTION = 1003;
-    public static final int CMD_POST_DHCP_ACTION = 1004;
-    public static final int CMD_PRE_DHCP_ACTION_COMPLETE = 1006;
-
-    public static final int DHCP_SUCCESS = 1;
-    public static final int DHCP_FAILURE = 2;
-}
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
deleted file mode 100644
index a61c2ef..0000000
--- a/services/net/java/android/net/ip/IpClient.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * Copyright (C) 2019 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.ip;
-
-import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
-
-import android.content.Context;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.ProxyInfo;
-import android.net.StaticIpConfiguration;
-import android.net.apf.ApfCapabilities;
-import android.os.ConditionVariable;
-import android.os.INetworkManagementService;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * Proxy for the IpClient in the NetworkStack. To be removed once clients are migrated.
- * @hide
- */
-public class IpClient {
-    private static final String TAG = IpClient.class.getSimpleName();
-    private static final int IPCLIENT_BLOCK_TIMEOUT_MS = 10_000;
-
-    public static final String DUMP_ARG = "ipclient";
-
-    private final ConditionVariable mIpClientCv;
-    private final ConditionVariable mShutdownCv;
-
-    private volatile IIpClient mIpClient;
-
-    /**
-     * @see IpClientCallbacks
-     */
-    public static class Callback extends IpClientCallbacks {}
-
-    /**
-     * IpClient callback that allows clients to block until provisioning is complete.
-     */
-    public static class WaitForProvisioningCallback extends Callback {
-        private final ConditionVariable mCV = new ConditionVariable();
-        private LinkProperties mCallbackLinkProperties;
-
-        /**
-         * Block until either {@link #onProvisioningSuccess(LinkProperties)} or
-         * {@link #onProvisioningFailure(LinkProperties)} is called.
-         */
-        public LinkProperties waitForProvisioning() {
-            mCV.block();
-            return mCallbackLinkProperties;
-        }
-
-        @Override
-        public void onProvisioningSuccess(LinkProperties newLp) {
-            mCallbackLinkProperties = newLp;
-            mCV.open();
-        }
-
-        @Override
-        public void onProvisioningFailure(LinkProperties newLp) {
-            mCallbackLinkProperties = null;
-            mCV.open();
-        }
-    }
-
-    private class CallbackImpl extends IpClientUtil.IpClientCallbacksProxy {
-        /**
-         * Create a new IpClientCallbacksProxy.
-         */
-        CallbackImpl(IpClientCallbacks cb) {
-            super(cb);
-        }
-
-        @Override
-        public void onIpClientCreated(IIpClient ipClient) {
-            mIpClient = ipClient;
-            mIpClientCv.open();
-            super.onIpClientCreated(ipClient);
-        }
-
-        @Override
-        public void onQuit() {
-            mShutdownCv.open();
-            super.onQuit();
-        }
-    }
-
-    /**
-     * Create a new IpClient.
-     */
-    public IpClient(Context context, String iface, Callback callback) {
-        mIpClientCv = new ConditionVariable(false);
-        mShutdownCv = new ConditionVariable(false);
-
-        IpClientUtil.makeIpClient(context, iface, new CallbackImpl(callback));
-    }
-
-    /**
-     * @see IpClient#IpClient(Context, String, IpClient.Callback)
-     */
-    public IpClient(Context context, String iface, Callback callback,
-            INetworkManagementService nms) {
-        this(context, iface, callback);
-    }
-
-    private interface IpClientAction {
-        void useIpClient(IIpClient ipClient) throws RemoteException;
-    }
-
-    private void doWithIpClient(IpClientAction action) {
-        mIpClientCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
-        try {
-            action.useIpClient(mIpClient);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error communicating with IpClient", e);
-        }
-    }
-
-    /**
-     * Notify IpClient that PreDhcpAction is completed.
-     */
-    public void completedPreDhcpAction() {
-        doWithIpClient(c -> c.completedPreDhcpAction());
-    }
-
-    /**
-     * Confirm the provisioning configuration.
-     */
-    public void confirmConfiguration() {
-        doWithIpClient(c -> c.confirmConfiguration());
-    }
-
-    /**
-     * Notify IpClient that packet filter read is complete.
-     */
-    public void readPacketFilterComplete(byte[] data) {
-        doWithIpClient(c -> c.readPacketFilterComplete(data));
-    }
-
-    /**
-     * Shutdown the IpClient altogether.
-     */
-    public void shutdown() {
-        doWithIpClient(c -> c.shutdown());
-    }
-
-    /**
-     * Start the IpClient provisioning.
-     */
-    public void startProvisioning(ProvisioningConfiguration config) {
-        doWithIpClient(c -> c.startProvisioning(config.toStableParcelable()));
-    }
-
-    /**
-     * Stop the IpClient.
-     */
-    public void stop() {
-        doWithIpClient(c -> c.stop());
-    }
-
-    /**
-     * Set the IpClient TCP buffer sizes.
-     */
-    public void setTcpBufferSizes(String tcpBufferSizes) {
-        doWithIpClient(c -> c.setTcpBufferSizes(tcpBufferSizes));
-    }
-
-    /**
-     * Set the IpClient HTTP proxy.
-     */
-    public void setHttpProxy(ProxyInfo proxyInfo) {
-        doWithIpClient(c -> c.setHttpProxy(toStableParcelable(proxyInfo)));
-    }
-
-    /**
-     * Set the IpClient multicast filter.
-     */
-    public void setMulticastFilter(boolean enabled) {
-        doWithIpClient(c -> c.setMulticastFilter(enabled));
-    }
-
-    /**
-     * Dump IpClient logs.
-     */
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        doWithIpClient(c -> IpClientUtil.dumpIpClient(c, fd, pw, args));
-    }
-
-    /**
-     * Block until IpClient shutdown.
-     */
-    public void awaitShutdown() {
-        mShutdownCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
-    }
-
-    /**
-     * Create a new ProvisioningConfiguration.
-     */
-    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
-        return new ProvisioningConfiguration.Builder();
-    }
-
-    /**
-     * TODO: remove after migrating clients to use the shared configuration class directly.
-     * @see android.net.shared.ProvisioningConfiguration
-     */
-    public static class ProvisioningConfiguration
-            extends android.net.shared.ProvisioningConfiguration {
-        public ProvisioningConfiguration(android.net.shared.ProvisioningConfiguration other) {
-            super(other);
-        }
-
-        /**
-         * @see android.net.shared.ProvisioningConfiguration.Builder
-         */
-        public static class Builder extends android.net.shared.ProvisioningConfiguration.Builder {
-            // Override all methods to have a return type matching this Builder
-            @Override
-            public Builder withoutIPv4() {
-                super.withoutIPv4();
-                return this;
-            }
-
-            @Override
-            public Builder withoutIPv6() {
-                super.withoutIPv6();
-                return this;
-            }
-
-            @Override
-            public Builder withoutMultinetworkPolicyTracker() {
-                super.withoutMultinetworkPolicyTracker();
-                return this;
-            }
-
-            @Override
-            public Builder withoutIpReachabilityMonitor() {
-                super.withoutIpReachabilityMonitor();
-                return this;
-            }
-
-            @Override
-            public Builder withPreDhcpAction() {
-                super.withPreDhcpAction();
-                return this;
-            }
-
-            @Override
-            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
-                super.withPreDhcpAction(dhcpActionTimeoutMs);
-                return this;
-            }
-
-            @Override
-            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
-                super.withStaticConfiguration(staticConfig);
-                return this;
-            }
-
-            @Override
-            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
-                super.withApfCapabilities(apfCapabilities);
-                return this;
-            }
-
-            @Override
-            public Builder withProvisioningTimeoutMs(int timeoutMs) {
-                super.withProvisioningTimeoutMs(timeoutMs);
-                return this;
-            }
-
-            @Override
-            public Builder withRandomMacAddress() {
-                super.withRandomMacAddress();
-                return this;
-            }
-
-            @Override
-            public Builder withStableMacAddress() {
-                super.withStableMacAddress();
-                return this;
-            }
-
-            @Override
-            public Builder withNetwork(Network network) {
-                super.withNetwork(network);
-                return this;
-            }
-
-            @Override
-            public Builder withDisplayName(String displayName) {
-                super.withDisplayName(displayName);
-                return this;
-            }
-
-            @Override
-            public ProvisioningConfiguration build() {
-                return new ProvisioningConfiguration(mConfig);
-            }
-        }
-    }
-}
diff --git a/services/net/java/android/net/shared/NetworkObserverRegistry.java b/services/net/java/android/net/shared/NetworkObserverRegistry.java
new file mode 100644
index 0000000..36945f5
--- /dev/null
+++ b/services/net/java/android/net/shared/NetworkObserverRegistry.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2019 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.shared;
+
+import static android.Manifest.permission.NETWORK_STACK;
+
+import android.content.Context;
+import android.net.INetd;
+import android.net.INetdUnsolicitedEventListener;
+import android.net.INetworkManagementEventObserver;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.RouteInfo;
+import android.os.Handler;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+/**
+ * A class for reporting network events to clients.
+ *
+ * Implements INetdUnsolicitedEventListener and registers with netd, and relays those events to
+ * all INetworkManagementEventObserver objects that have registered with it.
+ *
+ * TODO: Make the notifyXyz methods protected once subclasses (e.g., the NetworkManagementService
+ * subclass) no longer call them directly.
+ *
+ * TODO: change from RemoteCallbackList to direct in-process callbacks.
+ */
+public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub {
+
+    private final Context mContext;
+    private final Handler mDaemonHandler;
+    private static final String TAG = "NetworkObserverRegistry";
+
+    /**
+     * Constructs a new instance and registers it with netd.
+     * This method should only be called once since netd will reject multiple registrations from
+     * the same process.
+     */
+    public NetworkObserverRegistry(Context context, Handler handler, INetd netd)
+            throws RemoteException {
+        mContext = context;
+        mDaemonHandler = handler;
+        netd.registerUnsolicitedEventListener(this);
+    }
+
+    private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
+            new RemoteCallbackList<>();
+
+    /**
+     * Registers the specified observer and start sending callbacks to it.
+     * This method may be called on any thread.
+     */
+    public void registerObserver(INetworkManagementEventObserver observer) {
+        mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
+        mObservers.register(observer);
+    }
+
+    /**
+     * Unregisters the specified observer and stop sending callbacks to it.
+     * This method may be called on any thread.
+     */
+    public void unregisterObserver(INetworkManagementEventObserver observer) {
+        mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
+        mObservers.unregister(observer);
+    }
+
+    @FunctionalInterface
+    private interface NetworkManagementEventCallback {
+        void sendCallback(INetworkManagementEventObserver o) throws RemoteException;
+    }
+
+    private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) {
+        final int length = mObservers.beginBroadcast();
+        try {
+            for (int i = 0; i < length; i++) {
+                try {
+                    eventCallback.sendCallback(mObservers.getBroadcastItem(i));
+                } catch (RemoteException | RuntimeException e) {
+                }
+            }
+        } finally {
+            mObservers.finishBroadcast();
+        }
+    }
+
+    /**
+     * Notify our observers of a change in the data activity state of the interface
+     */
+    public void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos,
+            int uid, boolean fromRadio) {
+        invokeForAllObservers(o -> o.interfaceClassDataActivityChanged(
+                Integer.toString(type), isActive, tsNanos));
+    }
+
+    @Override
+    public void onInterfaceClassActivityChanged(boolean isActive,
+            int label, long timestamp, int uid) throws RemoteException {
+        final long timestampNanos;
+        if (timestamp <= 0) {
+            timestampNanos = SystemClock.elapsedRealtimeNanos();
+        } else {
+            timestampNanos = timestamp;
+        }
+        mDaemonHandler.post(() -> notifyInterfaceClassActivity(label, isActive,
+                timestampNanos, uid, false));
+    }
+
+    /**
+     * Notify our observers of a limit reached.
+     */
+    @Override
+    public void onQuotaLimitReached(String alertName, String ifName) throws RemoteException {
+        mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName));
+    }
+
+    /**
+     * Notify our observers of a limit reached.
+     */
+    public void notifyLimitReached(String limitName, String iface) {
+        invokeForAllObservers(o -> o.limitReached(limitName, iface));
+    }
+
+    @Override
+    public void onInterfaceDnsServerInfo(String ifName,
+            long lifetime, String[] servers) throws RemoteException {
+        mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers));
+    }
+
+    /**
+     * Notify our observers of DNS server information received.
+     */
+    public void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
+        invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses));
+    }
+
+    @Override
+    public void onInterfaceAddressUpdated(String addr,
+            String ifName, int flags, int scope) throws RemoteException {
+        final LinkAddress address = new LinkAddress(addr, flags, scope);
+        mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address));
+    }
+
+    /**
+     * Notify our observers of a new or updated interface address.
+     */
+    public void notifyAddressUpdated(String iface, LinkAddress address) {
+        invokeForAllObservers(o -> o.addressUpdated(iface, address));
+    }
+
+    @Override
+    public void onInterfaceAddressRemoved(String addr,
+            String ifName, int flags, int scope) throws RemoteException {
+        final LinkAddress address = new LinkAddress(addr, flags, scope);
+        mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address));
+    }
+
+    /**
+     * Notify our observers of a deleted interface address.
+     */
+    public void notifyAddressRemoved(String iface, LinkAddress address) {
+        invokeForAllObservers(o -> o.addressRemoved(iface, address));
+    }
+
+
+    @Override
+    public void onInterfaceAdded(String ifName) throws RemoteException {
+        mDaemonHandler.post(() -> notifyInterfaceAdded(ifName));
+    }
+
+    /**
+     * Notify our observers of an interface addition.
+     */
+    public void notifyInterfaceAdded(String iface) {
+        invokeForAllObservers(o -> o.interfaceAdded(iface));
+    }
+
+    @Override
+    public void onInterfaceRemoved(String ifName) throws RemoteException {
+        mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName));
+    }
+
+    /**
+     * Notify our observers of an interface removal.
+     */
+    public void notifyInterfaceRemoved(String iface) {
+        invokeForAllObservers(o -> o.interfaceRemoved(iface));
+    }
+
+    @Override
+    public void onInterfaceChanged(String ifName, boolean up) throws RemoteException {
+        mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up));
+    }
+
+    /**
+     * Notify our observers of an interface status change
+     */
+    public void notifyInterfaceStatusChanged(String iface, boolean up) {
+        invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up));
+    }
+
+    @Override
+    public void onInterfaceLinkStateChanged(String ifName, boolean up) throws RemoteException {
+        mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up));
+    }
+
+    /**
+     * Notify our observers of an interface link state change
+     * (typically, an Ethernet cable has been plugged-in or unplugged).
+     */
+    public void notifyInterfaceLinkStateChanged(String iface, boolean up) {
+        invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up));
+    }
+
+    @Override
+    public void onRouteChanged(boolean updated,
+            String route, String gateway, String ifName) throws RemoteException {
+        final RouteInfo processRoute = new RouteInfo(new IpPrefix(route),
+                ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway),
+                ifName);
+        mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute));
+    }
+
+    /**
+     * Notify our observers of a route change.
+     */
+    public void notifyRouteChange(boolean updated, RouteInfo route) {
+        if (updated) {
+            invokeForAllObservers(o -> o.routeUpdated(route));
+        } else {
+            invokeForAllObservers(o -> o.routeRemoved(route));
+        }
+    }
+
+    @Override
+    public void onStrictCleartextDetected(int uid, String hex) throws RemoteException {
+        // Don't do anything here because this is not a method of INetworkManagementEventObserver.
+        // Only the NMS subclass will implement this.
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
index d91ce39..de7d77d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
@@ -158,7 +158,7 @@
             boolean detectShortcutTrigger) {
         MagnificationGestureHandler h = new MagnificationGestureHandler(
                 mContext, mMagnificationController,
-                detectTripleTap, detectShortcutTrigger);
+                detectTripleTap, detectShortcutTrigger, DISPLAY_0);
         mHandler = new TestHandler(h.mDetectingState, mClock) {
             @Override
             protected String messageToString(Message m) {
diff --git a/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java
index ca39a7f..0b01657 100644
--- a/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java
@@ -918,15 +918,14 @@
         }
 
         setAccessibilityColorInversion(true);
-        setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
 
         startService();
-        assertAccessibilityTransformActivated(true /* activated */ );
-        assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
-        if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) {
-            assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED);
-        } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) {
-            assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC);
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) {
+            assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
+        } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) {
+            assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC);
         }
     }
 
@@ -937,15 +936,14 @@
         }
 
         setAccessibilityColorCorrection(true);
-        setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
 
         startService();
-        assertAccessibilityTransformActivated(true /* activated */ );
-        assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
-        if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) {
-            assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED);
-        } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) {
-            assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC);
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) {
+            assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
+        } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) {
+            assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC);
         }
     }
 
@@ -957,15 +955,14 @@
 
         setAccessibilityColorCorrection(true);
         setAccessibilityColorInversion(true);
-        setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
 
         startService();
-        assertAccessibilityTransformActivated(true /* activated */ );
-        assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
-        if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) {
-            assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED);
-        } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) {
-            assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC);
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) {
+            assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
+        } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) {
+            assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC);
         }
     }
 
@@ -977,12 +974,11 @@
 
         setAccessibilityColorCorrection(false);
         setAccessibilityColorInversion(false);
-        setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
 
         startService();
-        assertAccessibilityTransformActivated(false /* activated */ );
-        assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
-        assertActiveColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        assertActiveColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
     }
 
     /**
@@ -1098,17 +1094,6 @@
     }
 
     /**
-     * Convenience method for asserting that Accessibility color transform is detected.
-     *
-     * @param state {@code true} if any Accessibility transform should be activated
-     */
-    private void assertAccessibilityTransformActivated(boolean state) {
-        assertWithMessage("Unexpected Accessibility color transform state")
-                .that(mColorDisplayController.getAccessibilityTransformActivated())
-                .isEqualTo(state);
-    }
-
-    /**
      * Convenience method for asserting that the active color mode matches expectation.
      *
      * @param mode the expected active color mode.
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index 73e9613..742ae41 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -173,7 +173,8 @@
                 /* isReady */ staged ? true : false,
                 /* isFailed */ false,
                 /* isApplied */false,
-                /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED);
+                /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED,
+                /* stagedSessionErrorMessage */ "some error");
     }
 
     private void dumpSession(PackageInstallerSession session) {
@@ -295,6 +296,8 @@
         assertEquals(expected.isStagedSessionFailed(), actual.isStagedSessionFailed());
         assertEquals(expected.isStagedSessionReady(), actual.isStagedSessionReady());
         assertEquals(expected.getStagedSessionErrorCode(), actual.getStagedSessionErrorCode());
+        assertEquals(expected.getStagedSessionErrorMessage(),
+                actual.getStagedSessionErrorMessage());
         assertEquals(expected.isPrepared(), actual.isPrepared());
         assertEquals(expected.isSealed(), actual.isSealed());
         assertEquals(expected.isMultiPackage(), actual.isMultiPackage());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
new file mode 100644
index 0000000..19ace3c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 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 static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.os.IBinder;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationDefinition;
+import android.view.RemoteAnimationTarget;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for change transitions
+ *
+ * Build/Install/Run:
+ *  atest WmTests:AppChangeTransitionTests
+ */
+@FlakyTest(detail = "Promote when shown to be stable.")
+@SmallTest
+public class AppChangeTransitionTests extends WindowTestsBase {
+
+    private TaskStack mStack;
+    private Task mTask;
+    private WindowTestUtils.TestAppWindowToken mToken;
+
+    @Before
+    public void setUp() throws Exception {
+        mStack = createTaskStackOnDisplay(mDisplayContent);
+        mTask = createTaskInStack(mStack, 0 /* userId */);
+        mToken = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+        mToken.mSkipOnParentSet = false;
+
+        mTask.addChild(mToken, 0);
+    }
+
+    class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
+        @Override
+        public void onAnimationStart(RemoteAnimationTarget[] apps,
+                IRemoteAnimationFinishedCallback finishedCallback) {
+            for (RemoteAnimationTarget target : apps) {
+                assertNotNull(target.startBounds);
+            }
+            try {
+                finishedCallback.onAnimationFinished();
+            } catch (Exception e) {
+                throw new RuntimeException("Something went wrong");
+            }
+        }
+
+        @Override
+        public void onAnimationCancelled() {
+        }
+
+        @Override
+        public IBinder asBinder() {
+            return null;
+        }
+    }
+
+    @Test
+    public void testModeChangeRemoteAnimatorNoSnapshot() {
+        RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+        RemoteAnimationAdapter adapter =
+                new RemoteAnimationAdapter(new TestRemoteAnimationRunner(), 10, 1, false);
+        definition.addRemoteAnimation(TRANSIT_TASK_CHANGE_WINDOWING_MODE, adapter);
+        mDisplayContent.registerRemoteAnimations(definition);
+
+        mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertEquals(1, mDisplayContent.mChangingApps.size());
+        assertNull(mToken.getThumbnail());
+
+        waitUntilHandlersIdle();
+        mToken.removeImmediately();
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 3740786..a498a1a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -94,9 +94,9 @@
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar, null);
-        mProvider.updateControlForTarget(target);
+        mProvider.updateControlForTarget(target, false /* force */);
         assertNotNull(mProvider.getControl());
-        mProvider.updateControlForTarget(null);
+        mProvider.updateControlForTarget(null, false /* force */);
         assertNull(mProvider.getControl());
     }
 
@@ -106,7 +106,7 @@
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar, null);
-        mProvider.updateControlForTarget(target);
+        mProvider.updateControlForTarget(target, false /* force */);
         InsetsState state = new InsetsState();
         state.getSource(TYPE_TOP_BAR).setVisible(false);
         mProvider.onInsetsModified(target, state.getSource(TYPE_TOP_BAR));
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 9478be9..b867799 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -77,7 +77,7 @@
         MockitoAnnotations.initMocks(this);
 
         when(mMockRunner.asBinder()).thenReturn(new Binder());
-        mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50);
+        mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50, true /* changeNeedsSnapshot */);
         mAdapter.setCallingPid(123);
         mWm.mH.runWithScissors(() -> mHandler = new TestHandler(null, mClock), 0);
         mController = new RemoteAnimationController(mWm, mAdapter, mHandler);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
new file mode 100644
index 0000000..a7c84a1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 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 static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.server.wm.ActivityDisplay.POSITION_TOP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import android.content.pm.ApplicationInfo;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+
+/**
+ * Tests for the {@link WindowProcessController} class.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:WindowProcessControllerTests
+ */
+@Presubmit
+public class WindowProcessControllerTests extends ActivityTestsBase {
+
+    @Test
+    public void testDisplayConfigurationListener() {
+        final WindowProcessController wpc = new WindowProcessController(
+                        mService, mock(ApplicationInfo.class), null, 0, -1, null, null);
+        //By default, the process should not listen to any display.
+        assertEquals(INVALID_DISPLAY, wpc.getDisplayId());
+
+        // Register to display 1 as a listener.
+        TestActivityDisplay testActivityDisplay1 = createTestActivityDisplayInContainer();
+        wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1);
+        assertTrue(testActivityDisplay1.containsListener(wpc));
+        assertEquals(testActivityDisplay1.mDisplayId, wpc.getDisplayId());
+
+        // Move to display 2.
+        TestActivityDisplay testActivityDisplay2 = createTestActivityDisplayInContainer();
+        wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay2);
+        assertFalse(testActivityDisplay1.containsListener(wpc));
+        assertTrue(testActivityDisplay2.containsListener(wpc));
+        assertEquals(testActivityDisplay2.mDisplayId, wpc.getDisplayId());
+
+        // Null ActivityDisplay will not change anything.
+        wpc.registerDisplayConfigurationListenerLocked(null);
+        assertTrue(testActivityDisplay2.containsListener(wpc));
+        assertEquals(testActivityDisplay2.mDisplayId, wpc.getDisplayId());
+
+        // Unregister listener will remove the wpc from registered displays.
+        wpc.unregisterDisplayConfigurationListenerLocked();
+        assertFalse(testActivityDisplay1.containsListener(wpc));
+        assertFalse(testActivityDisplay2.containsListener(wpc));
+        assertEquals(INVALID_DISPLAY, wpc.getDisplayId());
+
+        // Unregistration still work even if the display was removed.
+        wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1);
+        assertEquals(testActivityDisplay1.mDisplayId, wpc.getDisplayId());
+        mRootActivityContainer.removeChild(testActivityDisplay1);
+        wpc.unregisterDisplayConfigurationListenerLocked();
+        assertEquals(INVALID_DISPLAY, wpc.getDisplayId());
+    }
+
+    private TestActivityDisplay createTestActivityDisplayInContainer() {
+        final TestActivityDisplay testActivityDisplay = createNewActivityDisplay();
+        mRootActivityContainer.addChild(testActivityDisplay, POSITION_TOP);
+        return testActivityDisplay;
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index 44e998b..2263cf3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -152,6 +152,7 @@
     public static class TestAppWindowToken extends AppWindowToken {
         boolean mOnTop = false;
         private Transaction mPendingTransactionOverride;
+        boolean mSkipOnParentSet = true;
 
         private TestAppWindowToken(DisplayContent dc) {
             super(dc.mWmService, new IApplicationToken.Stub() {
@@ -200,7 +201,9 @@
 
         @Override
         void onParentSet() {
-            // Do nothing.
+            if (!mSkipOnParentSet) {
+                super.onParentSet();
+            }
         }
 
         @Override
diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
index c2e4581..acf9946 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
@@ -24,9 +24,8 @@
 import android.content.pm.ActivityInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
 
-// TODO: fix this. either move this class into system server or add a dependency on
-// these wm classes to libiorap-java and libiorap-java-tests (somehow).
 import com.android.server.wm.ActivityMetricsLaunchObserver;
 import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
 import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
@@ -113,12 +112,12 @@
         @Override
         protected void writeToParcelImpl(Parcel p, int flags) {
             super.writeToParcelImpl(p, flags);
-            intent.writeToParcel(p, flags);
+            IntentProtoParcelable.write(p, intent, flags);
         }
 
         IntentStarted(Parcel p) {
             super(p);
-            intent = Intent.CREATOR.createFromParcel(p);
+            intent = IntentProtoParcelable.create(p);
         }
     }
 
@@ -232,8 +231,7 @@
     }
 
      public static class ActivityLaunchCancelled extends AppLaunchEvent {
-        public final @Nullable
-        @ActivityRecordProto byte[] activityRecordSnapshot;
+        public final @Nullable @ActivityRecordProto byte[] activityRecordSnapshot;
 
         public ActivityLaunchCancelled(@SequenceId long sequenceId,
                 @Nullable @ActivityRecordProto byte[] snapshot) {
@@ -352,7 +350,6 @@
             ActivityLaunchCancelled.class,
     };
 
-    // TODO: move to @ActivityRecordProto byte[] once we have unit tests.
     public static class ActivityRecordProtoParcelable {
         public static void write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot,
                 int flags) {
@@ -365,4 +362,31 @@
             return data;
         }
     }
+
+    public static class IntentProtoParcelable {
+        private static final int INTENT_PROTO_CHUNK_SIZE = 1024;
+
+        public static void write(Parcel p, @NonNull Intent intent, int flags) {
+            // There does not appear to be a way to 'reset' a ProtoOutputBuffer stream,
+            // so create a new one every time.
+            final ProtoOutputStream protoOutputStream =
+                    new ProtoOutputStream(INTENT_PROTO_CHUNK_SIZE);
+            // Write this data out as the top-most IntentProto (i.e. it is not a sub-object).
+            intent.writeToProto(protoOutputStream);
+            final byte[] bytes = protoOutputStream.getBytes();
+
+            p.writeByteArray(bytes);
+        }
+
+        // TODO: Should be mockable for testing?
+        // We cannot deserialize in the platform because we don't have a 'readFromProto'
+        // code.
+        public static @NonNull Intent create(Parcel p) {
+            // This will "read" the correct amount of data, but then we discard it.
+            byte[] data = p.createByteArray();
+
+            // Never called by real code in a platform, this binder API is implemented only in C++.
+            return new Intent("<cannot deserialize IntentProto>");
+        }
+    }
 }
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index 7fcad36..9a30b35 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -24,12 +24,16 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.Handler;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.IoThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.wm.ActivityMetricsLaunchObserver;
@@ -43,10 +47,20 @@
  */
 public class IorapForwardingService extends SystemService {
 
-    public static final boolean DEBUG = true; // TODO: read from a getprop?
     public static final String TAG = "IorapForwardingService";
+    /** $> adb shell 'setprop log.tag.IorapdForwardingService VERBOSE' */
+    public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    /** $> adb shell 'setprop iorapd.enable true' */
+    private static boolean IS_ENABLED = SystemProperties.getBoolean("iorapd.enable", true);
+    /** $> adb shell 'setprop iorapd.forwarding_service.wtf_crash true' */
+    private static boolean WTF_CRASH = SystemProperties.getBoolean(
+            "iorapd.forwarding_service.wtf_crash", false);
 
     private IIorap mIorapRemote;
+    private final Object mLock = new Object();
+    /** Handle onBinderDeath by periodically trying to reconnect. */
+    private final Handler mHandler =
+            new BinderConnectionHandler(IoThread.getHandler().getLooper());
 
     /**
      * Initializes the system service.
@@ -58,7 +72,7 @@
      * @param context The system server context.
      */
     public IorapForwardingService(Context context) {
-       super(context);
+        super(context);
     }
 
     //<editor-fold desc="Providers">
@@ -78,12 +92,40 @@
 
     @VisibleForTesting
     protected IIorap provideIorapRemote() {
+        IIorap iorap;
         try {
-            return IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd"));
+            iorap = IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd"));
         } catch (ServiceManager.ServiceNotFoundException e) {
-            // TODO: how do we handle service being missing?
-            throw new AssertionError(e);
+            handleRemoteError(e);
+            return null;
         }
+
+        try {
+            iorap.asBinder().linkToDeath(provideDeathRecipient(), /*flags*/0);
+        } catch (RemoteException e) {
+            handleRemoteError(e);
+            return null;
+        }
+
+        return iorap;
+    }
+
+    @VisibleForTesting
+    protected DeathRecipient provideDeathRecipient() {
+        return new DeathRecipient() {
+            @Override
+            public void binderDied() {
+                Log.w(TAG, "iorapd has died");
+                retryConnectToRemoteAndConfigure(/*attempts*/0);
+            }
+        };
+    }
+
+    @VisibleForTesting
+    protected boolean isIorapEnabled() {
+        // Same as the property in iorapd.rc -- disabling this will mean the 'iorapd' binder process
+        // never comes up, so all binder connections will fail indefinitely.
+        return IS_ENABLED;
     }
 
     //</editor-fold>
@@ -94,15 +136,128 @@
             Log.v(TAG, "onStart");
         }
 
+        retryConnectToRemoteAndConfigure(/*attempts*/0);
+    }
+
+    private class BinderConnectionHandler extends Handler {
+        public BinderConnectionHandler(android.os.Looper looper) {
+            super(looper);
+        }
+
+        public static final int MESSAGE_BINDER_CONNECT = 0;
+
+        private int mAttempts = 0;
+
+        @Override
+        public void handleMessage(android.os.Message message) {
+           switch (message.what) {
+               case MESSAGE_BINDER_CONNECT:
+                   if (!retryConnectToRemoteAndConfigure(mAttempts)) {
+                       mAttempts++;
+                   } else {
+                       mAttempts = 0;
+                   }
+                   break;
+               default:
+                   throw new AssertionError("Unknown message: " + message.toString());
+           }
+        }
+    }
+
+    /**
+     * Handle iorapd shutdowns and crashes, by attempting to reconnect
+     * until the service is reached again.
+     *
+     * <p>The first connection attempt is synchronous,
+     * subsequent attempts are done by posting delayed tasks to the IoThread.</p>
+     *
+     * @return true if connection succeeded now, or false if it failed now [and needs to requeue].
+     */
+    private boolean retryConnectToRemoteAndConfigure(int attempts) {
+        final int sleepTime = 1000;  // ms
+
+        if (DEBUG) {
+            Log.v(TAG, "retryConnectToRemoteAndConfigure - attempt #" + attempts);
+        }
+
+        if (connectToRemoteAndConfigure()) {
+            return true;
+        }
+
+        // Either 'iorapd' is stuck in a crash loop (ouch!!) or we manually
+        // called 'adb shell stop iorapd' , which means this would loop until it comes back
+        // up.
+        //
+        // TODO: it would be good to get nodified of 'adb shell stop iorapd' to avoid
+        // printing this warning.
+        Log.w(TAG, "Failed to connect to iorapd, is it down? Delay for " + sleepTime);
+
+        // Use a handler instead of Thread#sleep to avoid backing up the binder thread
+        // when this is called from the death recipient callback.
+        mHandler.sendMessageDelayed(
+                mHandler.obtainMessage(BinderConnectionHandler.MESSAGE_BINDER_CONNECT),
+                sleepTime);
+
+        return false;
+
+        // Log.e(TAG, "Can't connect to iorapd - giving up after " + attempts + " attempts");
+    }
+
+    private boolean connectToRemoteAndConfigure() {
+        synchronized (mLock) {
+            // Synchronize against any concurrent calls to this via the DeathRecipient.
+            return connectToRemoteAndConfigureLocked();
+        }
+    }
+
+    private boolean connectToRemoteAndConfigureLocked() {
+        if (!isIorapEnabled()) {
+            if (DEBUG) {
+                Log.v(TAG, "connectToRemoteAndConfigure - iorapd is disabled, skip rest of work");
+            }
+            // When we see that iorapd is disabled (when system server comes up),
+            // it stays disabled permanently until the next system server reset.
+
+            // TODO: consider listening to property changes as a callback, then we can
+            // be more dynamic about handling enable/disable.
+            return true;
+        }
+
         // Connect to the native binder service.
         mIorapRemote = provideIorapRemote();
+        if (mIorapRemote == null) {
+            Log.e(TAG, "connectToRemoteAndConfigure - null iorap remote. check for Log.wtf?");
+            return false;
+        }
         invokeRemote( () -> mIorapRemote.setTaskListener(new RemoteTaskListener()) );
+        registerInProcessListenersLocked();
+
+        return true;
+    }
+
+    private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver();
+    private boolean mRegisteredListeners = false;
+
+    private void registerInProcessListenersLocked() {
+        if (mRegisteredListeners) {
+            // Listeners are registered only once (idempotent operation).
+            //
+            // Today listeners are tolerant of the remote side going away
+            // by handling remote errors.
+            //
+            // We could try to 'unregister' the listener when we get a binder disconnect,
+            // but we'd still have to handle the case of encountering synchronous errors so
+            // it really wouldn't be a win (other than having less log spew).
+            return;
+        }
 
         // Listen to App Launch Sequence events from ActivityTaskManager,
         // and forward them to the native binder service.
         ActivityMetricsLaunchObserverRegistry launchObserverRegistry =
                 provideLaunchObserverRegistry();
-        launchObserverRegistry.registerLaunchObserver(new AppLaunchObserver());
+        launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
+
+        mRegisteredListeners = true;
     }
 
     private class AppLaunchObserver implements ActivityMetricsLaunchObserver {
@@ -110,6 +265,8 @@
         // launch sequences on the native side.
         private @AppLaunchEvent.SequenceId long mSequenceId = -1;
 
+        // All callbacks occur on the same background thread. Don't synchronize explicitly.
+
         @Override
         public void onIntentStarted(@NonNull Intent intent) {
             // #onIntentStarted [is the only transition that] initiates a new launch sequence.
@@ -174,7 +331,7 @@
 
             invokeRemote(() ->
                 mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
-                        new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, activity))
+                        new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, activity))
             );
         }
     }
@@ -201,6 +358,7 @@
         }
     }
 
+    /** Allow passing lambdas to #invokeRemote */
     private interface RemoteRunnable {
         void run() throws RemoteException;
     }
@@ -209,8 +367,26 @@
        try {
            r.run();
        } catch (RemoteException e) {
-           // TODO: what do we do with exceptions?
-           throw new AssertionError("not implemented", e);
+           // This could be a logic error (remote side returning error), which we need to fix.
+           //
+           // This could also be a DeadObjectException in which case its probably just iorapd
+           // being manually restarted.
+           //
+           // Don't make any assumption, since DeadObjectException could also mean iorapd crashed
+           // unexpectedly.
+           //
+           // DeadObjectExceptions are recovered from using DeathRecipient and #linkToDeath.
+           handleRemoteError(e);
        }
     }
+
+    private static void handleRemoteError(Throwable t) {
+        if (WTF_CRASH) {
+            // In development modes, we just want to crash.
+            throw new AssertionError("unexpected remote error", t);
+        } else {
+            // Log to wtf which gets sent to dropbox, and in system_server this does not crash.
+            Log.wtf(TAG, t);
+        }
+    }
 }
diff --git a/startop/iorap/tests/AndroidTest.xml b/startop/iorap/tests/AndroidTest.xml
index f83a16e..919154d 100644
--- a/startop/iorap/tests/AndroidTest.xml
+++ b/startop/iorap/tests/AndroidTest.xml
@@ -33,6 +33,15 @@
     <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer">
     </target_preparer>
 
+    <target_preparer
+        class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- Crash instead of using Log.wtf within the system_server iorap code. -->
+        <option name="set-property" key="iorapd.forwarding_service.wtf_crash" value="true" />
+        <!-- IIorapd has fake behavior: it doesn't do anything but reply with 'DONE' status -->
+        <option name="set-property" key="iorapd.binder.fake" value="true" />
+        <option name="restore-properties" value="true" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.google.android.startop.iorap.tests" />
         <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index 2b99ce1..2d29875 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -50,10 +50,10 @@
     }
 
     private CallAttributes(Parcel in) {
-        mPreciseCallState = (PreciseCallState) in.readValue(mPreciseCallState.getClass()
-                .getClassLoader());
+        mPreciseCallState = (PreciseCallState)
+                in.readValue(PreciseCallState.class.getClassLoader());
         mNetworkType = in.readInt();
-        mCallQuality = (CallQuality) in.readValue(mCallQuality.getClass().getClassLoader());
+        mCallQuality = (CallQuality) in.readValue(CallQuality.class.getClassLoader());
     }
 
     // getters
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
index 07c4a93..c16efbd 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
@@ -70,6 +70,7 @@
             R.id.benchmark_text_low_hitrate,
             R.id.benchmark_edit_text_input,
             R.id.benchmark_overdraw,
+            R.id.benchmark_bitmap_upload,
     };
 
     public static class LocalBenchmarksList extends ListFragment {
@@ -204,6 +205,7 @@
             case R.id.benchmark_text_low_hitrate:
             case R.id.benchmark_edit_text_input:
             case R.id.benchmark_overdraw:
+            case R.id.benchmark_bitmap_upload:
             case R.id.benchmark_memory_bandwidth:
             case R.id.benchmark_memory_latency:
             case R.id.benchmark_power_management:
@@ -323,6 +325,9 @@
                 intent = new Intent(getApplicationContext(), EditTextInputActivity.class);
                 break;
             case R.id.benchmark_overdraw:
+                intent = new Intent(getApplicationContext(), FullScreenOverdrawActivity.class);
+                break;
+            case R.id.benchmark_bitmap_upload:
                 intent = new Intent(getApplicationContext(), BitmapUploadActivity.class);
                 break;
             case R.id.benchmark_memory_bandwidth:
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
index 89c6aed..5723c59 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
@@ -229,6 +229,8 @@
                 return context.getString(R.string.cpu_gflops_name);
             case R.id.benchmark_overdraw:
                 return context.getString(R.string.overdraw_name);
+            case R.id.benchmark_bitmap_upload:
+                return context.getString(R.string.bitmap_upload_name);
             default:
                 return "Some Benchmark";
         }
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
index 7870902..7692836 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
@@ -32,6 +32,7 @@
 import android.view.View;
 
 import com.android.benchmark.R;
+import com.android.benchmark.registry.BenchmarkRegistry;
 import com.android.benchmark.ui.automation.Automator;
 import com.android.benchmark.ui.automation.Interaction;
 
@@ -124,7 +125,9 @@
         final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
         final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
 
-        mAutomator = new Automator("BMUpload", runId, iteration, getWindow(),
+        String name = BenchmarkRegistry.getBenchmarkName(this, R.id.benchmark_bitmap_upload);
+
+        mAutomator = new Automator(name, runId, iteration, getWindow(),
                 new Automator.AutomateCallback() {
                     @Override
                     public void onPostAutomate() {
diff --git a/tests/JankBench/app/src/main/res/values/ids.xml b/tests/JankBench/app/src/main/res/values/ids.xml
index 6801fd9..694e0d9 100644
--- a/tests/JankBench/app/src/main/res/values/ids.xml
+++ b/tests/JankBench/app/src/main/res/values/ids.xml
@@ -23,6 +23,7 @@
     <item name="benchmark_text_low_hitrate" type="id" />
     <item name="benchmark_edit_text_input" type="id" />
     <item name="benchmark_overdraw" type="id" />
+    <item name="benchmark_bitmap_upload" type="id" />
     <item name="benchmark_memory_bandwidth" type="id" />
     <item name="benchmark_memory_latency" type="id" />
     <item name="benchmark_power_management" type="id" />
diff --git a/tests/JankBench/app/src/main/res/values/strings.xml b/tests/JankBench/app/src/main/res/values/strings.xml
index 270adf8..5c240589 100644
--- a/tests/JankBench/app/src/main/res/values/strings.xml
+++ b/tests/JankBench/app/src/main/res/values/strings.xml
@@ -33,6 +33,8 @@
     <string name="edit_text_input_description">Tests edit text input</string>
     <string name="overdraw_name">Overdraw Test</string>
     <string name="overdraw_description">Tests how the device handles overdraw</string>
+    <string name="bitmap_upload_name">Bitmap Upload Test</string>
+    <string name="bitmap_upload_description">Tests bitmap upload</string>
     <string name="memory_bandwidth_name">Memory Bandwidth</string>
     <string name="memory_bandwidth_description">Test device\'s memory bandwidth</string>
     <string name="memory_latency_name">Memory Latency</string>
diff --git a/tests/JankBench/app/src/main/res/xml/benchmark.xml b/tests/JankBench/app/src/main/res/xml/benchmark.xml
index 07c453c..fccc7b9 100644
--- a/tests/JankBench/app/src/main/res/xml/benchmark.xml
+++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml
@@ -62,6 +62,12 @@
         benchmark:category="ui"
         benchmark:description="@string/overdraw_description" />
 
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/bitmap_upload_name"
+        benchmark:id="@id/benchmark_bitmap_upload"
+        benchmark:category="ui"
+        benchmark:description="@string/bitmap_upload_description" />
+
     <!--
     <com.android.benchmark.Benchmark
         benchmark:name="@string/memory_bandwidth_name"
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 5b17224..0b74d87 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -246,17 +246,17 @@
         assertFalse(vpn.getLockdown());
 
         // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
         assertTrue(vpn.getAlwaysOn());
         assertFalse(vpn.getLockdown());
 
         // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
         assertTrue(vpn.getAlwaysOn());
         assertTrue(vpn.getLockdown());
 
         // Remove always-on configuration.
-        assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
+        assertTrue(vpn.setAlwaysOnPackage(null, false));
         assertFalse(vpn.getAlwaysOn());
         assertFalse(vpn.getLockdown());
     }
@@ -270,11 +270,11 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
             new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -283,7 +283,7 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[1]);
 
         // Switch to another app.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
             new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -297,87 +297,6 @@
     }
 
     @Test
-    public void testLockdownWhitelist() throws Exception {
-        final Vpn vpn = createVpn(primaryUser.id);
-        final UidRange user = UidRange.createForUser(primaryUser.id);
-
-        // Set always-on with lockdown and whitelist app PKGS[2] from lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[2])));
-        verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
-                new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
-                new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
-        }));
-        assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
-        assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
-
-        // Change whitelisted app to PKGS[3].
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[3])));
-        verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
-                new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
-        }));
-        verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
-                new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1),
-                new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
-        }));
-        assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2]);
-        assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]);
-
-        // Change the VPN app.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[3])));
-        verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
-                new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
-                new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1)
-        }));
-        verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
-                new UidRange(user.start, user.start + PKG_UIDS[0] - 1),
-                new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1)
-        }));
-        assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
-        assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
-
-        // Remove the whitelist.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
-        verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
-                new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
-                new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
-        }));
-        verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
-                new UidRange(user.start + PKG_UIDS[0] + 1, user.stop),
-        }));
-        assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2],
-                user.start + PKG_UIDS[3]);
-        assertUnblocked(vpn, user.start + PKG_UIDS[0]);
-
-        // Add the whitelist.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1])));
-        verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
-                new UidRange(user.start + PKG_UIDS[0] + 1, user.stop)
-        }));
-        verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
-                new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
-                new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
-        }));
-        assertBlocked(vpn, user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
-        assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]);
-
-        // Try whitelisting a package with a comma, should be rejected.
-        assertFalse(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList("a.b,c.d")));
-
-        // Pass a non-existent packages in the whitelist, they (and only they) should be ignored.
-        // Whitelisted package should change from PGKS[1] to PKGS[2].
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true,
-                Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
-        verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{
-                new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
-                new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
-        }));
-        verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[]{
-                new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[2] - 1),
-                new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
-        }));
-    }
-
-    @Test
     public void testLockdownAddingAProfile() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
         setMockedUsers(primaryUser);
@@ -391,7 +310,7 @@
         final UidRange profile = UidRange.createForUser(tempProfile.id);
 
         // Set lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
             new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -517,7 +436,7 @@
                 .cancelAsUser(anyString(), anyInt(), eq(userHandle));
 
         // Start showing a notification for disconnected once always-on.
-        vpn.setAlwaysOnPackage(PKGS[0], false, null);
+        vpn.setAlwaysOnPackage(PKGS[0], false);
         order.verify(mNotificationManager)
                 .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
 
@@ -531,7 +450,7 @@
                 .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
 
         // Notification should be cleared after unsetting always-on package.
-        vpn.setAlwaysOnPackage(null, false, null);
+        vpn.setAlwaysOnPackage(null, false);
         order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
     }
 
@@ -664,9 +583,7 @@
             doAnswer(invocation -> {
                 final String appName = (String) invocation.getArguments()[0];
                 final int userId = (int) invocation.getArguments()[1];
-                Integer appId = packages.get(appName);
-                if (appId == null) throw new PackageManager.NameNotFoundException(appName);
-                return UserHandle.getUid(userId, appId);
+                return UserHandle.getUid(userId, packages.get(appName));
             }).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt());
         } catch (Exception e) {
         }
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
index f2ecef9..e57433a 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -202,9 +202,11 @@
         final CountDownLatch latch = new CountDownLatch(1);
         functor.accept(latch);
         try {
-            latch.await(5000, TimeUnit.MILLISECONDS);
+            if (!latch.await(5000, TimeUnit.MILLISECONDS)) {
+                fail(timeoutMessage);
+            }
         } catch (InterruptedException e) {
-            fail(timeoutMessage);
+            fail("Thread was interrupted");
         }
     }
 
@@ -314,6 +316,7 @@
                             assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
                             assertNull(key);
                             assertNull(attr);
+                            latch.countDown();
                         })));
     }
 
@@ -383,6 +386,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(FAKE_KEYS[5], key);
+                    latch.countDown();
                 })));
 
         // MTU matches key 4 but v4 address matches key 5. The latter is stronger.
@@ -392,6 +396,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(FAKE_KEYS[5], key);
+                    latch.countDown();
                 })));
 
         // Closest to key 3 (indeed, identical)
@@ -402,6 +407,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(FAKE_KEYS[3], key);
+                    latch.countDown();
                 })));
 
         // Group hint alone must not be strong enough to override the rest
@@ -411,6 +417,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(FAKE_KEYS[3], key);
+                    latch.countDown();
                 })));
 
         // Still closest to key 3, though confidence is lower
@@ -421,6 +428,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(FAKE_KEYS[3], key);
+                    latch.countDown();
                 })));
 
         // But changing the MTU makes this closer to key 4
@@ -430,6 +438,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(FAKE_KEYS[4], key);
+                    latch.countDown();
                 })));
 
         // MTU alone not strong enough to make this group-close
@@ -441,6 +450,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertNull(key);
+                    latch.countDown();
                 })));
     }
 
@@ -450,6 +460,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(sameness, answer.getNetworkSameness());
+                    latch.countDown();
                 })));
     }
 
@@ -488,6 +499,7 @@
                             + status.resultCode, status.isSuccess());
                     assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
                     assertNull(answer);
+                    latch.countDown();
                 })));
     }
 }
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 35fba3d..488de87 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.net.NetworkInfo.DetailedState;
@@ -120,8 +121,14 @@
     @UnsupportedAppUsage
     private String mMacAddress = DEFAULT_MAC_ADDRESS;
 
+    /**
+     * Whether the network is ephemeral or not.
+     */
     private boolean mEphemeral;
 
+    /**
+     * Whether the network is trusted or not.
+     */
     private boolean mTrusted;
 
     /**
@@ -130,6 +137,12 @@
     private boolean mOsuAp;
 
     /**
+     * If connected to a network suggestion or specifier, store the package name of the app,
+     * else null.
+     */
+    private String mNetworkSuggestionOrSpecifierPackageName;
+
+    /**
      * Running total count of lost (not ACKed) transmitted unicast data packets.
      * @hide
      */
@@ -209,6 +222,7 @@
         setMeteredHint(false);
         setEphemeral(false);
         setOsuAp(false);
+        setNetworkSuggestionOrSpecifierPackageName(null);
         txBad = 0;
         txSuccess = 0;
         rxSuccess = 0;
@@ -240,6 +254,8 @@
             mMeteredHint = source.mMeteredHint;
             mEphemeral = source.mEphemeral;
             mTrusted = source.mTrusted;
+            mNetworkSuggestionOrSpecifierPackageName =
+                    source.mNetworkSuggestionOrSpecifierPackageName;
             mOsuAp = source.mOsuAp;
             txBad = source.txBad;
             txRetries = source.txRetries;
@@ -476,6 +492,17 @@
         return mOsuAp;
     }
 
+    /** {@hide} */
+    public void setNetworkSuggestionOrSpecifierPackageName(@Nullable String packageName) {
+        mNetworkSuggestionOrSpecifierPackageName = packageName;
+    }
+
+    /** {@hide} */
+    public @Nullable String getNetworkSuggestionOrSpecifierPackageName() {
+        return mNetworkSuggestionOrSpecifierPackageName;
+    }
+
+
     /** @hide */
     @UnsupportedAppUsage
     public void setNetworkId(int id) {
@@ -634,6 +661,7 @@
         dest.writeDouble(rxSuccessRate);
         mSupplicantState.writeToParcel(dest, flags);
         dest.writeInt(mOsuAp ? 1 : 0);
+        dest.writeString(mNetworkSuggestionOrSpecifierPackageName);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -672,6 +700,7 @@
                 info.rxSuccessRate = in.readDouble();
                 info.mSupplicantState = SupplicantState.CREATOR.createFromParcel(in);
                 info.mOsuAp = in.readInt() != 0;
+                info.mNetworkSuggestionOrSpecifierPackageName = in.readString();
                 return info;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 40077e1..0668239 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -16,7 +16,7 @@
 
 package android.net.wifi;
 
-import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
 import static android.Manifest.permission.ACCESS_WIFI_STATE;
 import static android.Manifest.permission.READ_WIFI_CREDENTIAL;
 
@@ -950,8 +950,7 @@
      * which was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()} flag
      * set.
      * <p>
-     * Note: The broadcast is sent to the app only if it holds either one of
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+     * Note: The broadcast is sent to the app only if it holds
      * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission.
      *
      * @see #EXTRA_NETWORK_SUGGESTION
@@ -1183,7 +1182,7 @@
      * containing configurations which they created.
      */
     @Deprecated
-    @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_WIFI_STATE})
+    @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE})
     public List<WifiConfiguration> getConfiguredNetworks() {
         try {
             ParceledListSlice<WifiConfiguration> parceledList =
@@ -1199,7 +1198,7 @@
 
     /** @hide */
     @SystemApi
-    @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL})
+    @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL})
     public List<WifiConfiguration> getPrivilegedConfiguredNetworks() {
         try {
             ParceledListSlice<WifiConfiguration> parceledList =
@@ -1405,7 +1404,6 @@
      * {@link #reject()} to return the user's selection back to the platform via this callback.
      * @hide
      */
-    @SystemApi
     public interface NetworkRequestUserSelectionCallback {
         /**
          * User selected this network to connect to.
@@ -1429,7 +1427,6 @@
      * or reject the request by the app.
      * @hide
      */
-    @SystemApi
     public interface NetworkRequestMatchCallback {
         /**
          * Invoked to register a callback to be invoked to convey user selection. The callback
@@ -1606,7 +1603,6 @@
      *                 object. If null, then the application's main thread will be used.
      * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
     public void registerNetworkRequestMatchCallback(@NonNull NetworkRequestMatchCallback callback,
                                                     @Nullable Handler handler) {
@@ -1636,7 +1632,6 @@
      * @param callback Callback for network match events
      * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
     public void unregisterNetworkRequestMatchCallback(
             @NonNull NetworkRequestMatchCallback callback) {
@@ -1656,8 +1651,7 @@
      * When the device decides to connect to one of the provided network suggestions, platform sends
      * a directed broadcast {@link #ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} to the app if
      * the network was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()}
-     * flag set and the app holds either one of
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+     * flag set and the app holds
      * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission.
      *<p>
      * NOTE:
@@ -2290,7 +2284,6 @@
     /**
      * Return the results of the latest access point scan.
      * @return the list of access points found in the most recent scan. An app must hold
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
      * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
      * in order to get valid results.
      */
@@ -2601,7 +2594,7 @@
      * <p>
      * Applications need to have the following permissions to start LocalOnlyHotspot: {@link
      * android.Manifest.permission#CHANGE_WIFI_STATE} and {@link
-     * android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION}.  Callers without
+     * android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}.  Callers without
      * the permissions will trigger a {@link java.lang.SecurityException}.
      * <p>
      * @param callback LocalOnlyHotspotCallback for the application to receive updates about
@@ -2684,7 +2677,7 @@
      * {@link LocalOnlyHotspotObserver#onStopped()} callbacks.
      * <p>
      * Applications should have the
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION}
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}
      * permission.  Callers without the permission will trigger a
      * {@link java.lang.SecurityException}.
      * <p>
diff --git a/wifi/java/android/net/wifi/aware/IdentityChangedListener.java b/wifi/java/android/net/wifi/aware/IdentityChangedListener.java
index 81a06e8..a8b19b3 100644
--- a/wifi/java/android/net/wifi/aware/IdentityChangedListener.java
+++ b/wifi/java/android/net/wifi/aware/IdentityChangedListener.java
@@ -30,7 +30,7 @@
 public class IdentityChangedListener {
     /**
      * @param mac The MAC address of the Aware discovery interface. The application must have the
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} to get the actual MAC address,
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} to get the actual MAC address,
      *            otherwise all 0's will be provided.
      */
     public void onIdentityChanged(byte[] mac) {
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 1fa1fd5..8aef7a2 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -231,7 +231,7 @@
      * <p>
      * This version of the API attaches a listener to receive the MAC address of the Aware interface
      * on startup and whenever it is updated (it is randomized at regular intervals for privacy).
-     * The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+     * The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
      * permission to execute this attach request. Otherwise, use the
      * {@link #attach(AttachCallback, Handler)} version. Note that aside from permission
      * requirements this listener will wake up the host at regular intervals causing higher power
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
index 5f8841c..245b304 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
@@ -133,7 +133,7 @@
      *      An application must use the {@link DiscoverySession#close()} to
      *      terminate the publish discovery session once it isn't needed. This will free
      *      resources as well terminate any on-air transmissions.
-     * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+     * <p>The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
      * permission to start a publish discovery session.
      *
      * @param publishConfig The {@link PublishConfig} specifying the
@@ -179,7 +179,7 @@
      *      An application must use the {@link DiscoverySession#close()} to
      *      terminate the subscribe discovery session once it isn't needed. This will free
      *      resources as well terminate any on-air transmissions.
-     * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+     * <p>The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
      * permission to start a subscribe discovery session.
      *
      * @param subscribeConfig The {@link SubscribeConfig} specifying the
diff --git a/wifi/java/android/net/wifi/aware/package.html b/wifi/java/android/net/wifi/aware/package.html
index d5d962f6..c4f2e1f 100644
--- a/wifi/java/android/net/wifi/aware/package.html
+++ b/wifi/java/android/net/wifi/aware/package.html
@@ -15,7 +15,7 @@
 <ul>
     <li>{@link android.Manifest.permission#ACCESS_WIFI_STATE}</li>
     <li>{@link android.Manifest.permission#CHANGE_WIFI_STATE}</li>
-    <li>{@link android.Manifest.permission#ACCESS_COARSE_LOCATION}</li>
+    <li>{@link android.Manifest.permission#ACCESS_FINE_LOCATION}</li>
 </ul>
 
 <p class="note"><strong>Note:</strong> Not all Android-powered devices support Wi-Fi Aware
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 1bed914..052ab99 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -1139,7 +1139,7 @@
      * @param c is the channel created at {@link #initialize}
      * @param listener for callbacks on success or failure. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void discoverPeers(Channel c, ActionListener listener) {
         checkChannel(c);
         c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener));
@@ -1183,7 +1183,7 @@
      * @param config options as described in {@link WifiP2pConfig} class
      * @param listener for callbacks on success or failure. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void connect(Channel c, WifiP2pConfig config, ActionListener listener) {
         checkChannel(c);
         checkP2pConfig(config);
@@ -1225,7 +1225,7 @@
      * @param c is the channel created at {@link #initialize}
      * @param listener for callbacks on success or failure. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void createGroup(Channel c, ActionListener listener) {
         checkChannel(c);
         c.mAsyncChannel.sendMessage(CREATE_GROUP, WifiP2pGroup.PERSISTENT_NET_ID,
@@ -1256,7 +1256,7 @@
      * @param config the configuration of a p2p group.
      * @param listener for callbacks on success or failure. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void createGroup(@NonNull Channel c,
             @Nullable WifiP2pConfig config,
             @Nullable ActionListener listener) {
@@ -1344,7 +1344,7 @@
      * @param servInfo is a local service information.
      * @param listener for callbacks on success or failure. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void addLocalService(Channel c, WifiP2pServiceInfo servInfo, ActionListener listener) {
         checkChannel(c);
         checkServiceInfo(servInfo);
@@ -1454,7 +1454,7 @@
      * @param c is the channel created at {@link #initialize}
      * @param listener for callbacks on success or failure. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void discoverServices(Channel c, ActionListener listener) {
         checkChannel(c);
         c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, c.putListener(listener));
@@ -1530,7 +1530,7 @@
      * @param c is the channel created at {@link #initialize}
      * @param listener for callback when peer list is available. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void requestPeers(Channel c, PeerListListener listener) {
         checkChannel(c);
         c.mAsyncChannel.sendMessage(REQUEST_PEERS, 0, c.putListener(listener));
@@ -1553,7 +1553,7 @@
      * @param c is the channel created at {@link #initialize}
      * @param listener for callback when group info is available. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void requestGroupInfo(Channel c, GroupInfoListener listener) {
         checkChannel(c);
         c.mAsyncChannel.sendMessage(REQUEST_GROUP_INFO, 0, c.putListener(listener));
diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
index 677bf37..948dcfa 100644
--- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
@@ -35,6 +35,7 @@
     private static final long TEST_TX_RETRIES = 2;
     private static final long TEST_TX_BAD = 3;
     private static final long TEST_RX_SUCCESS = 4;
+    private static final String TEST_PACKAGE_NAME = "com.test.example";
 
     /**
      *  Verify parcel write/read with WifiInfo.
@@ -48,6 +49,7 @@
         writeWifiInfo.rxSuccess = TEST_RX_SUCCESS;
         writeWifiInfo.setTrusted(true);
         writeWifiInfo.setOsuAp(true);
+        writeWifiInfo.setNetworkSuggestionOrSpecifierPackageName(TEST_PACKAGE_NAME);
 
         Parcel parcel = Parcel.obtain();
         writeWifiInfo.writeToParcel(parcel, 0);
@@ -62,5 +64,6 @@
         assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess);
         assertTrue(readWifiInfo.isTrusted());
         assertTrue(readWifiInfo.isOsuAp());
+        assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getNetworkSuggestionOrSpecifierPackageName());
     }
 }