Merge "Settle auto PiP transaction in WindowContainerTransaction" into sc-dev
diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
index 906071f..3f30d3e 100644
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
@@ -19,11 +19,14 @@
 import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
 
+import com.android.modules.annotation.MinSdk;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -77,6 +80,7 @@
  There are four types of HDR video(Dolby-Vision, HDR10, HDR10+, HLG) supported by the platform,
  application will only need to specify individual types they supported.
  */
+@MinSdk(Build.VERSION_CODES.S)
 public final class ApplicationMediaCapabilities implements Parcelable {
     private static final String TAG = "ApplicationMediaCapabilities";
 
diff --git a/apex/media/framework/java/android/media/MediaFeature.java b/apex/media/framework/java/android/media/MediaFeature.java
index 0e461888..8d1b159 100644
--- a/apex/media/framework/java/android/media/MediaFeature.java
+++ b/apex/media/framework/java/android/media/MediaFeature.java
@@ -17,6 +17,9 @@
 package android.media;
 
 import android.annotation.StringDef;
+import android.os.Build;
+
+import com.android.modules.annotation.MinSdk;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -24,6 +27,7 @@
 /**
  * MediaFeature defines various media features, e.g. hdr type.
  */
+@MinSdk(Build.VERSION_CODES.S)
 public final class MediaFeature {
      /**
      * Defines tye type of HDR(high dynamic range) video.
diff --git a/apex/media/framework/java/android/media/MediaFrameworkInitializer.java b/apex/media/framework/java/android/media/MediaFrameworkInitializer.java
index 9332835..de2924e 100644
--- a/apex/media/framework/java/android/media/MediaFrameworkInitializer.java
+++ b/apex/media/framework/java/android/media/MediaFrameworkInitializer.java
@@ -21,7 +21,9 @@
 import android.annotation.SystemApi.Client;
 import android.app.SystemServiceRegistry;
 import android.content.Context;
+import android.os.Build;
 
+import com.android.modules.annotation.MinSdk;
 import com.android.modules.utils.build.SdkLevel;
 
 /**
@@ -29,6 +31,7 @@
  *
  * @hide
  */
+@MinSdk(Build.VERSION_CODES.S)
 @SystemApi(client = Client.MODULE_LIBRARIES)
 public class MediaFrameworkInitializer {
     private MediaFrameworkInitializer() {
diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java
index 30d1896..84332e5 100644
--- a/apex/media/framework/java/android/media/MediaTranscodeManager.java
+++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.net.Uri;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -34,6 +35,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.annotation.MinSdk;
 
 import java.io.FileNotFoundException;
 import java.lang.annotation.Retention;
@@ -99,6 +101,7 @@
  TODO(hkuang): Clarify whether supports framerate conversion.
  @hide
  */
+@MinSdk(Build.VERSION_CODES.S)
 @SystemApi
 public final class MediaTranscodeManager {
     private static final String TAG = "MediaTranscodeManager";
diff --git a/core/api/current.txt b/core/api/current.txt
index 63e35f1..bca2e92 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10961,6 +10961,7 @@
     field public static final String ACTION_MANAGED_PROFILE_UNLOCKED = "android.intent.action.MANAGED_PROFILE_UNLOCKED";
     field public static final String ACTION_MANAGE_NETWORK_USAGE = "android.intent.action.MANAGE_NETWORK_USAGE";
     field public static final String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE";
+    field public static final String ACTION_MANAGE_UNUSED_APPS = "android.intent.action.MANAGE_UNUSED_APPS";
     field public static final String ACTION_MEDIA_BAD_REMOVAL = "android.intent.action.MEDIA_BAD_REMOVAL";
     field public static final String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON";
     field public static final String ACTION_MEDIA_CHECKING = "android.intent.action.MEDIA_CHECKING";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5b61835..39e259d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5194,6 +5194,21 @@
     field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int RADIO_TUNER = 1998; // 0x7ce
   }
 
+  public final class MediaRouter2 {
+    method @NonNull public java.util.List<android.media.MediaRoute2Info> getAllRoutes();
+    method @Nullable public String getClientPackageName();
+    method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
+    method @Nullable public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
+    method public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
+    method public void startScan();
+    method public void stopScan();
+    method public void transfer(@NonNull android.media.MediaRouter2.RoutingController, @NonNull android.media.MediaRoute2Info);
+  }
+
+  public abstract static class MediaRouter2.RouteCallback {
+    method public void onPreferredFeaturesChanged(@NonNull java.util.List<java.lang.String>);
+  }
+
   public class PlayerProxy {
     method public void pause();
     method public void setPan(float);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c601aab..5cf83ac 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1901,6 +1901,20 @@
             "android.intent.action.AUTO_REVOKE_PERMISSIONS";
 
     /**
+     * Activity action: Launch UI to manage unused apps (hibernated apps).
+     *
+     * <p>
+     * Input: Nothing.
+     * </p>
+     * <p>
+     * Output: Nothing.
+     * </p>
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_UNUSED_APPS =
+            "android.intent.action.MANAGE_UNUSED_APPS";
+
+    /**
      * Activity action: Launch UI to review permissions for an app.
      * The system uses this intent if permission review for apps not
      * supporting the new runtime permissions model is enabled. In
diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java
index d71a830..a9348c6 100644
--- a/core/java/android/service/timezone/TimeZoneProviderService.java
+++ b/core/java/android/service/timezone/TimeZoneProviderService.java
@@ -218,7 +218,30 @@
     }
 
     /**
-     * Starts the provider sending updates.
+     * Informs the provider that it should start detecting and reporting the detected time zone
+     * state via the various {@code report} methods. Implementations of {@link
+     * #onStartUpdates(long)} should return immediately, and will typically be used to start
+     * worker threads or begin asynchronous location listening.
+     *
+     * <p>Between {@link #onStartUpdates(long)} and {@link #onStopUpdates()} calls, the Android
+     * system server holds the latest report from the provider in memory. After an initial report,
+     * provider implementations are only required to send a report via {@link
+     * #reportSuggestion(TimeZoneProviderSuggestion)} or via {@link #reportUncertain()} when it
+     * differs from the previous report.
+     *
+     * <p>{@link #reportPermanentFailure(Throwable)} can also be called by provider implementations
+     * in rare cases, after which the provider should consider itself stopped and not make any
+     * further reports. {@link #onStopUpdates()} will not be called in this case.
+     *
+     * <p>The {@code initializationTimeoutMillis} parameter indicates how long the provider has been
+     * granted to call one of the {@code report} methods for the first time. If the provider does
+     * not call one of the {@code report} methods in this time, it may be judged uncertain and the
+     * Android system server may move on to use other providers or detection methods. Providers
+     * should therefore make best efforts during this time to generate a report, which could involve
+     * increased power usage. Providers should preferably report an explicit {@link
+     * #reportUncertain()} if the time zone(s) cannot be detected within the initialization timeout.
+     *
+     * @see #onStopUpdates() for the signal from the system server to stop sending reports
      */
     public abstract void onStartUpdates(@DurationMillisLong long initializationTimeoutMillis);
 
@@ -228,7 +251,8 @@
     }
 
     /**
-     * Stops the provider sending updates.
+     * Stops the provider sending further updates. This will be called after {@link
+     * #onStartUpdates(long)}.
      */
     public abstract void onStopUpdates();
 
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index ee98878..52801fa 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -474,6 +474,12 @@
     public static final String SHARE_USE_SERVICE_TARGETS = "share_use_service_targets";
 
 
+    /*
+     * (long) The duration that the home button must be pressed before triggering Assist
+     */
+    public static final String HOME_BUTTON_LONG_PRESS_DURATION_MS =
+            "home_button_long_press_duration_ms";
+
     private SystemUiDeviceConfigFlags() {
     }
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f9227617..d783b44 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2775,11 +2775,11 @@
          The app can check whether it has this authorization by calling
          {@link android.provider.Settings#canDrawOverlays
          Settings.canDrawOverlays()}.
-         <p>Protection level: signature|appop|installer|appPredictor|pre23|development -->
+         <p>Protection level: signature|setup|appop|installer|appPredictor|pre23|development -->
     <permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
         android:label="@string/permlab_systemAlertWindow"
         android:description="@string/permdesc_systemAlertWindow"
-        android:protectionLevel="signature|appop|installer|appPredictor|pre23|development" />
+        android:protectionLevel="signature|setup|appop|installer|appPredictor|pre23|development" />
 
     <!-- @SystemApi @hide Allows an application to create windows using the type
          {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY},
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTests.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
similarity index 98%
rename from core/tests/mockingcoretests/src/android/view/DisplayTests.java
rename to core/tests/mockingcoretests/src/android/view/DisplayTest.java
index a036db2..f8dd153 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTests.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -56,12 +56,15 @@
  *
  * <p>Build/Install/Run:
  *
- * atest FrameworksMockingCoreTests:android.view.DisplayTests
+ * atest FrameworksMockingCoreTests:android.view.DisplayTest
+ *
+ * <p>This test class is a part of Window Manager Service tests and specified in
+ * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @Presubmit
-public class DisplayTests {
+public class DisplayTest {
 
     private static final int APP_WIDTH = 272;
     private static final int APP_HEIGHT = 700;
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index b3a180d..b171599 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -155,6 +155,7 @@
     <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="media" />
     <assign-permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" uid="media" />
     <assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="media" />
+    <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="media" />
 
     <assign-permission name="android.permission.INTERNET" uid="media" />
 
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 3f03302..1b5dc8b 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -38,14 +38,6 @@
     path: "src",
 }
 
-filegroup {
-    name: "wm_shell-aidls",
-    srcs: [
-        "src/**/*.aidl",
-    ],
-    path: "src",
-}
-
 // TODO(b/168581922) protologtool do not support kotlin(*.kt)
 filegroup {
     name: "wm_shell-sources-kt",
@@ -106,7 +98,7 @@
         ":wm_shell_protolog_src",
         // TODO(b/168581922) protologtool do not support kotlin(*.kt)
         ":wm_shell-sources-kt",
-        ":wm_shell-aidls",
+        "src/**/I*.aidl",
     ],
     resource_dirs: [
         "res",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index d451f4a..eaed24d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -47,7 +47,21 @@
     private final ShellExecutor mMainExecutor;
     private final HandlerImpl mImpl = new HandlerImpl();
 
-    public ShellCommandHandlerImpl(
+    public static ShellCommandHandler create(
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
+            Optional<SplitScreenController> splitScreenOptional,
+            Optional<Pip> pipOptional,
+            Optional<OneHandedController> oneHandedOptional,
+            Optional<HideDisplayCutoutController> hideDisplayCutout,
+            Optional<AppPairsController> appPairsOptional,
+            ShellExecutor mainExecutor) {
+        return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional,
+                splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
+                appPairsOptional, mainExecutor).mImpl;
+    }
+
+    private ShellCommandHandlerImpl(
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
@@ -66,10 +80,6 @@
         mMainExecutor = mainExecutor;
     }
 
-    public ShellCommandHandler asShellCommandHandler() {
-        return mImpl;
-    }
-
     /** Dumps WM Shell internal state. */
     private void dump(PrintWriter pw) {
         mShellTaskOrganizer.dump(pw, "");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index 6f4550c..85bd24c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -26,7 +26,7 @@
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.startingsurface.StartingSurface;
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.Optional;
@@ -47,20 +47,44 @@
     private final FullscreenTaskListener mFullscreenTaskListener;
     private final ShellExecutor mMainExecutor;
     private final Transitions mTransitions;
-    private final StartingWindowController mStartingWindow;
+    private final Optional<StartingSurface> mStartingSurfaceOptional;
 
     private final InitImpl mImpl = new InitImpl();
 
-    public ShellInitImpl(DisplayImeController displayImeController,
+    public static ShellInit create(DisplayImeController displayImeController,
             DragAndDropController dragAndDropController,
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<AppPairsController> appPairsOptional,
+            Optional<StartingSurface> startingSurfaceOptional,
             Optional<PipTouchHandler> pipTouchHandlerOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Transitions transitions,
-            StartingWindowController startingWindow,
+            ShellExecutor mainExecutor) {
+        return new ShellInitImpl(displayImeController,
+                dragAndDropController,
+                shellTaskOrganizer,
+                legacySplitScreenOptional,
+                splitScreenOptional,
+                appPairsOptional,
+                startingSurfaceOptional,
+                pipTouchHandlerOptional,
+                fullscreenTaskListener,
+                transitions,
+                mainExecutor).mImpl;
+    }
+
+    private ShellInitImpl(DisplayImeController displayImeController,
+            DragAndDropController dragAndDropController,
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
+            Optional<SplitScreenController> splitScreenOptional,
+            Optional<AppPairsController> appPairsOptional,
+            Optional<StartingSurface> startingSurfaceOptional,
+            Optional<PipTouchHandler> pipTouchHandlerOptional,
+            FullscreenTaskListener fullscreenTaskListener,
+            Transitions transitions,
             ShellExecutor mainExecutor) {
         mDisplayImeController = displayImeController;
         mDragAndDropController = dragAndDropController;
@@ -72,21 +96,17 @@
         mPipTouchHandlerOptional = pipTouchHandlerOptional;
         mTransitions = transitions;
         mMainExecutor = mainExecutor;
-        mStartingWindow = startingWindow;
-    }
-
-    public ShellInit asShellInit() {
-        return mImpl;
+        mStartingSurfaceOptional = startingSurfaceOptional;
     }
 
     private void init() {
         // Start listening for display changes
         mDisplayImeController.startMonitorDisplays();
 
-        // Setup the shell organizer
         mShellTaskOrganizer.addListenerForType(
                 mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);
-        mShellTaskOrganizer.initStartingWindow(mStartingWindow);
+        mStartingSurfaceOptional.ifPresent(mShellTaskOrganizer::initStartingSurface);
+        // Register the shell organizer
         mShellTaskOrganizer.registerOrganizer();
 
         mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 94d13ea..fcb53cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -48,7 +48,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.sizecompatui.SizeCompatUIController;
-import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.startingsurface.StartingSurface;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -133,7 +133,7 @@
     private final ArraySet<LocusIdListener> mLocusIdListeners = new ArraySet<>();
 
     private final Object mLock = new Object();
-    private StartingWindowController mStartingWindow;
+    private StartingSurface mStartingSurface;
 
     /**
      * In charge of showing size compat UI. Can be {@code null} if device doesn't support size
@@ -184,8 +184,8 @@
     /**
      * @hide
      */
-    public void initStartingWindow(StartingWindowController startingWindow) {
-        mStartingWindow = startingWindow;
+    public void initStartingSurface(StartingSurface startingSurface) {
+        mStartingSurface = startingSurface;
     }
 
     /**
@@ -302,23 +302,23 @@
 
     @Override
     public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
-        if (mStartingWindow != null) {
-            mStartingWindow.addStartingWindow(info, appToken);
+        if (mStartingSurface != null) {
+            mStartingSurface.addStartingWindow(info, appToken);
         }
     }
 
     @Override
     public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
             boolean playRevealAnimation) {
-        if (mStartingWindow != null) {
-            mStartingWindow.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
+        if (mStartingSurface != null) {
+            mStartingSurface.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
         }
     }
 
     @Override
     public void copySplashScreenView(int taskId) {
-        if (mStartingWindow != null) {
-            mStartingWindow.copySplashScreenView(taskId);
+        if (mStartingSurface != null) {
+            mStartingSurface.copySplashScreenView(taskId);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java
deleted file mode 100644
index b29058b..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2021 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.wm.shell.common;
-
-import android.Manifest;
-import android.util.Slog;
-
-import java.util.function.Consumer;
-
-/**
- * Helpers for working with executors
- */
-public class ExecutorUtils {
-
-    /**
-     * Checks that the caller has the MANAGE_ACTIVITY_TASKS permission and executes the given
-     * callback.
-     */
-    public static <T> void executeRemoteCallWithTaskPermission(RemoteCallable<T> controllerInstance,
-            String log, Consumer<T> callback) {
-        executeRemoteCallWithTaskPermission(controllerInstance, log, callback,
-                false /* blocking */);
-    }
-
-    /**
-     * Checks that the caller has the MANAGE_ACTIVITY_TASKS permission and executes the given
-     * callback.
-     */
-    public static <T> void executeRemoteCallWithTaskPermission(RemoteCallable<T> controllerInstance,
-            String log, Consumer<T> callback, boolean blocking) {
-        if (controllerInstance == null) return;
-
-        final RemoteCallable<T> controller = controllerInstance;
-        controllerInstance.getContext().enforceCallingPermission(
-                Manifest.permission.MANAGE_ACTIVITY_TASKS, log);
-        if (blocking) {
-            try {
-                controllerInstance.getRemoteCallExecutor().executeBlocking(() -> {
-                    callback.accept((T) controller);
-                });
-            } catch (InterruptedException e) {
-                Slog.e("ExecutorUtils", "Remote call failed", e);
-            }
-        } else {
-            controllerInstance.getRemoteCallExecutor().execute(() -> {
-                callback.accept((T) controller);
-            });
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java
deleted file mode 100644
index 30f535b..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2021 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.wm.shell.common;
-
-import android.content.Context;
-
-/**
- * An interface for controllers that can receive remote calls.
- */
-public interface RemoteCallable<T> {
-    /**
-     * Returns a context used for permission checking.
-     */
-    Context getContext();
-
-    /**
-     * Returns the executor to post the handler callback to.
-     */
-    ShellExecutor getRemoteCallExecutor();
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 9a09ca4..aab2334 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -235,7 +235,7 @@
             mStarter.startShortcut(packageName, id, stage, position, opts, user);
         } else {
             mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT),
-                    null, stage, position, opts);
+                    mContext, null, stage, position, opts);
         }
     }
 
@@ -295,7 +295,7 @@
                 @Nullable Bundle options);
         void startShortcut(String packageName, String shortcutId, @StageType int stage,
                 @StagePosition int position, @Nullable Bundle options, UserHandle user);
-        void startIntent(PendingIntent intent, Intent fillInIntent,
+        void startIntent(PendingIntent intent, Context context, Intent fillInIntent,
                 @StageType int stage, @StagePosition int position,
                 @Nullable Bundle options);
         void enterSplitScreen(int taskId, boolean leftOrTop);
@@ -337,8 +337,9 @@
         }
 
         @Override
-        public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, int stage,
-                int position, @Nullable Bundle options) {
+        public void startIntent(PendingIntent intent, Context context,
+                @Nullable Intent fillInIntent, int stage, int position,
+                @Nullable Bundle options) {
             try {
                 intent.send(mContext, 0, fillInIntent, null, null, null, options);
             } catch (PendingIntent.CanceledException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl
deleted file mode 100644
index 008b508..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2021 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.wm.shell.onehanded;
-
-/**
- * Interface that is exposed to remote callers to manipulate the OneHanded feature.
- */
-interface IOneHanded {
-
-    /**
-     * Enters one handed mode.
-     */
-    oneway void startOneHanded() = 1;
-
-    /**
-     * Exits one handed mode.
-     */
-    oneway void stopOneHanded() = 2;
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index a7e9a01..4f31c37 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -26,14 +26,6 @@
  */
 @ExternalThread
 public interface OneHanded {
-
-    /**
-     * Returns a binder that can be passed to an external process to manipulate OneHanded.
-     */
-    default IOneHanded createExternalInterface() {
-        return null;
-    }
-
     /**
      * Return one handed settings enabled or not.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 8022c1b..5fc7c98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -18,10 +18,6 @@
 
 import static android.os.UserHandle.USER_CURRENT;
 
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-
-import android.Manifest;
-import android.annotation.BinderThread;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.om.IOverlayManager;
@@ -47,8 +43,6 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.ExecutorUtils;
-import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -60,7 +54,7 @@
 /**
  * Manages and manipulates the one handed states, transitions, and gesture for phones.
  */
-public class OneHandedController implements RemoteCallable<OneHandedController> {
+public class OneHandedController {
     private static final String TAG = "OneHandedController";
 
     private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
@@ -245,16 +239,6 @@
         return mImpl;
     }
 
-    @Override
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public ShellExecutor getRemoteCallExecutor() {
-        return mMainExecutor;
-    }
-
     /**
      * Set one handed enabled or disabled when user update settings
      */
@@ -583,22 +567,8 @@
         }
     }
 
-    /**
-     * The interface for calls from outside the Shell, within the host process.
-     */
     @ExternalThread
     private class OneHandedImpl implements OneHanded {
-        private IOneHandedImpl mIOneHanded;
-
-        @Override
-        public IOneHanded createExternalInterface() {
-            if (mIOneHanded != null) {
-                mIOneHanded.invalidate();
-            }
-            mIOneHanded = new IOneHandedImpl(OneHandedController.this);
-            return mIOneHanded;
-        }
-
         @Override
         public boolean isOneHandedEnabled() {
             // This is volatile so return directly
@@ -667,39 +637,4 @@
             });
         }
     }
-
-    /**
-     * The interface for calls from outside the host process.
-     */
-    @BinderThread
-    private static class IOneHandedImpl extends IOneHanded.Stub {
-        private OneHandedController mController;
-
-        IOneHandedImpl(OneHandedController controller) {
-            mController = controller;
-        }
-
-        /**
-         * Invalidates this instance, preventing future calls from updating the controller.
-         */
-        void invalidate() {
-            mController = null;
-        }
-
-        @Override
-        public void startOneHanded() {
-            executeRemoteCallWithTaskPermission(mController, "startOneHanded",
-                    (controller) -> {
-                        controller.startOneHanded();
-                    });
-        }
-
-        @Override
-        public void stopOneHanded() {
-            executeRemoteCallWithTaskPermission(mController, "stopOneHanded",
-                    (controller) -> {
-                        controller.stopOneHanded();
-                    });
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
deleted file mode 100644
index a6ffa6e..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2021 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.wm.shell.pip;
-
-import android.app.PictureInPictureParams;
-import android.content.ComponentName;
-import android.content.pm.ActivityInfo;
-import android.graphics.Rect;
-
-import com.android.wm.shell.pip.IPipAnimationListener;
-
-/**
- * Interface that is exposed to remote callers to manipulate the Pip feature.
- */
-interface IPip {
-
-    /**
-     * Notifies that Activity is about to be swiped to home with entering PiP transition and
-     * queries the destination bounds for PiP depends on Launcher's rotation and shelf height.
-     *
-     * @param componentName ComponentName represents the Activity
-     * @param activityInfo ActivityInfo tied to the Activity
-     * @param pictureInPictureParams PictureInPictureParams tied to the Activity
-     * @param launcherRotation Launcher rotation to calculate the PiP destination bounds
-     * @param shelfHeight Shelf height of launcher to calculate the PiP destination bounds
-     * @return destination bounds the PiP window should land into
-     */
-    Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo,
-                in PictureInPictureParams pictureInPictureParams,
-                int launcherRotation, int shelfHeight) = 1;
-
-    /**
-     * Notifies the swiping Activity to PiP onto home transition is finished
-     *
-     * @param componentName ComponentName represents the Activity
-     * @param destinationBounds the destination bounds the PiP window lands into
-     */
-    oneway void stopSwipePipToHome(in ComponentName componentName, in Rect destinationBounds) = 2;
-
-    /**
-     * Sets listener to get pinned stack animation callbacks.
-     */
-    oneway void setPinnedStackAnimationListener(IPipAnimationListener listener) = 3;
-
-    /**
-     * Sets the shelf height and visibility.
-     */
-    oneway void setShelfHeight(boolean visible, int shelfHeight) = 4;
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 6d4773b..d14c3e3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -16,10 +16,15 @@
 
 package com.android.wm.shell.pip;
 
+import android.annotation.Nullable;
+import android.app.PictureInPictureParams;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 
 import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.pip.phone.PipTouchHandler;
 
 import java.io.PrintWriter;
 import java.util.function.Consumer;
@@ -29,14 +34,6 @@
  */
 @ExternalThread
 public interface Pip {
-
-    /**
-     * Returns a binder that can be passed to an external process to manipulate PIP.
-     */
-    default IPip createExternalInterface() {
-        return null;
-    }
-
     /**
      * Expand PIP, it's possible that specific request to activate the window via Alt-tab.
      */
@@ -112,6 +109,30 @@
     default void showPictureInPictureMenu() {}
 
     /**
+     * Called by Launcher when swiping an auto-pip enabled Activity to home starts
+     * @param componentName {@link ComponentName} represents the Activity entering PiP
+     * @param activityInfo {@link ActivityInfo} tied to the Activity
+     * @param pictureInPictureParams {@link PictureInPictureParams} tied to the Activity
+     * @param launcherRotation Rotation Launcher is in
+     * @param shelfHeight Shelf height when landing PiP window onto Launcher
+     * @return Destination bounds of PiP window based on the parameters passed in
+     */
+    default Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+            PictureInPictureParams pictureInPictureParams,
+            int launcherRotation, int shelfHeight) {
+        return null;
+    }
+
+    /**
+     * Called by Launcher when swiping an auto-pip enable Activity to home finishes
+     * @param componentName {@link ComponentName} represents the Activity entering PiP
+     * @param destinationBounds Destination bounds of PiP window
+     */
+    default void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
+        return;
+    }
+
+    /**
      * Called by NavigationBar in order to listen in for PiP bounds change. This is mostly used
      * for times where the PiP bounds could conflict with SystemUI elements, such as a stashed
      * PiP and the Back-from-Edge gesture.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 501b90e..9a584c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -21,10 +21,8 @@
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 import static android.view.WindowManager.INPUT_CONSUMER_PIP;
 
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
 
-import android.Manifest;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.PictureInPictureParams;
@@ -35,7 +33,6 @@
 import android.content.pm.ParceledListSlice;
 import android.content.res.Configuration;
 import android.graphics.Rect;
-import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -47,7 +44,6 @@
 import android.view.WindowManagerGlobal;
 import android.window.WindowContainerTransaction;
 
-import androidx.annotation.BinderThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -55,13 +51,9 @@
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.ExecutorUtils;
-import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.pip.IPip;
-import com.android.wm.shell.pip.IPipAnimationListener;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
@@ -79,8 +71,7 @@
 /**
  * Manages the picture-in-picture (PIP) UI and states for Phones.
  */
-public class PipController implements PipTransitionController.PipTransitionCallback,
-        RemoteCallable<PipController> {
+public class PipController implements PipTransitionController.PipTransitionCallback {
     private static final String TAG = "PipController";
 
     private Context mContext;
@@ -94,12 +85,12 @@
     private PipBoundsState mPipBoundsState;
     private PipTouchHandler mTouchHandler;
     private PipTransitionController mPipTransitionController;
-    protected final PipImpl mImpl;
+    protected final PipImpl mImpl = new PipImpl();
 
     private final Rect mTmpInsetBounds = new Rect();
 
     private boolean mIsInFixedRotation;
-    private IPipAnimationListener mPinnedStackAnimationRecentsCallback;
+    private Consumer<Boolean> mPinnedStackAnimationRecentsCallback;
 
     protected PhonePipMenuController mMenuController;
     protected PipTaskOrganizer mPipTaskOrganizer;
@@ -273,7 +264,6 @@
         }
 
         mContext = context;
-        mImpl = new PipImpl();
         mWindowManagerShellWrapper = windowManagerShellWrapper;
         mDisplayController = displayController;
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
@@ -376,16 +366,6 @@
                 });
     }
 
-    @Override
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public ShellExecutor getRemoteCallExecutor() {
-        return mMainExecutor;
-    }
-
     private void onConfigurationChanged(Configuration newConfig) {
         mPipBoundsAlgorithm.onConfigurationChanged(mContext);
         mTouchHandler.onConfigurationChanged();
@@ -494,7 +474,7 @@
         mPipTaskOrganizer.setOneShotAnimationType(animationType);
     }
 
-    private void setPinnedStackAnimationListener(IPipAnimationListener callback) {
+    private void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
         mPinnedStackAnimationRecentsCallback = callback;
     }
 
@@ -532,11 +512,7 @@
         // Disable touches while the animation is running
         mTouchHandler.setTouchEnabled(false);
         if (mPinnedStackAnimationRecentsCallback != null) {
-            try {
-                mPinnedStackAnimationRecentsCallback.onPipAnimationStarted();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to call onPinnedStackAnimationStarted()", e);
-            }
+            mPinnedStackAnimationRecentsCallback.accept(true);
         }
     }
 
@@ -662,21 +638,7 @@
         mPipInputConsumer.dump(pw, innerPrefix);
     }
 
-    /**
-     * The interface for calls from outside the Shell, within the host process.
-     */
     private class PipImpl implements Pip {
-        private IPipImpl mIPip;
-
-        @Override
-        public IPip createExternalInterface() {
-            if (mIPip != null) {
-                mIPip.invalidate();
-            }
-            mIPip = new IPipImpl(PipController.this);
-            return mIPip;
-        }
-
         @Override
         public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
             mMainExecutor.execute(() -> {
@@ -734,6 +696,13 @@
         }
 
         @Override
+        public void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
+            mMainExecutor.execute(() -> {
+                PipController.this.setPinnedStackAnimationListener(callback);
+            });
+        }
+
+        @Override
         public void setPinnedStackAnimationType(int animationType) {
             mMainExecutor.execute(() -> {
                 PipController.this.setPinnedStackAnimationType(animationType);
@@ -755,6 +724,29 @@
         }
 
         @Override
+        public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+                PictureInPictureParams pictureInPictureParams, int launcherRotation,
+                int shelfHeight) {
+            Rect[] result = new Rect[1];
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    result[0] = PipController.this.startSwipePipToHome(componentName, activityInfo,
+                            pictureInPictureParams, launcherRotation, shelfHeight);
+                });
+            } catch (InterruptedException e) {
+                Slog.e(TAG, "Failed to start swipe pip to home");
+            }
+            return result[0];
+        }
+
+        @Override
+        public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
+            mMainExecutor.execute(() -> {
+                PipController.this.stopSwipePipToHome(componentName, destinationBounds);
+            });
+        }
+
+        @Override
         public void dump(PrintWriter pw) {
             try {
                 mMainExecutor.executeBlocking(() -> {
@@ -765,89 +757,4 @@
             }
         }
     }
-
-    /**
-     * The interface for calls from outside the host process.
-     */
-    @BinderThread
-    private static class IPipImpl extends IPip.Stub {
-        private PipController mController;
-        private IPipAnimationListener mListener;
-        private final IBinder.DeathRecipient mListenerDeathRecipient =
-                new IBinder.DeathRecipient() {
-                    @Override
-                    @BinderThread
-                    public void binderDied() {
-                        final PipController controller = mController;
-                        controller.getRemoteCallExecutor().execute(() -> {
-                            mListener = null;
-                            controller.setPinnedStackAnimationListener(null);
-                        });
-                    }
-                };
-
-        IPipImpl(PipController controller) {
-            mController = controller;
-        }
-
-        /**
-         * Invalidates this instance, preventing future calls from updating the controller.
-         */
-        void invalidate() {
-            mController = null;
-        }
-
-        @Override
-        public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
-                PictureInPictureParams pictureInPictureParams, int launcherRotation,
-                int shelfHeight) {
-            Rect[] result = new Rect[1];
-            executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
-                    (controller) -> {
-                        result[0] = controller.startSwipePipToHome(componentName, activityInfo,
-                                pictureInPictureParams, launcherRotation, shelfHeight);
-                    }, true /* blocking */);
-            return result[0];
-        }
-
-        @Override
-        public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
-            executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
-                    (controller) -> {
-                        controller.stopSwipePipToHome(componentName, destinationBounds);
-                    });
-        }
-
-        @Override
-        public void setShelfHeight(boolean visible, int height) {
-            executeRemoteCallWithTaskPermission(mController, "setShelfHeight",
-                    (controller) -> {
-                        controller.setShelfHeight(visible, height);
-                    });
-        }
-
-        @Override
-        public void setPinnedStackAnimationListener(IPipAnimationListener listener) {
-            executeRemoteCallWithTaskPermission(mController, "setPinnedStackAnimationListener",
-                    (controller) -> {
-                        if (mListener != null) {
-                            // Reset the old death recipient
-                            mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
-                                    0 /* flags */);
-                        }
-                        if (listener != null) {
-                            // Register the death recipient for the new listener to clear the listener
-                            try {
-                                listener.asBinder().linkToDeath(mListenerDeathRecipient,
-                                        0 /* flags */);
-                            } catch (RemoteException e) {
-                                Slog.e(TAG, "Failed to link to death");
-                                return;
-                            }
-                        }
-                        mListener = listener;
-                        controller.setPinnedStackAnimationListener(listener);
-                    });
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
deleted file mode 100644
index 0c46eab..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2021 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.wm.shell.splitscreen;
-
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.UserHandle;
-
-import com.android.wm.shell.splitscreen.ISplitScreenListener;
-
-/**
- * Interface that is exposed to remote callers to manipulate the splitscreen feature.
- */
-interface ISplitScreen {
-
-    /**
-     * Registers a split screen listener.
-     */
-    oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1;
-
-    /**
-     * Unregisters a split screen listener.
-     */
-    oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
-
-    /**
-     * Hides the side-stage if it is currently visible.
-     */
-    oneway void setSideStageVisibility(boolean visible) = 3;
-
-    /**
-     * Removes a task from the side stage.
-     */
-    oneway void removeFromSideStage(int taskId) = 4;
-
-    /**
-     * Removes the split-screen stages.
-     */
-    oneway void exitSplitScreen() = 5;
-
-    /**
-     * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible.
-     */
-    oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6;
-
-    /**
-     * Starts a task in a stage.
-     */
-    oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7;
-
-    /**
-     * Starts a shortcut in a stage.
-     */
-    oneway void startShortcut(String packageName, String shortcutId, int stage, int position,
-            in Bundle options, in UserHandle user) = 8;
-
-    /**
-     * Starts an activity in a stage.
-     */
-    oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage,
-            int position, in Bundle options) = 9;
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 340b55d7..25a84bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -35,7 +35,7 @@
  * TODO: Figure out which of these are actually needed outside of the Shell
  */
 @ExternalThread
-public interface SplitScreen {
+public interface SplitScreen extends DragAndDropPolicy.Starter {
     /**
      * Stage position isn't specified normally meaning to use what ever it is currently set to.
      */
@@ -89,10 +89,35 @@
         void onTaskStageChanged(int taskId, @StageType int stage, boolean visible);
     }
 
-    /**
-     * Returns a binder that can be passed to an external process to manipulate SplitScreen.
-     */
-    default ISplitScreen createExternalInterface() {
-        return null;
-    }
+    /** @return {@code true} if split-screen is currently visible. */
+    boolean isSplitScreenVisible();
+    /** Moves a task in the side-stage of split-screen. */
+    boolean moveToSideStage(int taskId, @StagePosition int sideStagePosition);
+    /** Moves a task in the side-stage of split-screen. */
+    boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+            @StagePosition int sideStagePosition);
+    /** Removes a task from the side-stage of split-screen. */
+    boolean removeFromSideStage(int taskId);
+    /** Sets the position of the side-stage. */
+    void setSideStagePosition(@StagePosition int sideStagePosition);
+    /** Hides the side-stage if it is currently visible. */
+    void setSideStageVisibility(boolean visible);
+
+    /** Removes the split-screen stages. */
+    void exitSplitScreen();
+    /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */
+    void exitSplitScreenOnHide(boolean exitSplitScreenOnHide);
+    /** Gets the stage bounds. */
+    void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds);
+
+    void registerSplitScreenListener(SplitScreenListener listener);
+    void unregisterSplitScreenListener(SplitScreenListener listener);
+
+    void startTask(int taskId,
+            @StageType int stage, @StagePosition int position, @Nullable Bundle options);
+    void startShortcut(String packageName, String shortcutId, @StageType int stage,
+            @StagePosition int position, @Nullable Bundle options, UserHandle user);
+    void startIntent(PendingIntent intent, Context context,
+            @Nullable Intent fillInIntent, @StageType int stage,
+            @StagePosition int position, @Nullable Bundle options);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index d4362ef..bb6f6f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -18,7 +18,6 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_UNDEFINED;
@@ -35,24 +34,19 @@
 import android.content.pm.LauncherApps;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Slog;
 
-import androidx.annotation.BinderThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.draganddrop.DragAndDropPolicy;
-import com.android.wm.shell.splitscreen.ISplitScreenListener;
 
 import java.io.PrintWriter;
 
@@ -61,8 +55,7 @@
  * {@link SplitScreen}.
  * @see StageCoordinator
  */
-public class SplitScreenController implements DragAndDropPolicy.Starter,
-        RemoteCallable<SplitScreenController> {
+public class SplitScreenController implements DragAndDropPolicy.Starter {
     private static final String TAG = SplitScreenController.class.getSimpleName();
 
     private final ShellTaskOrganizer mTaskOrganizer;
@@ -91,16 +84,6 @@
         return mImpl;
     }
 
-    @Override
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public ShellExecutor getRemoteCallExecutor() {
-        return mMainExecutor;
-    }
-
     public void onOrganizerRegistered() {
         if (mStageCoordinator == null) {
             // TODO: Multi-display
@@ -189,13 +172,13 @@
         }
     }
 
-    public void startIntent(PendingIntent intent, Intent fillInIntent,
-            @SplitScreen.StageType int stage, @SplitScreen.StagePosition int position,
-            @Nullable Bundle options) {
+    public void startIntent(PendingIntent intent, Context context,
+            Intent fillInIntent, @SplitScreen.StageType int stage,
+            @SplitScreen.StagePosition int position, @Nullable Bundle options) {
         options = resolveStartStage(stage, position, options);
 
         try {
-            intent.send(mContext, 0, fillInIntent, null, null, null, options);
+            intent.send(context, 0, fillInIntent, null, null, null, options);
         } catch (PendingIntent.CanceledException e) {
             Slog.e(TAG, "Failed to launch activity", e);
         }
@@ -259,170 +242,121 @@
         }
     }
 
-    /**
-     * The interface for calls from outside the Shell, within the host process.
-     */
-    @ExternalThread
     private class SplitScreenImpl implements SplitScreen {
-        private ISplitScreenImpl mISplitScreen;
-
         @Override
-        public ISplitScreen createExternalInterface() {
-            if (mISplitScreen != null) {
-                mISplitScreen.invalidate();
-            }
-            mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
-            return mISplitScreen;
-        }
-    }
-
-    /**
-     * The interface for calls from outside the host process.
-     */
-    @BinderThread
-    private static class ISplitScreenImpl extends ISplitScreen.Stub {
-        private SplitScreenController mController;
-        private ISplitScreenListener mListener;
-        private final SplitScreen.SplitScreenListener mSplitScreenListener =
-                new SplitScreen.SplitScreenListener() {
-                    @Override
-                    public void onStagePositionChanged(int stage, int position) {
-                        try {
-                            if (mListener != null) {
-                                mListener.onStagePositionChanged(stage, position);
-                            }
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "onStagePositionChanged", e);
-                        }
-                    }
-
-                    @Override
-                    public void onTaskStageChanged(int taskId, int stage, boolean visible) {
-                        try {
-                            if (mListener != null) {
-                                mListener.onTaskStageChanged(taskId, stage, visible);
-                            }
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "onTaskStageChanged", e);
-                        }
-                    }
-                };
-        private final IBinder.DeathRecipient mListenerDeathRecipient =
-                new IBinder.DeathRecipient() {
-                    @Override
-                    @BinderThread
-                    public void binderDied() {
-                        final SplitScreenController controller = mController;
-                        controller.getRemoteCallExecutor().execute(() -> {
-                            mListener = null;
-                            controller.unregisterSplitScreenListener(mSplitScreenListener);
-                        });
-                    }
-                };
-
-        public ISplitScreenImpl(SplitScreenController controller) {
-            mController = controller;
-        }
-
-        /**
-         * Invalidates this instance, preventing future calls from updating the controller.
-         */
-        void invalidate() {
-            mController = null;
+        public boolean isSplitScreenVisible() {
+            return mMainExecutor.executeBlockingForResult(() -> {
+                return SplitScreenController.this.isSplitScreenVisible();
+            }, Boolean.class);
         }
 
         @Override
-        public void registerSplitScreenListener(ISplitScreenListener listener) {
-            executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
-                    (controller) -> {
-                        if (mListener != null) {
-                            mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
-                                    0 /* flags */);
-                        }
-                        if (listener != null) {
-                            try {
-                                listener.asBinder().linkToDeath(mListenerDeathRecipient,
-                                        0 /* flags */);
-                            } catch (RemoteException e) {
-                                Slog.e(TAG, "Failed to link to death");
-                                return;
-                            }
-                        }
-                        mListener = listener;
-                        controller.registerSplitScreenListener(mSplitScreenListener);
-                    });
+        public boolean moveToSideStage(int taskId, int sideStagePosition) {
+            return mMainExecutor.executeBlockingForResult(() -> {
+                return SplitScreenController.this.moveToSideStage(taskId, sideStagePosition);
+            }, Boolean.class);
         }
 
         @Override
-        public void unregisterSplitScreenListener(ISplitScreenListener listener) {
-            executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
-                    (controller) -> {
-                        if (mListener != null) {
-                            mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
-                                    0 /* flags */);
-                        }
-                        mListener = null;
-                        controller.unregisterSplitScreenListener(mSplitScreenListener);
-                    });
+        public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+                int sideStagePosition) {
+            return mMainExecutor.executeBlockingForResult(() -> {
+                return SplitScreenController.this.moveToSideStage(task, sideStagePosition);
+            }, Boolean.class);
         }
 
         @Override
-        public void exitSplitScreen() {
-            executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
-                    (controller) -> {
-                        controller.exitSplitScreen();
-                    });
+        public boolean removeFromSideStage(int taskId) {
+            return mMainExecutor.executeBlockingForResult(() -> {
+                return SplitScreenController.this.removeFromSideStage(taskId);
+            }, Boolean.class);
         }
 
         @Override
-        public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
-            executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
-                    (controller) -> {
-                        controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
-                    });
+        public void setSideStagePosition(int sideStagePosition) {
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.setSideStagePosition(sideStagePosition);
+            });
         }
 
         @Override
         public void setSideStageVisibility(boolean visible) {
-            executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
-                    (controller) -> {
-                        controller.setSideStageVisibility(visible);
-                    });
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.setSideStageVisibility(visible);
+            });
         }
 
         @Override
-        public void removeFromSideStage(int taskId) {
-            executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
-                    (controller) -> {
-                        controller.removeFromSideStage(taskId);
-                    });
+        public void enterSplitScreen(int taskId, boolean leftOrTop) {
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.enterSplitScreen(taskId, leftOrTop);
+            });
+        }
+
+        @Override
+        public void exitSplitScreen() {
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.exitSplitScreen();
+            });
+        }
+
+        @Override
+        public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.exitSplitScreenOnHide(exitSplitScreenOnHide);
+            });
+        }
+
+        @Override
+        public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    SplitScreenController.this.getStageBounds(outTopOrLeftBounds,
+                            outBottomOrRightBounds);
+                });
+            } catch (InterruptedException e) {
+                Slog.e(TAG, "Failed to get stage bounds in 2s");
+            }
+        }
+
+        @Override
+        public void registerSplitScreenListener(SplitScreenListener listener) {
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.registerSplitScreenListener(listener);
+            });
+        }
+
+        @Override
+        public void unregisterSplitScreenListener(SplitScreenListener listener) {
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.unregisterSplitScreenListener(listener);
+            });
         }
 
         @Override
         public void startTask(int taskId, int stage, int position, @Nullable Bundle options) {
-            executeRemoteCallWithTaskPermission(mController, "startTask",
-                    (controller) -> {
-                        controller.startTask(taskId, stage, position, options);
-                    });
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.startTask(taskId, stage, position, options);
+            });
         }
 
         @Override
         public void startShortcut(String packageName, String shortcutId, int stage, int position,
                 @Nullable Bundle options, UserHandle user) {
-            executeRemoteCallWithTaskPermission(mController, "startShortcut",
-                    (controller) -> {
-                        controller.startShortcut(packageName, shortcutId, stage, position,
-                                options, user);
-                    });
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.startShortcut(packageName, shortcutId, stage, position,
+                        options, user);
+            });
         }
 
         @Override
-        public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
-                @Nullable Bundle options) {
-            executeRemoteCallWithTaskPermission(mController, "startIntent",
-                    (controller) -> {
-                        controller.startIntent(intent, fillInIntent, stage, position, options);
-                    });
+        public void startIntent(PendingIntent intent, Context context, Intent fillInIntent,
+                int stage, int position, @Nullable Bundle options) {
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.startIntent(intent, context, fillInIntent, stage,
+                        position, options);
+            });
         }
     }
+
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl
deleted file mode 100644
index 546c406..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2021 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.wm.shell.startingsurface;
-
-import com.android.wm.shell.startingsurface.IStartingWindowListener;
-
-/**
- * Interface that is exposed to remote callers to manipulate starting windows.
- */
-interface IStartingWindow {
-    /**
-     * Sets listener to get task launching callbacks.
-     */
-    oneway void setStartingWindowListener(IStartingWindowListener listener) = 43;
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
index 079d689..f258286 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
@@ -16,15 +16,35 @@
 
 package com.android.wm.shell.startingsurface;
 
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.StartingWindowInfo;
+
+import java.util.function.BiConsumer;
 /**
  * Interface to engage starting window feature.
  */
 public interface StartingSurface {
+    /**
+     * Called when a task need a starting window.
+     */
+    void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken);
+    /**
+     * Called when the content of a task is ready to show, starting window can be removed.
+     */
+    void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
+            boolean playRevealAnimation);
+    /**
+     * Called when the Task wants to copy the splash screen.
+     * @param taskId
+     */
+    void copySplashScreenView(int taskId);
 
     /**
-     * Returns a binder that can be passed to an external process to manipulate starting windows.
+     * Registers the starting window listener.
+     *
+     * @param listener The callback when need a starting window.
      */
-    default IStartingWindow createExternalInterface() {
-        return null;
-    }
+    void setStartingWindowListener(BiConsumer<Integer, Integer> listener);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index b6ca869..a694e52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -25,8 +25,6 @@
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
 
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
 import android.content.Context;
@@ -39,9 +37,6 @@
 import android.window.TaskOrganizer;
 import android.window.TaskSnapshot;
 
-import androidx.annotation.BinderThread;
-
-import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 
@@ -63,7 +58,7 @@
  * constructor to keep everything synchronized.
  * @hide
  */
-public class StartingWindowController implements RemoteCallable<StartingWindowController> {
+public class StartingWindowController {
     private static final String TAG = StartingWindowController.class.getSimpleName();
     static final boolean DEBUG_SPLASH_SCREEN = false;
     static final boolean DEBUG_TASK_SNAPSHOT = false;
@@ -73,17 +68,17 @@
 
     private BiConsumer<Integer, Integer> mTaskLaunchingCallback;
     private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
-    private final Context mContext;
     private final ShellExecutor mSplashScreenExecutor;
 
     // For Car Launcher
     public StartingWindowController(Context context, ShellExecutor splashScreenExecutor) {
-        this(context, splashScreenExecutor, new TransactionPool());
+        mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
+                new TransactionPool());
+        mSplashScreenExecutor = splashScreenExecutor;
     }
 
     public StartingWindowController(Context context, ShellExecutor splashScreenExecutor,
             TransactionPool pool) {
-        mContext = context;
         mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor, pool);
         mSplashScreenExecutor = splashScreenExecutor;
     }
@@ -95,16 +90,6 @@
         return mImpl;
     }
 
-    @Override
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public ShellExecutor getRemoteCallExecutor() {
-        return mSplashScreenExecutor;
-    }
-
     private static class StartingTypeChecker {
         TaskSnapshot mSnapshot;
 
@@ -203,121 +188,59 @@
     /**
      * Called when a task need a starting window.
      */
-    public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
-        mSplashScreenExecutor.execute(() -> {
-            final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(windowInfo);
-            final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
-            if (mTaskLaunchingCallback != null) {
-                mTaskLaunchingCallback.accept(runningTaskInfo.taskId, suggestionType);
-            }
-            if (suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
-                mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken);
-            } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
-                final TaskSnapshot snapshot = mStartingTypeChecker.mSnapshot;
-                mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, snapshot);
-            }
-            // If prefer don't show, then don't show!
-        });
+    void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
+        final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(windowInfo);
+        final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
+        if (mTaskLaunchingCallback != null) {
+            mTaskLaunchingCallback.accept(runningTaskInfo.taskId, suggestionType);
+        }
+        if (suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
+            mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken);
+        } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
+            final TaskSnapshot snapshot = mStartingTypeChecker.mSnapshot;
+            mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, snapshot);
+        }
+        // If prefer don't show, then don't show!
     }
 
-    public void copySplashScreenView(int taskId) {
-        mSplashScreenExecutor.execute(() -> {
-            mStartingSurfaceDrawer.copySplashScreenView(taskId);
-        });
+    void copySplashScreenView(int taskId) {
+        mStartingSurfaceDrawer.copySplashScreenView(taskId);
     }
 
     /**
      * Called when the content of a task is ready to show, starting window can be removed.
      */
-    public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
+    void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
             boolean playRevealAnimation) {
-        mSplashScreenExecutor.execute(() -> {
-            mStartingSurfaceDrawer.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
-        });
+        mStartingSurfaceDrawer.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
     }
 
-    /**
-     * The interface for calls from outside the Shell, within the host process.
-     */
     private class StartingSurfaceImpl implements StartingSurface {
-        private IStartingWindowImpl mIStartingWindow;
 
         @Override
-        public IStartingWindowImpl createExternalInterface() {
-            if (mIStartingWindow != null) {
-                mIStartingWindow.invalidate();
-            }
-            mIStartingWindow = new IStartingWindowImpl(StartingWindowController.this);
-            return mIStartingWindow;
-        }
-    }
-
-    /**
-     * The interface for calls from outside the host process.
-     */
-    @BinderThread
-    private static class IStartingWindowImpl extends IStartingWindow.Stub {
-        private StartingWindowController mController;
-        private IStartingWindowListener mListener;
-        private final BiConsumer<Integer, Integer> mStartingWindowListener =
-                this::notifyIStartingWindowListener;
-        private final IBinder.DeathRecipient mListenerDeathRecipient =
-                new IBinder.DeathRecipient() {
-                    @Override
-                    @BinderThread
-                    public void binderDied() {
-                        final StartingWindowController controller = mController;
-                        controller.getRemoteCallExecutor().execute(() -> {
-                            mListener = null;
-                            controller.setStartingWindowListener(null);
-                        });
-                    }
-                };
-
-        public IStartingWindowImpl(StartingWindowController controller) {
-            mController = controller;
-        }
-
-        /**
-         * Invalidates this instance, preventing future calls from updating the controller.
-         */
-        void invalidate() {
-            mController = null;
+        public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
+            mSplashScreenExecutor.execute(() ->
+                    StartingWindowController.this.addStartingWindow(windowInfo, appToken));
         }
 
         @Override
-        public void setStartingWindowListener(IStartingWindowListener listener) {
-            executeRemoteCallWithTaskPermission(mController, "setStartingWindowListener",
-                    (controller) -> {
-                        if (mListener != null) {
-                            // Reset the old death recipient
-                            mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
-                                    0 /* flags */);
-                        }
-                        if (listener != null) {
-                            try {
-                                listener.asBinder().linkToDeath(mListenerDeathRecipient,
-                                        0 /* flags */);
-                            } catch (RemoteException e) {
-                                Slog.e(TAG, "Failed to link to death");
-                                return;
-                            }
-                        }
-                        mListener = listener;
-                        controller.setStartingWindowListener(mStartingWindowListener);
-                    });
+        public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
+                boolean playRevealAnimation) {
+            mSplashScreenExecutor.execute(() ->
+                    StartingWindowController.this.removeStartingWindow(taskId, leash, frame,
+                            playRevealAnimation));
         }
 
-        private void notifyIStartingWindowListener(int taskId, int supportedType) {
-            if (mListener == null) {
-                return;
-            }
+        @Override
+        public void copySplashScreenView(int taskId) {
+            mSplashScreenExecutor.execute(() ->
+                    StartingWindowController.this.copySplashScreenView(taskId));
+        }
 
-            try {
-                mListener.onTaskLaunching(taskId, supportedType);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to notify task launching", e);
-            }
+        @Override
+        public void setStartingWindowListener(BiConsumer<Integer, Integer> listener) {
+            mSplashScreenExecutor.execute(() ->
+                    StartingWindowController.this.setStartingWindowListener(listener));
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
deleted file mode 100644
index dffc700..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 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.wm.shell.transition;
-
-import android.window.IRemoteTransition;
-import android.window.TransitionFilter;
-
-/**
- * Interface that is exposed to remote callers to manipulate the transitions feature.
- */
-interface IShellTransitions {
-
-    /**
-     * Registers a remote transition handler.
-     */
-    oneway void registerRemote(in TransitionFilter filter,
-            in IRemoteTransition remoteTransition) = 1;
-
-    /**
-     * Unregisters a remote transition handler.
-     */
-    oneway void unregisterRemote(in IRemoteTransition remoteTransition) = 2;
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 9667f4b..ac93a17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell.transition;
 
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.IBinder;
@@ -25,7 +23,6 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
-import android.util.Slog;
 import android.view.SurfaceControl;
 import android.window.IRemoteTransition;
 import android.window.IRemoteTransitionFinishedCallback;
@@ -34,8 +31,6 @@
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
 
-import androidx.annotation.BinderThread;
-
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -47,8 +42,6 @@
  * if the request includes a specific remote.
  */
 public class RemoteTransitionHandler implements Transitions.TransitionHandler {
-    private static final String TAG = "RemoteTransitionHandler";
-
     private final ShellExecutor mMainExecutor;
 
     /** Includes remotes explicitly requested by, eg, ActivityOptions */
@@ -58,33 +51,15 @@
     private final ArrayList<Pair<TransitionFilter, IRemoteTransition>> mFilters =
             new ArrayList<>();
 
-    private final IBinder.DeathRecipient mTransitionDeathRecipient =
-            new IBinder.DeathRecipient() {
-                @Override
-                @BinderThread
-                public void binderDied() {
-                    mMainExecutor.execute(() -> {
-                        mFilters.clear();
-                    });
-                }
-            };
-
     RemoteTransitionHandler(@NonNull ShellExecutor mainExecutor) {
         mMainExecutor = mainExecutor;
     }
 
     void addFiltered(TransitionFilter filter, IRemoteTransition remote) {
-        try {
-            remote.asBinder().linkToDeath(mTransitionDeathRecipient, 0 /* flags */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to link to death");
-            return;
-        }
         mFilters.add(new Pair<>(filter, remote));
     }
 
     void removeFiltered(IRemoteTransition remote) {
-        remote.asBinder().unlinkToDeath(mTransitionDeathRecipient, 0 /* flags */);
         for (int i = mFilters.size() - 1; i >= 0; --i) {
             if (mFilters.get(i).second == remote) {
                 mFilters.remove(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java
similarity index 83%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java
index bc42c6e..85bbf74 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java
@@ -26,15 +26,7 @@
  * Interface to manage remote transitions.
  */
 @ExternalThread
-public interface ShellTransitions {
-
-    /**
-     * Returns a binder that can be passed to an external process to manipulate remote transitions.
-     */
-    default IShellTransitions createExternalInterface() {
-        return null;
-    }
-
+public interface RemoteTransitions {
     /**
      * Registers a remote transition.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index ca1b53d..677db10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -23,8 +23,6 @@
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ContentResolver;
@@ -53,7 +51,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ExternalThread;
@@ -63,7 +60,7 @@
 import java.util.Arrays;
 
 /** Plays transition animations */
-public class Transitions implements RemoteCallable<Transitions> {
+public class Transitions {
     static final String TAG = "ShellTransitions";
 
     /** Set to {@code true} to enable shell transitions. */
@@ -76,7 +73,7 @@
     private final ShellExecutor mAnimExecutor;
     private final TransitionPlayerImpl mPlayerImpl;
     private final RemoteTransitionHandler mRemoteTransitionHandler;
-    private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
+    private final RemoteTransitionImpl mImpl = new RemoteTransitionImpl();
 
     /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
     private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
@@ -90,6 +87,10 @@
     /** Keeps track of currently tracked transitions and all the animations associated with each */
     private final ArrayMap<IBinder, ActiveTransition> mActiveTransitions = new ArrayMap<>();
 
+    public static RemoteTransitions asRemoteTransitions(Transitions transitions) {
+        return transitions.mImpl;
+    }
+
     public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
             @NonNull Context context, @NonNull ShellExecutor mainExecutor,
             @NonNull ShellExecutor animExecutor) {
@@ -125,20 +126,6 @@
         mRemoteTransitionHandler = null;
     }
 
-    public ShellTransitions asRemoteTransitions() {
-        return mImpl;
-    }
-
-    @Override
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public ShellExecutor getRemoteCallExecutor() {
-        return mMainExecutor;
-    }
-
     private void dispatchAnimScaleSetting(float scale) {
         for (int i = mHandlers.size() - 1; i >= 0; --i) {
             mHandlers.get(i).setAnimScaleSetting(scale);
@@ -147,8 +134,8 @@
 
     /** Create an empty/non-registering transitions object for system-ui tests. */
     @VisibleForTesting
-    public static ShellTransitions createEmptyForTesting() {
-        return new ShellTransitions() {
+    public static RemoteTransitions createEmptyForTesting() {
+        return new RemoteTransitions() {
             @Override
             public void registerRemote(@androidx.annotation.NonNull TransitionFilter filter,
                     @androidx.annotation.NonNull IRemoteTransition remoteTransition) {
@@ -439,74 +426,24 @@
         }
     }
 
-    /**
-     * The interface for calls from outside the Shell, within the host process.
-     */
     @ExternalThread
-    private class ShellTransitionImpl implements ShellTransitions {
-        private IShellTransitionsImpl mIShellTransitions;
-
-        @Override
-        public IShellTransitions createExternalInterface() {
-            if (mIShellTransitions != null) {
-                mIShellTransitions.invalidate();
-            }
-            mIShellTransitions = new IShellTransitionsImpl(Transitions.this);
-            return mIShellTransitions;
-        }
-
+    private class RemoteTransitionImpl implements RemoteTransitions {
         @Override
         public void registerRemote(@NonNull TransitionFilter filter,
                 @NonNull IRemoteTransition remoteTransition) {
             mMainExecutor.execute(() -> {
-                mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
+                Transitions.this.registerRemote(filter, remoteTransition);
             });
         }
 
         @Override
         public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
             mMainExecutor.execute(() -> {
-                mRemoteTransitionHandler.removeFiltered(remoteTransition);
+                Transitions.this.unregisterRemote(remoteTransition);
             });
         }
     }
 
-    /**
-     * The interface for calls from outside the host process.
-     */
-    @BinderThread
-    private static class IShellTransitionsImpl extends IShellTransitions.Stub {
-        private Transitions mTransitions;
-
-        IShellTransitionsImpl(Transitions transitions) {
-            mTransitions = transitions;
-        }
-
-        /**
-         * Invalidates this instance, preventing future calls from updating the controller.
-         */
-        void invalidate() {
-            mTransitions = null;
-        }
-
-        @Override
-        public void registerRemote(@NonNull TransitionFilter filter,
-                @NonNull IRemoteTransition remoteTransition) {
-            executeRemoteCallWithTaskPermission(mTransitions, "registerRemote",
-                    (transitions) -> {
-                        transitions.mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
-                    });
-        }
-
-        @Override
-        public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
-            executeRemoteCallWithTaskPermission(mTransitions, "unregisterRemote",
-                    (transitions) -> {
-                        transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition);
-                    });
-        }
-    }
-
     private class SettingsObserver extends ContentObserver {
 
         SettingsObserver() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 2f2bbba..c1c4c6d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -205,7 +205,7 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mSplitScreenStarter).startIntent(any(), any(),
+        verify(mSplitScreenStarter).startIntent(any(), any(), any(),
                 eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
     }
 
@@ -217,12 +217,12 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mSplitScreenStarter).startIntent(any(), any(),
+        verify(mSplitScreenStarter).startIntent(any(), any(), any(),
                 eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
         reset(mSplitScreenStarter);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
-        verify(mSplitScreenStarter).startIntent(any(), any(),
+        verify(mSplitScreenStarter).startIntent(any(), any(), any(),
                 eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
     }
 
@@ -234,12 +234,12 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mSplitScreenStarter).startIntent(any(), any(),
+        verify(mSplitScreenStarter).startIntent(any(), any(), any(),
                 eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
         reset(mSplitScreenStarter);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
-        verify(mSplitScreenStarter).startIntent(any(), any(),
+        verify(mSplitScreenStarter).startIntent(any(), any(), any(),
                 eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
     }
 
@@ -251,7 +251,7 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mSplitScreenStarter).startIntent(any(), any(),
+        verify(mSplitScreenStarter).startIntent(any(), any(), any(),
                 eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
     }
 
@@ -263,7 +263,7 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mSplitScreenStarter).startIntent(any(), any(),
+        verify(mSplitScreenStarter).startIntent(any(), any(), any(),
                 eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
     }
 
@@ -276,13 +276,13 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mSplitScreenStarter).startIntent(any(), any(),
+        verify(mSplitScreenStarter).startIntent(any(), any(), any(),
                 eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
         reset(mSplitScreenStarter);
 
         // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
-        verify(mSplitScreenStarter).startIntent(any(), any(),
+        verify(mSplitScreenStarter).startIntent(any(), any(), any(),
                 eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
     }
 
@@ -295,13 +295,13 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mSplitScreenStarter).startIntent(any(), any(),
+        verify(mSplitScreenStarter).startIntent(any(), any(), any(),
                 eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
         reset(mSplitScreenStarter);
 
         // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
-        verify(mSplitScreenStarter).startIntent(any(), any(),
+        verify(mSplitScreenStarter).startIntent(any(), any(), any(),
                 eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
     }
 
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index b4db305..5f44b62 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -21,6 +21,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -89,6 +90,7 @@
     private final CopyOnWriteArrayList<ControllerCreationRequest> mControllerCreationRequests =
             new CopyOnWriteArrayList<>();
 
+    // TODO: Specify the fields that are only used (or not used) by system media router.
     private final String mClientPackageName;
     private final ManagerCallback mManagerCallback;
 
@@ -132,18 +134,34 @@
     }
 
     /**
-     * Gets an instance of the media router which controls the app's media routing.
+     * Gets an instance of the system media router which controls the app's media routing.
      * Returns {@code null} if the given package name is invalid.
+     * There are several things to note when using the media routers created with this method.
      * <p>
-     * Note: For media routers created with this method, the discovery preference passed to
-     * {@link #registerRouteCallback} will have no effect. The callback will be called accordingly
-     * with the client app's discovery preference. Therefore, it is recommended to pass
+     * First of all, the discovery preference passed to {@link #registerRouteCallback}
+     * will have no effect. The callback will be called accordingly with the client app's
+     * discovery preference. Therefore, it is recommended to pass
      * {@link RouteDiscoveryPreference#EMPTY} there.
+     * <p>
+     * Also, do not keep/compare the instances of the {@link RoutingController}, since they are
+     * always newly created with the latest session information whenever below methods are called:
+     * <ul>
+     * <li> {@link #getControllers()} </li>
+     * <li> {@link #getController(String)}} </li>
+     * <li> {@link TransferCallback#onTransfer(RoutingController, RoutingController)} </li>
+     * <li> {@link TransferCallback#onStop(RoutingController)} </li>
+     * <li> {@link ControllerCallback#onControllerUpdated(RoutingController)} </li>
+     * </ul>
+     * Therefore, in order to track the current routing status, keep the controller's ID instead,
+     * and use {@link #getController(String)} and {@link #getSystemController()} for
+     * getting controllers.
+     * <p>
+     * Finally, it will have no effect to call {@link #setOnGetControllerHintsListener}.
      *
      * @param clientPackageName the package name of the app to control
      * @hide
      */
-    //@SystemApi
+    @SystemApi
     @Nullable
     public static MediaRouter2 getInstance(@NonNull Context context,
             @NonNull String clientPackageName) {
@@ -168,12 +186,40 @@
                 instance = new MediaRouter2(context, clientPackageName);
                 sSystemMediaRouter2Map.put(clientPackageName, instance);
                 // TODO: Remove router instance once it is not needed.
-                instance.registerManagerCallback();
+                instance.registerManagerCallbackForSystemRouter();
             }
             return instance;
         }
     }
 
+    /**
+     * Starts scanning remote routes.
+     * Note that calling start/stopScan is applied to all system routers in the same process.
+     *
+     * @see #stopScan()
+     * @hide
+     */
+    @SystemApi
+    public void startScan() {
+        if (isSystemRouter()) {
+            sManager.startScan();
+        }
+    }
+
+    /**
+     * Stops scanning remote routes to reduce resource consumption.
+     * Note that calling start/stopScan is applied to all system routers in the same process.
+     *
+     * @see #startScan()
+     * @hide
+     */
+    @SystemApi
+    public void stopScan() {
+        if (isSystemRouter()) {
+            sManager.stopScan();
+        }
+    }
+
     private MediaRouter2(Context appContext) {
         mContext = appContext;
         mMediaRouterService = IMediaRouterService.Stub.asInterface(
@@ -209,13 +255,15 @@
     }
 
     private MediaRouter2(Context context, String clientPackageName) {
+        mContext = context;
         mClientPackageName = clientPackageName;
         mManagerCallback = new ManagerCallback();
-        mContext = context;
-        mMediaRouterService = null;
-        mPackageName = null;
         mHandler = new Handler(Looper.getMainLooper());
-        mSystemController = null;
+        mSystemController = new SystemRoutingController(sManager.getSystemRoutingSession());
+        mMediaRouterService = null; // TODO: Make this non-null and check permission.
+
+        // Only used by non-system MediaRouter2.
+        mPackageName = null;
     }
 
     /**
@@ -240,7 +288,7 @@
      * @see #getInstance(Context, String)
      * @hide
      */
-    //@SystemApi
+    @SystemApi
     @Nullable
     public String getClientPackageName() {
         return mClientPackageName;
@@ -358,7 +406,8 @@
      *
      * @hide
      */
-    //@SystemApi
+    @SystemApi
+    @NonNull
     public List<MediaRoute2Info> getAllRoutes() {
         if (isSystemRouter()) {
             return sManager.getAllRoutes();
@@ -377,10 +426,6 @@
      */
     @NonNull
     public List<MediaRoute2Info> getRoutes() {
-        if (isSystemRouter()) {
-            return sManager.getAvailableRoutes(mClientPackageName);
-        }
-
         synchronized (mLock) {
             if (mShouldUpdateRoutes) {
                 mShouldUpdateRoutes = false;
@@ -474,6 +519,9 @@
      *                 {@code null} for unset.
      */
     public void setOnGetControllerHintsListener(@Nullable OnGetControllerHintsListener listener) {
+        if (isSystemRouter()) {
+            return;
+        }
         mOnGetControllerHintsListener = listener;
     }
 
@@ -519,7 +567,7 @@
      * @param route the route you want to transfer the media to.
      * @hide
      */
-    //@SystemApi
+    @SystemApi
     public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
         if (isSystemRouter()) {
             sManager.transfer(controller.getRoutingSessionInfo(), route);
@@ -606,6 +654,23 @@
     }
 
     /**
+     * Gets a {@link RoutingController} whose ID is equal to the given ID.
+     * Returns {@code null} if there is no matching controller.
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public RoutingController getController(@NonNull String id) {
+        Objects.requireNonNull(id, "id must not be null");
+        for (RoutingController controller : getControllers()) {
+            if (TextUtils.equals(id, controller.getId())) {
+                return controller;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Gets the list of currently active {@link RoutingController routing controllers} on which
      * media can be played.
      * <p>
@@ -614,15 +679,25 @@
      */
     @NonNull
     public List<RoutingController> getControllers() {
-        // TODO: Do not create the controller instances every time,
-        //       Instead, update the list using the sessions' ID and session related callbacks.
+        List<RoutingController> result = new ArrayList<>();
+
         if (isSystemRouter()) {
-            return sManager.getRoutingSessions(mClientPackageName).stream()
-                    .map(info -> new RoutingController(info))
-                    .collect(Collectors.toList());
+            // Unlike non-system MediaRouter2, controller instances cannot be kept,
+            // since the transfer events initiated from other apps will not come through manager.
+            List<RoutingSessionInfo> sessions = sManager.getRoutingSessions(mClientPackageName);
+            for (RoutingSessionInfo session : sessions) {
+                RoutingController controller;
+                if (session.isSystemSession()) {
+                    mSystemController.setRoutingSessionInfo(session);
+                    controller = mSystemController;
+                } else {
+                    controller = new RoutingController(session);
+                }
+                result.add(controller);
+            }
+            return result;
         }
 
-        List<RoutingController> result = new ArrayList<>();
         result.add(0, mSystemController);
         synchronized (mLock) {
             result.addAll(mNonSystemRoutingControllers.values());
@@ -639,9 +714,15 @@
      * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
      * @hide
      */
+    @SystemApi
     public void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
         Objects.requireNonNull(route, "route must not be null");
 
+        if (isSystemRouter()) {
+            sManager.setRouteVolume(route, volume);
+            return;
+        }
+
         MediaRouter2Stub stub;
         synchronized (mLock) {
             stub = mStub;
@@ -928,8 +1009,9 @@
 
     /**
      * Registers {@link MediaRouter2Manager.Callback} for getting events.
+     * Should only used for system media routers.
      */
-    private void registerManagerCallback() {
+    private void registerManagerCallbackForSystemRouter() {
         // Using direct executor here, since MediaRouter2Manager also posts to the main handler.
         sManager.registerCallback(Runnable::run, mManagerCallback);
     }
@@ -941,6 +1023,16 @@
                 .collect(Collectors.toList());
     }
 
+    private void updateAllRoutesFromManager() {
+        synchronized (mLock) {
+            mRoutes.clear();
+            for (MediaRoute2Info route : sManager.getAllRoutes()) {
+                mRoutes.put(route.getId(), route);
+            }
+            mShouldUpdateRoutes = true;
+        }
+    }
+
     private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record: mRouteCallbackRecords) {
             List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
@@ -971,6 +1063,13 @@
         }
     }
 
+    private void notifyPreferredFeaturesChanged(List<String> features) {
+        for (RouteCallbackRecord record: mRouteCallbackRecords) {
+            record.mExecutor.execute(
+                    () -> record.mRouteCallback.onPreferredFeaturesChanged(features));
+        }
+    }
+
     private void notifyTransfer(RoutingController oldController, RoutingController newController) {
         for (TransferCallbackRecord record: mTransferCallbackRecords) {
             record.mExecutor.execute(
@@ -1024,6 +1123,17 @@
          * @param routes the list of routes that have been changed. It's never empty.
          */
         public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
+
+        /**
+         * Called when the client app's preferred features are changed.
+         * When this is called, it is recommended to {@link #getRoutes()} to get the routes
+         * that are currently available to the app.
+         *
+         * @param preferredFeatures the new preferred features set by the application
+         * @hide
+         */
+        @SystemApi
+        public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {}
     }
 
     /**
@@ -1131,6 +1241,11 @@
             mState = CONTROLLER_STATE_ACTIVE;
         }
 
+        RoutingController(@NonNull RoutingSessionInfo sessionInfo, int state) {
+            mSessionInfo = sessionInfo;
+            mState = state;
+        }
+
         /**
          * @return the ID of the controller. It is globally unique.
          */
@@ -1291,6 +1406,11 @@
                 return;
             }
 
+            if (isSystemRouter()) {
+                sManager.selectRoute(getRoutingSessionInfo(), route);
+                return;
+            }
+
             MediaRouter2Stub stub;
             synchronized (mLock) {
                 stub = mStub;
@@ -1338,6 +1458,11 @@
                 return;
             }
 
+            if (isSystemRouter()) {
+                sManager.deselectRoute(getRoutingSessionInfo(), route);
+                return;
+            }
+
             MediaRouter2Stub stub;
             synchronized (mLock) {
                 stub = mStub;
@@ -1407,6 +1532,12 @@
                 Log.w(TAG, "setVolume: Called on released controller. Ignoring.");
                 return;
             }
+
+            if (isSystemRouter()) {
+                sManager.setSessionVolume(getRoutingSessionInfo(), volume);
+                return;
+            }
+
             MediaRouter2Stub stub;
             synchronized (mLock) {
                 stub = mStub;
@@ -1471,6 +1602,11 @@
                 mState = CONTROLLER_STATE_RELEASED;
             }
 
+            if (isSystemRouter()) {
+                sManager.releaseSession(getRoutingSessionInfo());
+                return;
+            }
+
             synchronized (mLock) {
                 mNonSystemRoutingControllers.remove(getId(), this);
 
@@ -1539,6 +1675,12 @@
         }
 
         private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
+            if (isSystemRouter()) {
+                return getRoutes().stream()
+                        .filter(r -> routeIds.contains(r.getId()))
+                        .collect(Collectors.toList());
+            }
+
             synchronized (mLock) {
                 return routeIds.stream().map(mRoutes::get)
                         .filter(Objects::nonNull)
@@ -1722,12 +1864,17 @@
         }
     }
 
+    // Note: All methods are run on main thread.
     class ManagerCallback implements MediaRouter2Manager.Callback {
 
         @Override
         public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {
-            List<MediaRoute2Info> filteredRoutes =
-                    sManager.filterRoutesForPackage(routes, mClientPackageName);
+            updateAllRoutesFromManager();
+
+            List<MediaRoute2Info> filteredRoutes;
+            synchronized (mLock) {
+                filteredRoutes = filterRoutes(routes, mDiscoveryPreference);
+            }
             if (filteredRoutes.isEmpty()) {
                 return;
             }
@@ -1739,8 +1886,12 @@
 
         @Override
         public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {
-            List<MediaRoute2Info> filteredRoutes =
-                    sManager.filterRoutesForPackage(routes, mClientPackageName);
+            updateAllRoutesFromManager();
+
+            List<MediaRoute2Info> filteredRoutes;
+            synchronized (mLock) {
+                filteredRoutes = filterRoutes(routes, mDiscoveryPreference);
+            }
             if (filteredRoutes.isEmpty()) {
                 return;
             }
@@ -1752,8 +1903,12 @@
 
         @Override
         public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {
-            List<MediaRoute2Info> filteredRoutes =
-                    sManager.filterRoutesForPackage(routes, mClientPackageName);
+            updateAllRoutesFromManager();
+
+            List<MediaRoute2Info> filteredRoutes;
+            synchronized (mLock) {
+                filteredRoutes = filterRoutes(routes, mDiscoveryPreference);
+            }
             if (filteredRoutes.isEmpty()) {
                 return;
             }
@@ -1764,31 +1919,98 @@
         }
 
         @Override
-        public void onSessionUpdated(@NonNull RoutingSessionInfo session) {
-            // TODO: Call ControllerCallback.onControllerUpdated
-        }
-
-        @Override
         public void onTransferred(@NonNull RoutingSessionInfo oldSession,
-                @Nullable RoutingSessionInfo newSession) {
-            // TODO: Call TransferCallback.onTransfer
+                @NonNull RoutingSessionInfo newSession) {
+            if (!oldSession.isSystemSession()
+                    && !TextUtils.equals(mClientPackageName, oldSession.getClientPackageName())) {
+                return;
+            }
+
+            if (!newSession.isSystemSession()
+                    && !TextUtils.equals(mClientPackageName, newSession.getClientPackageName())) {
+                return;
+            }
+
+            // For successful in-session transfer, onControllerUpdated() handles it.
+            if (TextUtils.equals(oldSession.getId(), newSession.getId())) {
+                return;
+            }
+
+
+            RoutingController oldController;
+            if (oldSession.isSystemSession()) {
+                mSystemController.setRoutingSessionInfo(oldSession);
+                oldController = mSystemController;
+            } else {
+                oldController = new RoutingController(oldSession);
+            }
+
+            RoutingController newController;
+            if (oldSession.isSystemSession()) {
+                mSystemController.setRoutingSessionInfo(newSession);
+                newController = mSystemController;
+            } else {
+                newController = new RoutingController(newSession);
+            }
+
+            notifyTransfer(oldController, newController);
         }
 
         @Override
         public void onTransferFailed(@NonNull RoutingSessionInfo session,
                 @NonNull MediaRoute2Info route) {
-            // TODO: Call TransferCallback.onTransferFailure
+            if (!session.isSystemSession()
+                    && !TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+                return;
+            }
+            notifyTransferFailure(route);
+        }
+
+        @Override
+        public void onSessionUpdated(@NonNull RoutingSessionInfo session) {
+            if (!session.isSystemSession()
+                    && !TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+                return;
+            }
+
+            RoutingController controller;
+            if (session.isSystemSession()) {
+                mSystemController.setRoutingSessionInfo(session);
+                controller = mSystemController;
+            } else {
+                controller = new RoutingController(session);
+            }
+            notifyControllerUpdated(controller);
         }
 
         @Override
         public void onSessionReleased(@NonNull RoutingSessionInfo session) {
-            // TODO: Call TransferCallback.onStop()
+            if (session.isSystemSession()) {
+                Log.e(TAG, "onSessionReleased: Called on system session. Ignoring.");
+                return;
+            }
+
+            if (!TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+                return;
+            }
+
+            notifyStop(new RoutingController(session, RoutingController.CONTROLLER_STATE_RELEASED));
         }
 
         @Override
         public void onPreferredFeaturesChanged(@NonNull String packageName,
                 @NonNull List<String> preferredFeatures) {
-            // Does nothing.
+            if (!TextUtils.equals(mClientPackageName, packageName)) {
+                return;
+            }
+
+            synchronized (mLock) {
+                mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
+                        preferredFeatures, true).build();
+            }
+
+            updateAllRoutesFromManager();
+            notifyPreferredFeaturesChanged(preferredFeatures);
         }
 
         @Override
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index ca619d4..20e3573 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -148,7 +148,7 @@
 
     /**
      * Starts scanning remote routes.
-     * @see #stopScan(String)
+     * @see #stopScan()
      */
     public void startScan() {
         Client client = getOrCreateClient();
@@ -163,7 +163,7 @@
 
     /**
      * Stops scanning remote routes to reduce resource consumption.
-     * @see #startScan(String)
+     * @see #startScan()
      */
     public void stopScan() {
         Client client = getOrCreateClient();
@@ -788,8 +788,8 @@
      * Requests releasing a session.
      * <p>
      * If a session is released, any operation on the session will be ignored.
-     * {@link Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)} with {@code null}
-     * session will be called when the session is released.
+     * {@link Callback#onSessionReleased(RoutingSessionInfo)} will be called
+     * when the session is released.
      * </p>
      *
      * @see Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)
@@ -945,10 +945,10 @@
          * Called when media is transferred.
          *
          * @param oldSession the previous session
-         * @param newSession the new session or {@code null} if the session is released.
+         * @param newSession the new session
          */
         default void onTransferred(@NonNull RoutingSessionInfo oldSession,
-                @Nullable RoutingSessionInfo newSession) { }
+                @NonNull RoutingSessionInfo newSession) { }
 
         /**
          * Called when {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} fails.
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index b6fd286..6574353 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -54,7 +54,7 @@
     name: "SystemUI-sensors",
     srcs: [
         "src/com/android/systemui/util/sensors/ThresholdSensor.java",
-    ]
+    ],
 }
 
 android_library {
@@ -99,7 +99,7 @@
         "SystemUI-tags",
         "SystemUI-proto",
         "dagger2",
-        "jsr330"
+        "jsr330",
     ],
     manifest: "AndroidManifest.xml",
 
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 9e67e4b..d6204db 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -25,7 +25,10 @@
 
     name: "SystemUIPluginLib",
 
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "bcsmartspace/src/**/*.java",
+    ],
 
     static_libs: [
         "PluginCoreLib",
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
new file mode 100644
index 0000000..f8a9a045
--- /dev/null
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.os.Parcelable;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+import java.util.List;
+
+/**
+ * Interface to provide SmartspaceTargets to BcSmartspace.
+ */
+@ProvidesInterface(action = BcSmartspaceDataPlugin.ACTION, version = BcSmartspaceDataPlugin.VERSION)
+public interface BcSmartspaceDataPlugin extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA";
+    int VERSION = 1;
+
+    /** Register a listener to get Smartspace data. */
+    void registerListener(SmartspaceTargetListener listener);
+
+    /** Unregister a listener. */
+    void unregisterListener(SmartspaceTargetListener listener);
+
+    /** Provides Smartspace data to registered listeners. */
+    interface SmartspaceTargetListener {
+        /** Each Parcelable is a SmartspaceTarget that represents a card. */
+        void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets);
+    }
+}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 70be7c6..a8083f1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -851,8 +851,6 @@
     <string name="quick_settings_wifi_label">Wi-Fi</string>
     <!-- QuickSettings: Internet [CHAR LIMIT=NONE] -->
     <string name="quick_settings_internet_label">Internet</string>
-    <!-- QuickSettings: Airplane-safe [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_airplane_safe_label">Airplane-safe</string>
     <!-- QuickSettings: networks available [CHAR LIMIT=NONE] -->
     <string name="quick_settings_networks_available">Networks available</string>
     <!-- QuickSettings: networks unavailable [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index f98a959..09e9675a 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -41,7 +41,6 @@
     srcs: [
         "src/**/*.java",
         "src/**/I*.aidl",
-        ":wm_shell-aidls",
     ],
 
     static_libs: [
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IPinnedStackAnimationListener.aidl
similarity index 68%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl
rename to packages/SystemUI/shared/src/com/android/systemui/shared/recents/IPinnedStackAnimationListener.aidl
index 2569b78..97aa512 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IPinnedStackAnimationListener.aidl
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip;
+package com.android.systemui.shared.recents;
 
 /**
- * Listener interface that Launcher attaches to SystemUI to get Pip animation callbacks.
+ * Listener interface that Launcher attaches to SystemUI to get
+ * pinned stack animation callbacks.
  */
-oneway interface IPipAnimationListener {
+oneway interface IPinnedStackAnimationListener {
     /**
-     * Notifies the listener that the Pip animation is started.
+     * Notifies the pinned stack animation is started.
      */
-    void onPipAnimationStarted();
+    void onPinnedStackAnimationStarted();
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
similarity index 83%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl
rename to packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
index faab4c2..54242be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
@@ -14,20 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.splitscreen;
+package com.android.systemui.shared.recents;
 
 /**
  * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
  */
 oneway interface ISplitScreenListener {
-
-    /**
-     * Called when the stage position changes.
-     */
     void onStagePositionChanged(int stage, int position);
-
-    /**
-     * Called when a task changes stages.
-     */
     void onTaskStageChanged(int taskId, int stage, boolean visible);
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IStartingWindowListener.aidl
similarity index 90%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl
rename to packages/SystemUI/shared/src/com/android/systemui/shared/recents/IStartingWindowListener.aidl
index f562c8f..eb3e60c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IStartingWindowListener.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.startingsurface;
+package com.android.systemui.shared.recents;
 
 /**
  * Listener interface that Launcher attaches to SystemUI to get
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 3da3085..5126284 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -16,6 +16,11 @@
 
 package com.android.systemui.shared.recents;
 
+import android.app.PendingIntent;
+import android.app.PictureInPictureParams;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.graphics.Bitmap;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -23,15 +28,26 @@
 import android.os.UserHandle;
 import android.view.MotionEvent;
 
+import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
+import com.android.systemui.shared.recents.ISplitScreenListener;
+import com.android.systemui.shared.recents.IStartingWindowListener;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.RemoteTransitionCompat;
 
 /**
  * Temporary callbacks into SystemUI.
+ * Next id = 44
  */
 interface ISystemUiProxy {
 
     /**
+     * Proxies SurfaceControl.screenshotToBuffer().
+     * @Removed
+     * GraphicBufferCompat screenshot(in Rect sourceCrop, int width, int height, int minLayer,
+     *             int maxLayer, boolean useIdentityTransform, int rotation) = 0;
+     */
+
+    /**
      * Begins screen pinning on the provided {@param taskId}.
      */
     void startScreenPinning(int taskId) = 1;
@@ -99,6 +115,11 @@
     void stopScreenPinning() = 17;
 
     /**
+     * Sets the shelf height and visibility.
+     */
+    void setShelfHeight(boolean visible, int shelfHeight) = 20;
+
+    /**
      * Handle the provided image as if it was a screenshot.
      *
      * Deprecated, use handleImageBundleAsScreenshot with image bundle and UserTask
@@ -118,12 +139,27 @@
     void notifySwipeToHomeFinished() = 23;
 
     /**
+     * Sets listener to get pinned stack animation callbacks.
+     */
+    void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) = 24;
+
+    /**
      * Notifies that quickstep will switch to a new task
      * @param rotation indicates which Surface.Rotation the gesture was started in
      */
     void onQuickSwitchToNewTask(int rotation) = 25;
 
     /**
+     * Start the one-handed mode.
+     */
+    void startOneHandedMode() = 26;
+
+    /**
+     * Stop the one-handed mode.
+     */
+    void stopOneHandedMode() = 27;
+
+    /**
      * Handle the provided image as if it was a screenshot.
      */
     void handleImageBundleAsScreenshot(in Bundle screenImageBundle, in Rect locationInScreen,
@@ -134,5 +170,88 @@
      */
     void expandNotificationPanel() = 29;
 
-    // Next id = 44
+    /**
+     * Notifies that Activity is about to be swiped to home with entering PiP transition and
+     * queries the destination bounds for PiP depends on Launcher's rotation and shelf height.
+     *
+     * @param componentName ComponentName represents the Activity
+     * @param activityInfo ActivityInfo tied to the Activity
+     * @param pictureInPictureParams PictureInPictureParams tied to the Activity
+     * @param launcherRotation Launcher rotation to calculate the PiP destination bounds
+     * @param shelfHeight Shelf height of launcher to calculate the PiP destination bounds
+     * @return destination bounds the PiP window should land into
+     */
+    Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo,
+                in PictureInPictureParams pictureInPictureParams,
+                int launcherRotation, int shelfHeight) = 30;
+
+    /**
+     * Notifies the swiping Activity to PiP onto home transition is finished
+     *
+     * @param componentName ComponentName represents the Activity
+     * @param destinationBounds the destination bounds the PiP window lands into
+     */
+    void stopSwipePipToHome(in ComponentName componentName, in Rect destinationBounds) = 31;
+
+    /**
+     * Registers a RemoteTransitionCompat that will handle transitions. This parameter bundles an
+     * IRemoteTransition and a filter that must pass for it.
+     */
+    void registerRemoteTransition(in RemoteTransitionCompat remoteTransition) = 32;
+
+    /** Unegisters a RemoteTransitionCompat that will handle transitions. */
+    void unregisterRemoteTransition(in RemoteTransitionCompat remoteTransition) = 33;
+
+// SplitScreen APIs...copied from SplitScreen.java
+    /**
+     * Stage position isn't specified normally meaning to use what ever it is currently set to.
+     */
+    //int STAGE_POSITION_UNDEFINED = -1;
+    /**
+     * Specifies that a stage is positioned at the top half of the screen if
+     * in portrait mode or at the left half of the screen if in landscape mode.
+     */
+    //int STAGE_POSITION_TOP_OR_LEFT = 0;
+    /**
+     * Specifies that a stage is positioned at the bottom half of the screen if
+     * in portrait mode or at the right half of the screen if in landscape mode.
+     */
+    //int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+
+    /**
+     * Stage type isn't specified normally meaning to use what ever the default is.
+     * E.g. exit split-screen and launch the app in fullscreen.
+     */
+    //int STAGE_TYPE_UNDEFINED = -1;
+    /**
+     * The main stage type.
+     * @see MainStage
+     */
+    //int STAGE_TYPE_MAIN = 0;
+    /**
+     * The side stage type.
+     * @see SideStage
+     */
+    //int STAGE_TYPE_SIDE = 1;
+
+    void registerSplitScreenListener(in ISplitScreenListener listener) = 34;
+    void unregisterSplitScreenListener(in ISplitScreenListener listener) = 35;
+
+    /** Hides the side-stage if it is currently visible. */
+    void setSideStageVisibility(in boolean visible) = 36;
+    /** Removes the split-screen stages. */
+    void exitSplitScreen() = 37;
+    /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */
+    void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 38;
+    void startTask(in int taskId, in int stage, in int position, in Bundle options) = 39;
+    void startShortcut(in String packageName, in String shortcutId, in int stage, in int position,
+            in Bundle options, in UserHandle user) = 40;
+    void startIntent(
+            in PendingIntent intent, in Intent fillInIntent, in int stage, in int position,
+            in Bundle options) = 41;
+    void removeFromSideStage(in int taskId) = 42;
+    /**
+     * Sets listener to get task launching callbacks.
+     */
+    void setStartingWindowListener(IStartingWindowListener listener) = 43;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 41840af..937c1df 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -41,18 +41,6 @@
     public static final String KEY_EXTRA_INPUT_MONITOR = "extra_input_monitor";
     public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
     public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners";
-    // See IPip.aidl
-    public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
-    // See ISplitScreen.aidl
-    public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
-    // See IOneHanded.aidl
-    public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
-    // See IShellTransitions.aidl
-    public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
-            "extra_shell_shell_transitions";
-    // See IStartingWindow.aidl
-    public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
-            "extra_shell_starting_window";
 
     public static final String NAV_BAR_MODE_2BUTTON_OVERLAY =
             WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index ed3d5ec..8f79de5 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -34,7 +34,7 @@
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.transition.ShellTransitions;
+import com.android.wm.shell.transition.RemoteTransitions;
 
 import java.util.Optional;
 
@@ -87,7 +87,7 @@
         Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump);
 
         @BindsInstance
-        Builder setTransitions(ShellTransitions t);
+        Builder setTransitions(RemoteTransitions t);
 
         @BindsInstance
         Builder setStartingSurface(Optional<StartingSurface> s);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index bbd95b4..1b77d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -33,7 +33,7 @@
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.transition.ShellTransitions;
+import com.android.wm.shell.transition.RemoteTransitions;
 
 import java.util.Optional;
 
@@ -98,7 +98,7 @@
     Optional<TaskViewFactory> getTaskViewFactory();
 
     @WMSingleton
-    ShellTransitions getTransitions();
+    RemoteTransitions getTransitions();
 
     @WMSingleton
     Optional<StartingSurface> getStartingSurface();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index b471659..c3a523f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -35,6 +35,7 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_FORCE_OPAQUE;
 import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
@@ -85,6 +86,7 @@
 import android.util.Log;
 import android.view.Display;
 import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
 import android.view.IWindowManager;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.KeyEvent;
@@ -213,6 +215,7 @@
 
     private boolean mAllowForceNavBarHandleOpaque;
     private boolean mForceNavBarHandleOpaque;
+    private Optional<Long> mHomeButtonLongPressDurationMs;
     private boolean mIsCurrentUserSetup;
 
     /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
@@ -384,6 +387,14 @@
 
     private final Runnable mAutoDim = () -> getBarTransitions().setAutoDim(true);
 
+    private final Runnable mOnVariableDurationHomeLongClick = () -> {
+        if (onHomeLongClick(mNavigationBarView.getHomeButton().getCurrentView())) {
+            mNavigationBarView.getHomeButton().getCurrentView().performHapticFeedback(
+                    HapticFeedbackConstants.LONG_PRESS,
+                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+        }
+    };
+
     private final ContentObserver mAssistContentObserver = new ContentObserver(
             new Handler(Looper.getMainLooper())) {
         @Override
@@ -405,6 +416,13 @@
                         mForceNavBarHandleOpaque = properties.getBoolean(
                                 NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true);
                     }
+
+                    if (properties.getKeyset().contains(HOME_BUTTON_LONG_PRESS_DURATION_MS)) {
+                        mHomeButtonLongPressDurationMs = Optional.of(
+                            properties.getLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 0)
+                        ).filter(duration -> duration != 0);
+                        reconfigureHomeLongClick();
+                    }
                 }
             };
 
@@ -523,6 +541,11 @@
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 NAV_BAR_HANDLE_FORCE_OPAQUE,
                 /* defaultValue = */ true);
+        mHomeButtonLongPressDurationMs = Optional.of(DeviceConfig.getLong(
+            DeviceConfig.NAMESPACE_SYSTEMUI,
+            HOME_BUTTON_LONG_PRESS_DURATION_MS,
+            /* defaultValue = */ 0
+        )).filter(duration -> duration != 0);
         DeviceConfig.addOnPropertiesChangedListener(
                 DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
 
@@ -782,6 +805,22 @@
         }
     }
 
+    private void reconfigureHomeLongClick() {
+        if (mNavigationBarView == null
+                || mNavigationBarView.getHomeButton().getCurrentView() == null) {
+            return;
+        }
+        if (mHomeButtonLongPressDurationMs.isPresent()) {
+            mNavigationBarView.getHomeButton().getCurrentView().setLongClickable(false);
+            mNavigationBarView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(false);
+            mNavigationBarView.getHomeButton().setOnLongClickListener(null);
+        } else {
+            mNavigationBarView.getHomeButton().getCurrentView().setLongClickable(true);
+            mNavigationBarView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(true);
+            mNavigationBarView.getHomeButton().setOnLongClickListener(this::onHomeLongClick);
+        }
+    }
+
     private int deltaRotation(int oldRotation, int newRotation) {
         int delta = newRotation - oldRotation;
         if (delta < 0) delta += 4;
@@ -792,6 +831,7 @@
         pw.println("NavigationBar (displayId=" + mDisplayId + "):");
         pw.println("  mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation);
         pw.println("  mCurrentRotation=" + mCurrentRotation);
+        pw.println("  mHomeButtonLongPressDurationMs=" + mHomeButtonLongPressDurationMs);
 
         if (mNavigationBarView != null) {
             pw.println("  mNavigationBarWindowState="
@@ -1121,7 +1161,8 @@
 
         ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
         homeButton.setOnTouchListener(this::onHomeTouch);
-        homeButton.setOnLongClickListener(this::onHomeLongClick);
+
+        reconfigureHomeLongClick();
 
         ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
         accessibilityButton.setOnClickListener(this::onAccessibilityClick);
@@ -1131,7 +1172,8 @@
         updateScreenPinningGestures();
     }
 
-    private boolean onHomeTouch(View v, MotionEvent event) {
+    @VisibleForTesting
+    boolean onHomeTouch(View v, MotionEvent event) {
         if (mHomeBlockedThisTouch && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
             return true;
         }
@@ -1151,9 +1193,13 @@
                         return true;
                     }
                 }
+                mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> {
+                    mHandler.postDelayed(mOnVariableDurationHomeLongClick, longPressDuration);
+                });
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
+                mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
                 mStatusBarLazy.get().awakenDreams();
                 break;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 14a3fc0..1a17e61 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -416,7 +416,6 @@
                 }
             } else {
                 state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
-                state.label = r.getString(R.string.quick_settings_airplane_safe_label);
             }
         } else if (cb.mNoDefaultNetwork && cb.mNoNetworksAvailable) {
             state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
@@ -480,9 +479,6 @@
             state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
             state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
         } else {
-            if (cb.mAirplaneModeEnabled) {
-                state.label = r.getString(R.string.quick_settings_airplane_safe_label);
-            }
             state.icon = new SignalIcon(cb.mMobileSignalIconId);
             state.secondaryLabel = appendMobileDataType(cb.mDataSubscriptionName,
                     getMobileDataContentName(cb));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index b0a3f43..8951605 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -25,11 +25,6 @@
 
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
@@ -40,12 +35,15 @@
 
 import android.annotation.FloatRange;
 import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.PictureInPictureParams;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
 import android.graphics.Bitmap;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -59,12 +57,14 @@
 import android.os.PatternMatcher;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.Log;
 import android.view.InputMonitor;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.accessibility.AccessibilityManager;
+import android.window.IRemoteTransition;
 
 import androidx.annotation.NonNull;
 
@@ -83,11 +83,15 @@
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
+import com.android.systemui.shared.recents.ISplitScreenListener;
+import com.android.systemui.shared.recents.IStartingWindowListener;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.RemoteTransitionCompat;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -99,7 +103,7 @@
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.transition.ShellTransitions;
+import com.android.wm.shell.transition.RemoteTransitions;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -107,6 +111,7 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 
@@ -146,11 +151,12 @@
     private final ScreenshotHelper mScreenshotHelper;
     private final Optional<OneHanded> mOneHandedOptional;
     private final CommandQueue mCommandQueue;
-    private final ShellTransitions mShellTransitions;
+    private final RemoteTransitions mShellTransitions;
     private final Optional<StartingSurface> mStartingSurface;
 
     private Region mActiveNavBarRegion;
 
+    private IPinnedStackAnimationListener mIPinnedStackAnimationListener;
     private IOverviewProxy mOverviewProxy;
     private int mConnectionBackoffAttempts;
     private boolean mBound;
@@ -163,6 +169,8 @@
     private float mWindowCornerRadius;
     private boolean mSupportsRoundedCornersOnWindows;
     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
+    private final ArraySet<IRemoteTransition> mRemoteTransitions = new ArraySet<>();
+    private IStartingWindowListener mIStartingWindowListener;
 
     @VisibleForTesting
     public ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@@ -375,6 +383,20 @@
         }
 
         @Override
+        public void setShelfHeight(boolean visible, int shelfHeight) {
+            if (!verifyCaller("setShelfHeight")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mPipOptional.ifPresent(
+                        pip -> pip.setShelfHeight(visible, shelfHeight));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
                 Insets visibleInsets, int taskId) {
             // Deprecated
@@ -402,6 +424,36 @@
         }
 
         @Override
+        public void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) {
+            if (!verifyCaller("setPinnedStackAnimationListener")) {
+                return;
+            }
+            mIPinnedStackAnimationListener = listener;
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mPipOptional.ifPresent(
+                        pip -> pip.setPinnedStackAnimationListener(mPinnedStackAnimationCallback));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setStartingWindowListener(IStartingWindowListener listener) {
+            if (!verifyCaller("setStartingWindowListener")) {
+                return;
+            }
+            mIStartingWindowListener = listener;
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mStartingSurface.ifPresent(s ->
+                        s.setStartingWindowListener(mStartingWindowListener));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) {
             if (!verifyCaller("onQuickSwitchToNewTask")) {
                 return;
@@ -415,6 +467,32 @@
         }
 
         @Override
+        public void startOneHandedMode() {
+            if (!verifyCaller("startOneHandedMode")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mOneHandedOptional.ifPresent(oneHanded -> oneHanded.startOneHanded());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void stopOneHandedMode()  {
+            if (!verifyCaller("stopOneHandedMode")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mOneHandedOptional.ifPresent(oneHanded -> oneHanded.stopOneHanded());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
                 Insets visibleInsets, Task.TaskKey task) {
             mScreenshotHelper.provideScreenshot(
@@ -442,6 +520,190 @@
             }
         }
 
+        @Override
+        public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+                PictureInPictureParams pictureInPictureParams,
+                int launcherRotation, int shelfHeight) {
+            if (!verifyCaller("startSwipePipToHome")) {
+                return null;
+            }
+            final long binderToken = Binder.clearCallingIdentity();
+            try {
+                return mPipOptional.map(pip ->
+                        pip.startSwipePipToHome(componentName, activityInfo,
+                                pictureInPictureParams, launcherRotation, shelfHeight))
+                        .orElse(null);
+            } finally {
+                Binder.restoreCallingIdentity(binderToken);
+            }
+        }
+
+        @Override
+        public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
+            if (!verifyCaller("stopSwipePipToHome")) {
+                return;
+            }
+            final long binderToken = Binder.clearCallingIdentity();
+            try {
+                mPipOptional.ifPresent(pip -> pip.stopSwipePipToHome(
+                        componentName, destinationBounds));
+            } finally {
+                Binder.restoreCallingIdentity(binderToken);
+            }
+        }
+
+        @Override
+        public void registerRemoteTransition(RemoteTransitionCompat remoteTransition) {
+            if (!verifyCaller("registerRemoteTransition")) return;
+            final long binderToken = Binder.clearCallingIdentity();
+            try {
+                mRemoteTransitions.add(remoteTransition.getTransition());
+                mShellTransitions.registerRemote(
+                        remoteTransition.getFilter(), remoteTransition.getTransition());
+            } finally {
+                Binder.restoreCallingIdentity(binderToken);
+            }
+        }
+
+        @Override
+        public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) {
+            if (!verifyCaller("registerRemoteTransition")) return;
+            final long binderToken = Binder.clearCallingIdentity();
+            try {
+                mRemoteTransitions.remove(remoteTransition.getTransition());
+                mShellTransitions.unregisterRemote(remoteTransition.getTransition());
+            } finally {
+                Binder.restoreCallingIdentity(binderToken);
+            }
+        }
+
+        @Override
+        public void registerSplitScreenListener(ISplitScreenListener listener) {
+            if (!verifyCaller("registerSplitScreenListener")) {
+                return;
+            }
+            mISplitScreenListener = listener;
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(
+                        s -> s.registerSplitScreenListener(mSplitScreenListener));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void unregisterSplitScreenListener(ISplitScreenListener listener) {
+            if (!verifyCaller("unregisterSplitScreenListener")) {
+                return;
+            }
+            mISplitScreenListener = null;
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(
+                        s -> s.unregisterSplitScreenListener(mSplitScreenListener));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setSideStageVisibility(boolean visible) {
+            if (!verifyCaller("setSideStageVisibility")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(s -> s.setSideStageVisibility(visible));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void exitSplitScreen() {
+            if (!verifyCaller("exitSplitScreen")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(s -> s.exitSplitScreen());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+            if (!verifyCaller("exitSplitScreenOnHide")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(s -> s.exitSplitScreenOnHide(exitSplitScreenOnHide));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void startTask(int taskId, int stage, int position, Bundle options) {
+            if (!verifyCaller("startTask")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(
+                        s -> s.startTask(taskId, stage, position, options));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void startShortcut(String packageName, String shortcutId, int stage, int position,
+                Bundle options, UserHandle user) {
+            if (!verifyCaller("startShortcut")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(s ->
+                        s.startShortcut(packageName, shortcutId, stage, position, options, user));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void startIntent(PendingIntent intent, Intent fillInIntent,
+                int stage, int position, Bundle options) {
+            if (!verifyCaller("startIntent")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(s ->
+                        s.startIntent(intent, mContext, fillInIntent, stage, position, options));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void removeFromSideStage(int taskId) {
+            if (!verifyCaller("removeFromSideStage")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(
+                        s -> s.removeFromSideStage(taskId));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         private boolean verifyCaller(String reason) {
             final int callerId = Binder.getCallingUserHandle().getIdentifier();
             if (callerId != mCurrentBoundedUserId) {
@@ -495,22 +757,6 @@
             params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
             params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
             params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
-
-            mPipOptional.ifPresent((pip) -> params.putBinder(
-                    KEY_EXTRA_SHELL_PIP,
-                    pip.createExternalInterface().asBinder()));
-            mSplitScreenOptional.ifPresent((splitscreen) -> params.putBinder(
-                    KEY_EXTRA_SHELL_SPLIT_SCREEN,
-                    splitscreen.createExternalInterface().asBinder()));
-            mOneHandedOptional.ifPresent((onehanded) -> params.putBinder(
-                    KEY_EXTRA_SHELL_ONE_HANDED,
-                    onehanded.createExternalInterface().asBinder()));
-            params.putBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
-                    mShellTransitions.createExternalInterface().asBinder());
-            mStartingSurface.ifPresent((startingwindow) -> params.putBinder(
-                    KEY_EXTRA_SHELL_STARTING_WINDOW,
-                    startingwindow.createExternalInterface().asBinder()));
-
             try {
                 mOverviewProxy.onInitialize(params);
             } catch (RemoteException e) {
@@ -550,11 +796,42 @@
     private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged;
     private final BiConsumer<Rect, Rect> mSplitScreenBoundsChangeListener =
             this::notifySplitScreenBoundsChanged;
+    private final Consumer<Boolean> mPinnedStackAnimationCallback =
+            this::notifyPinnedStackAnimationStarted;
+
+    private final BiConsumer<Integer, Integer> mStartingWindowListener =
+            this::notifyTaskLaunching;
 
     // This is the death handler for the binder from the launcher service
     private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
             = this::cleanupAfterDeath;
 
+    private ISplitScreenListener mISplitScreenListener;
+    private final SplitScreen.SplitScreenListener mSplitScreenListener =
+            new SplitScreen.SplitScreenListener() {
+        @Override
+        public void onStagePositionChanged(int stage, int position) {
+            try {
+                if (mISplitScreenListener != null) {
+                    mISplitScreenListener.onStagePositionChanged(stage, position);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG_OPS, "onStagePositionChanged", e);
+            }
+        }
+
+        @Override
+        public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+            try {
+                if (mISplitScreenListener != null) {
+                    mISplitScreenListener.onTaskStageChanged(taskId, stage, visible);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG_OPS, "onTaskStageChanged", e);
+            }
+        }
+    };
+
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     @Inject
     public OverviewProxyService(Context context, CommandQueue commandQueue,
@@ -567,7 +844,7 @@
             Optional<Lazy<StatusBar>> statusBarOptionalLazy,
             Optional<OneHanded> oneHandedOptional,
             BroadcastDispatcher broadcastDispatcher,
-            ShellTransitions shellTransitions,
+            RemoteTransitions shellTransitions,
             Optional<StartingSurface> startingSurface) {
         super(broadcastDispatcher);
         mContext = context;
@@ -684,6 +961,29 @@
         }
     }
 
+    private void notifyPinnedStackAnimationStarted(Boolean isAnimationStarted) {
+        if (mIPinnedStackAnimationListener == null) {
+            return;
+        }
+        try {
+            mIPinnedStackAnimationListener.onPinnedStackAnimationStarted();
+        } catch (RemoteException e) {
+            Log.e(TAG_OPS, "Failed to call onPinnedStackAnimationStarted()", e);
+        }
+    }
+
+    private void notifyTaskLaunching(int taskId, int supportedType) {
+        if (mIStartingWindowListener == null) {
+            return;
+        }
+
+        try {
+            mIStartingWindowListener.onTaskLaunching(taskId, supportedType);
+        } catch (RemoteException e) {
+            Log.e(TAG_OPS, "Failed to call notifyTaskLaunching()", e);
+        }
+    }
+
     private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
             boolean bouncerShowing) {
         mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
@@ -727,6 +1027,12 @@
         // Clean up the minimized state if launcher dies
         mLegacySplitScreenOptional.ifPresent(
                 splitScreen -> splitScreen.setMinimized(false));
+
+        // Clean up any registered remote transitions
+        for (int i = mRemoteTransitions.size() - 1; i >= 0; --i) {
+            mShellTransitions.unregisterRemote(mRemoteTransitions.valueAt(i));
+        }
+        mRemoteTransitions.clear();
     }
 
     public void startConnectionToCurrentUser() {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index ddfa63a..3f4ec85 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -81,7 +81,7 @@
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.startingsurface.StartingSurface;
 import com.android.wm.shell.startingsurface.StartingWindowController;
-import com.android.wm.shell.transition.ShellTransitions;
+import com.android.wm.shell.transition.RemoteTransitions;
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.Optional;
@@ -399,8 +399,8 @@
 
     @WMSingleton
     @Provides
-    static ShellTransitions provideRemoteTransitions(Transitions transitions) {
-        return transitions.asRemoteTransitions();
+    static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
+        return Transitions.asRemoteTransitions(transitions);
     }
 
     @WMSingleton
@@ -509,33 +509,27 @@
 
     @WMSingleton
     @Provides
-    static ShellInit provideShellInit(ShellInitImpl impl) {
-        return impl.asShellInit();
-    }
-
-    @WMSingleton
-    @Provides
-    static ShellInitImpl provideShellInitImpl(DisplayImeController displayImeController,
+    static ShellInit provideShellInit(DisplayImeController displayImeController,
             DragAndDropController dragAndDropController,
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<AppPairsController> appPairsOptional,
+            Optional<StartingSurface> startingSurface,
             Optional<PipTouchHandler> pipTouchHandlerOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Transitions transitions,
-            StartingWindowController startingWindow,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new ShellInitImpl(displayImeController,
+        return ShellInitImpl.create(displayImeController,
                 dragAndDropController,
                 shellTaskOrganizer,
                 legacySplitScreenOptional,
                 splitScreenOptional,
                 appPairsOptional,
+                startingSurface,
                 pipTouchHandlerOptional,
                 fullscreenTaskListener,
                 transitions,
-                startingWindow,
                 mainExecutor);
     }
 
@@ -545,13 +539,7 @@
      */
     @WMSingleton
     @Provides
-    static Optional<ShellCommandHandler> provideShellCommandHandler(ShellCommandHandlerImpl impl) {
-        return Optional.of(impl.asShellCommandHandler());
-    }
-
-    @WMSingleton
-    @Provides
-    static ShellCommandHandlerImpl provideShellCommandHandlerImpl(
+    static Optional<ShellCommandHandler> provideShellCommandHandler(
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
@@ -560,8 +548,8 @@
             Optional<HideDisplayCutoutController> hideDisplayCutout,
             Optional<AppPairsController> appPairsOptional,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new ShellCommandHandlerImpl(shellTaskOrganizer,
+        return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
                 legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
-                hideDisplayCutout, appPairsOptional, mainExecutor);
+                hideDisplayCutout, appPairsOptional, mainExecutor));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 2b76f1c..22c553b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -23,6 +23,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
 import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
 
 import static org.junit.Assert.assertEquals;
@@ -31,6 +32,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -44,12 +46,16 @@
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.MotionEvent;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
 import android.view.accessibility.AccessibilityManager;
@@ -79,6 +85,7 @@
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.pip.Pip;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -101,6 +108,7 @@
     private OverviewProxyService mOverviewProxyService;
     private CommandQueue mCommandQueue;
     private SysUiState mMockSysUiState;
+    @Mock
     private Handler mHandler;
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
@@ -124,12 +132,17 @@
         mDependency.injectMockDependency(NavigationBarController.class);
         mOverviewProxyService = mDependency.injectMockDependency(OverviewProxyService.class);
         TestableLooper.get(this).runWithLooper(() -> {
-            mHandler = new Handler();
             mNavigationBar = createNavBar(mContext);
             mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
         });
     }
 
+    @After
+    public void tearDown() throws Exception {
+        DeviceConfig.resetToDefaults(
+                Settings.RESET_MODE_PACKAGE_DEFAULTS, DeviceConfig.NAMESPACE_SYSTEMUI);
+    }
+
     private void setupSysuiDependency() {
         Display display = new Display(DisplayManagerGlobal.getInstance(), EXTERNAL_DISPLAY_ID,
                 new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
@@ -163,6 +176,32 @@
     }
 
     @Test
+    public void testHomeLongPressWithCustomDuration() throws Exception {
+        DeviceConfig.setProperties(
+                new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_SYSTEMUI)
+                    .setLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 100)
+                    .build());
+        mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null));
+
+        mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain(
+                /*downTime=*/SystemClock.uptimeMillis(),
+                /*eventTime=*/SystemClock.uptimeMillis(),
+                /*action=*/MotionEvent.ACTION_DOWN,
+                0, 0, 0
+        ));
+        verify(mHandler, times(1)).postDelayed(any(), eq(100L));
+
+        mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain(
+                /*downTime=*/SystemClock.uptimeMillis(),
+                /*eventTime=*/SystemClock.uptimeMillis(),
+                /*action=*/MotionEvent.ACTION_UP,
+                0, 0, 0
+        ));
+
+        verify(mHandler, times(1)).removeCallbacks(any());
+    }
+
+    @Test
     public void testRegisteredWithDispatcher() {
         mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null));
         verify(mBroadcastDispatcher).registerReceiverWithHandler(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
new file mode 100644
index 0000000..25104b8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.startingsurface.StartingSurface;
+import com.android.wm.shell.transition.RemoteTransitions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+import dagger.Lazy;
+
+/**
+ * Unit tests for {@link com.android.systemui.recents.OverviewProxyService}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class OverviewProxyServiceTest extends SysuiTestCase {
+    private OverviewProxyService mSpiedOverviewProxyService;
+    private TestableContext mSpiedContext;
+
+    @Mock private BroadcastDispatcher mMockBroadcastDispatcher;
+    @Mock private CommandQueue mMockCommandQueue;
+    @Mock private Lazy<NavigationBarController> mMockNavBarControllerLazy;
+    @Mock private IPinnedStackAnimationListener mMockPinnedStackAnimationListener;
+    @Mock private NavigationModeController mMockNavModeController;
+    @Mock private NotificationShadeWindowController mMockStatusBarWinController;
+    @Mock private Optional<Pip> mMockPipOptional;
+    @Mock private Optional<LegacySplitScreen> mMockLegacySplitScreenOptional;
+    @Mock private Optional<SplitScreen> mMockSplitScreenOptional;
+    @Mock private Optional<Lazy<StatusBar>> mMockStatusBarOptionalLazy;
+    @Mock private Optional<com.android.wm.shell.onehanded.OneHanded> mMockOneHandedOptional;
+    @Mock private PackageManager mPackageManager;
+    @Mock private SysUiState mMockSysUiState;
+    @Mock private RemoteTransitions mMockTransitions;
+    @Mock private Optional<StartingSurface> mStartingSurface;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+
+        mSpiedContext = spy(mContext);
+
+        when(mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
+        when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
+
+        mSpiedOverviewProxyService = spy(new OverviewProxyService(mSpiedContext, mMockCommandQueue,
+                mMockNavBarControllerLazy, mMockNavModeController, mMockStatusBarWinController,
+                mMockSysUiState, mMockPipOptional, mMockLegacySplitScreenOptional,
+                mMockSplitScreenOptional, mMockStatusBarOptionalLazy, mMockOneHandedOptional,
+                mMockBroadcastDispatcher, mMockTransitions, mStartingSurface));
+    }
+
+    @Test
+    public void testNonPipDevice_shouldNotNotifySwipeToHomeFinished() throws RemoteException {
+        mSpiedOverviewProxyService.mSysUiProxy.notifySwipeToHomeFinished();
+
+        verify(mMockPipOptional, never()).ifPresent(any());
+    }
+
+    @Test
+    public void testNonPipDevice_shouldNotSetPinnedStackAnimationListener() throws RemoteException {
+        mSpiedOverviewProxyService.mSysUiProxy.setPinnedStackAnimationListener(
+                mMockPinnedStackAnimationListener);
+
+        verify(mMockPipOptional, never()).ifPresent(any());
+    }
+
+    @Test
+    public void testNonPipDevice_shouldNotSetShelfHeight() throws RemoteException {
+        mSpiedOverviewProxyService.mSysUiProxy.setShelfHeight(true /* visible */,
+                100 /* shelfHeight */);
+
+        verify(mMockPipOptional, never()).ifPresent(any());
+    }
+}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index b00689b..0fe874c 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -81,10 +81,19 @@
     out: ["services.core.protolog.json"],
 }
 
+genrule {
+    name: "statslog-art-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module art" +
+         " --javaPackage com.android.internal.art --javaClass ArtStatsLog --worksource",
+    out: ["com/android/internal/art/ArtStatsLog.java"],
+}
+
 java_library_static {
     name: "services.core.unboosted",
     defaults: ["platform_service_defaults"],
     srcs: [
+        ":statslog-art-java-gen",
         ":services.core-sources",
         ":services.core.protologsrc",
         ":dumpstate_aidl",
diff --git a/services/core/java/com/android/server/ContextHubSystemService.java b/services/core/java/com/android/server/ContextHubSystemService.java
index a353519..96ff900 100644
--- a/services/core/java/com/android/server/ContextHubSystemService.java
+++ b/services/core/java/com/android/server/ContextHubSystemService.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.util.Log;
 
@@ -51,4 +53,9 @@
             publishBinderService(Context.CONTEXTHUB_SERVICE, mContextHubService);
         }
     }
+
+    @Override
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        mContextHubService.onUserChanged();
+    }
 }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index dde45c4..c44089b 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -17,13 +17,16 @@
 package com.android.server.location.contexthub;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
+import android.hardware.SensorPrivacyManagerInternal;
 import android.hardware.contexthub.V1_0.AsyncEventType;
 import android.hardware.contexthub.V1_0.ContextHub;
 import android.hardware.contexthub.V1_0.ContextHubMsg;
@@ -59,6 +62,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.DumpUtils;
+import com.android.server.LocalServices;
 import com.android.server.location.ContextHubServiceProto;
 
 import java.io.FileDescriptor;
@@ -127,6 +131,8 @@
     // Lock object for sendWifiSettingUpdate()
     private final Object mSendWifiSettingUpdateLock = new Object();
 
+    private final SensorPrivacyManagerInternal mSensorPrivacyManagerInternal;
+
     /**
      * Class extending the callback to register with a Context Hub.
      */
@@ -186,6 +192,7 @@
         if (mContextHubWrapper == null) {
             mTransactionManager = null;
             mClientManager = null;
+            mSensorPrivacyManagerInternal = null;
             mDefaultClientMap = Collections.emptyMap();
             mContextHubIdToInfoMap = Collections.emptyMap();
             mSupportedContextHubPerms = Collections.emptyList();
@@ -208,6 +215,8 @@
         mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
         mTransactionManager = new ContextHubTransactionManager(
                 mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
+        mSensorPrivacyManagerInternal =
+                LocalServices.getService(SensorPrivacyManagerInternal.class);
 
         HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
         for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
@@ -284,18 +293,16 @@
         }
 
         if (mContextHubWrapper.supportsMicrophoneDisableSettingNotifications()) {
-            sendMicrophoneDisableSettingUpdate();
+            sendMicrophoneDisableSettingUpdateForCurrentUser();
 
-            SensorPrivacyManager.OnSensorPrivacyChangedListener listener =
-                    new SensorPrivacyManager.OnSensorPrivacyChangedListener() {
-                    @Override
-                    public void onSensorPrivacyChanged(boolean enabled) {
-                        sendMicrophoneDisableSettingUpdate();
-                    }
-                };
-            SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext);
-            manager.addSensorPrivacyListener(
-                    SensorPrivacyManager.Sensors.MICROPHONE, listener);
+            mSensorPrivacyManagerInternal.addSensorPrivacyListenerForAllUsers(
+                    SensorPrivacyManager.Sensors.MICROPHONE, (userId, enabled) -> {
+                        if (userId == getCurrentUserId()) {
+                            Log.d(TAG, "User: " + userId + " enabled: " + enabled);
+                            sendMicrophoneDisableSettingUpdate(enabled);
+                        }
+                });
+
         }
     }
 
@@ -1074,19 +1081,48 @@
     }
 
     /**
-     * Obtains the latest microphone disable setting value and notifies the
-     * Context Hub.
+     * Notifies a microphone disable settings change to the Context Hub.
      */
-    private void sendMicrophoneDisableSettingUpdate() {
-        SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext);
-        boolean disabled = manager.isSensorPrivacyEnabled(
-                SensorPrivacyManager.Sensors.MICROPHONE);
-        Log.d(TAG, "Mic Disabled Setting: " + disabled);
-        mContextHubWrapper.onMicrophoneDisableSettingChanged(disabled);
+    private void sendMicrophoneDisableSettingUpdate(boolean enabled) {
+        Log.d(TAG, "Mic Disabled Setting: " + enabled);
+        mContextHubWrapper.onMicrophoneDisableSettingChanged(enabled);
+    }
+
+    /**
+     * Obtains the latest microphone disabled setting for the current user
+     * and notifies the Context Hub.
+     */
+    private void sendMicrophoneDisableSettingUpdateForCurrentUser() {
+        boolean isEnabled = mSensorPrivacyManagerInternal.isSensorPrivacyEnabled(
+                getCurrentUserId(), SensorPrivacyManager.Sensors.MICROPHONE);
+        sendMicrophoneDisableSettingUpdate(isEnabled);
     }
 
 
     private String getCallingPackageName() {
         return mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
     }
+
+    private int getCurrentUserId() {
+        final long id = Binder.clearCallingIdentity();
+        try {
+            UserInfo currentUser = ActivityManager.getService().getCurrentUser();
+            return currentUser.id;
+        } catch (RemoteException e) {
+            // Activity manager not running, nothing we can do - assume user 0.
+        } finally {
+            Binder.restoreCallingIdentity(id);
+        }
+        return UserHandle.USER_SYSTEM;
+    }
+
+    /**
+     * Send a microphone disable settings update whenever the foreground user changes.
+     * We always send a settings update regardless of the previous state for the same user
+     * since the CHRE framework is expected to handle repeated identical setting update.
+     */
+    public void onUserChanged() {
+        Log.d(TAG, "User changed to id: " + getCurrentUserId());
+        sendMicrophoneDisableSettingUpdateForCurrentUser();
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 0a443f3..7aaab0c 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -51,6 +51,7 @@
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.os.storage.StorageManager;
@@ -62,6 +63,8 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.dex.ArtManagerService;
+import com.android.server.pm.dex.ArtStatsLogUtils;
+import com.android.server.pm.dex.ArtStatsLogUtils.ArtStatsLogger;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.dex.DexoptUtils;
@@ -99,6 +102,8 @@
     private final PowerManager.WakeLock mDexoptWakeLock;
     private volatile boolean mSystemReady;
 
+    private final ArtStatsLogger mArtStatsLogger = new ArtStatsLogger();
+
     PackageDexOptimizer(Installer installer, Object installLock, Context context,
             String wakeLockTag) {
         this.mInstaller = installer;
@@ -252,6 +257,28 @@
                         profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid,
                         packageStats, options.isDowngrade(), profileName, dexMetadataPath,
                         options.getCompilationReason());
+
+                // Only report metrics for base apk for now.
+                // TODO: add ISA and APK type to metrics.
+                if (pkg.getBaseApkPath().equals(path)) {
+                    Trace.traceBegin(Trace.TRACE_TAG_PACKAGE_MANAGER, "dex2oat-metrics");
+                    try {
+                        long sessionId = Math.randomLongInternal();
+                        ArtStatsLogUtils.writeStatsLog(
+                                mArtStatsLogger,
+                                sessionId,
+                                path,
+                                compilerFilter,
+                                sharedGid,
+                                packageStats.getCompileTime(path),
+                                dexMetadataPath,
+                                options.getCompilationReason(),
+                                newResult);
+                    } finally {
+                        Trace.traceEnd(Trace.TRACE_TAG_PACKAGE_MANAGER);
+                    }
+                }
+
                 // The end result is:
                 //  - FAILED if any path failed,
                 //  - PERFORMED if at least one path needed compilation,
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
new file mode 100644
index 0000000..3b77c39
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.dex;
+
+import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED;
+import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY;
+import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
+import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK;
+import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK;
+
+import android.util.jar.StrictJarFile;
+import android.util.Slog;
+
+import com.android.internal.art.ArtStatsLog;
+import com.android.server.pm.PackageManagerService;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+
+/** Utils class to report ART metrics to statsd. */
+public class ArtStatsLogUtils {
+    private static final String TAG = ArtStatsLogUtils.class.getSimpleName();
+    private static final String PROFILE_DEX_METADATA = "primary.prof";
+    private static final String VDEX_DEX_METADATA = "primary.vdex";
+
+
+    private static final int ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY =
+            ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY;
+    private static final int ART_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED =
+            ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED;
+    private static final int ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED =
+            ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
+
+    private static final int ART_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK =
+            ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK;
+    private static final int ART_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK =
+            ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK;
+
+    private static final Map<Integer, Integer> COMPILATION_REASON_MAP = new HashMap();
+
+    static {
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_UNKNOWN, ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_UNKNOWN);
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_FIRST_BOOT, ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_FIRST_BOOT);
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_BOOT_AFTER_OTA, ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_BOOT);
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_POST_BOOT, ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_POST_BOOT);
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INSTALL, ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL);
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INSTALL_FAST, ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_FAST);
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INSTALL_BULK, ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK);
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INSTALL_BULK_SECONDARY,
+                ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY);
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED,
+                ART_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED);
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED,
+                ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED);
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_BACKGROUND_DEXOPT, ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_BG_DEXOPT);
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_AB_OTA, ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_AB_OTA);
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE,
+                ArtStatsLog.
+                        ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INACTIVE);
+        COMPILATION_REASON_MAP.put(PackageManagerService.REASON_SHARED,
+                ArtStatsLog.ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_SHARED);
+    }
+
+    private static final Map<String, Integer> COMPILE_FILTER_MAP = new HashMap();
+
+    static {
+        COMPILE_FILTER_MAP.put("error", ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_ERROR);
+        COMPILE_FILTER_MAP.put("unknown", ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_UNKNOWN);
+        COMPILE_FILTER_MAP.put("assume-verified", ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_ASSUMED_VERIFIED);
+        COMPILE_FILTER_MAP.put("extract", ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_EXTRACT);
+        COMPILE_FILTER_MAP.put("verify", ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_VERIFY);
+        COMPILE_FILTER_MAP.put("quicken", ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_QUICKEN);
+        COMPILE_FILTER_MAP.put("space-profile", ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_SPACE_PROFILE);
+        COMPILE_FILTER_MAP.put("space", ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_SPACE);
+        COMPILE_FILTER_MAP.put("speed-profile", ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_SPEED_PROFILE);
+        COMPILE_FILTER_MAP.put("speed", ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_SPEED);
+        COMPILE_FILTER_MAP.put("everything-profile", ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_EVERYTHING_PROFILE);
+        COMPILE_FILTER_MAP.put("everything", ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_EVERYTHING);
+        COMPILE_FILTER_MAP.put("run-from-apk", ArtStatsLog.
+                ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_APK);
+        COMPILE_FILTER_MAP.put("run-from-apk-fallback",
+                ART_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK);
+        COMPILE_FILTER_MAP.put("run-from-vdex-fallback",
+                ART_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK);
+    }
+
+    public static void writeStatsLog(
+            ArtStatsLogger logger,
+            long sessionId,
+            String path,
+            String compilerFilter,
+            int uid,
+            long compileTime,
+            String dexMetadataPath,
+            int compilationReason,
+            int result) {
+        int dexMetadataType = getDexMetadataType(dexMetadataPath);
+        logger.write(
+                sessionId,
+                uid,
+                compilationReason,
+                compilerFilter,
+                ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_RESULT_CODE,
+                result,
+                dexMetadataType);
+        logger.write(
+                sessionId,
+                uid,
+                compilationReason,
+                compilerFilter,
+                ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_DEX_CODE_BYTES,
+                getDexBytes(path),
+                dexMetadataType);
+        logger.write(
+                sessionId,
+                uid,
+                compilationReason,
+                compilerFilter,
+                ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_TOTAL_TIME,
+                compileTime,
+                dexMetadataType);
+    }
+
+    private static long getDexBytes(String apkPath) {
+        StrictJarFile jarFile = null;
+        long dexBytes = 0;
+        try {
+            jarFile = new StrictJarFile(apkPath,
+                    /*verify=*/ false,
+                    /*signatureSchemeRollbackProtectionsEnforced=*/ false);
+            Iterator<ZipEntry> it = jarFile.iterator();
+            while (it.hasNext()) {
+                ZipEntry entry = it.next();
+                if (entry.getName().matches("classes(\\d)*[.]dex")) {
+                    dexBytes += entry.getSize();
+                }
+            }
+            return dexBytes;
+        } catch (IOException ignore) {
+            Slog.e(TAG, "Error when parsing APK " + apkPath);
+            return -1L;
+        } finally {
+            try {
+                if (jarFile != null) {
+                    jarFile.close();
+                }
+            } catch (IOException ignore) {
+            }
+        }
+    }
+
+    private static int getDexMetadataType(String dexMetadataPath) {
+        if (dexMetadataPath == null) {
+            return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__NONE_DEX_METADATA;
+        }
+        StrictJarFile jarFile = null;
+        try {
+            jarFile = new StrictJarFile(dexMetadataPath,
+                    /*verify=*/ false,
+                    /*signatureSchemeRollbackProtectionsEnforced=*/false);
+            boolean hasProfile = findFileName(jarFile, PROFILE_DEX_METADATA);
+            boolean hasVdex = findFileName(jarFile, VDEX_DEX_METADATA);
+            if (hasProfile && hasVdex) {
+                return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__PROFILE_AND_VDEX;
+            } else if (hasProfile) {
+                return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__PROFILE;
+            } else if (hasVdex) {
+                return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__VDEX;
+            } else {
+                return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__UNKNOWN_DEX_METADATA;
+            }
+        } catch (IOException ignore) {
+            Slog.e(TAG, "Error when parsing dex metadata " + dexMetadataPath);
+            return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__ERROR_DEX_METADATA;
+        } finally {
+            try {
+                if (jarFile != null) {
+                    jarFile.close();
+                }
+            } catch (IOException ignore) {
+            }
+        }
+    }
+
+    private static boolean findFileName(StrictJarFile jarFile, String filename) throws IOException {
+        Iterator<ZipEntry> it = jarFile.iterator();
+        while (it.hasNext()) {
+            ZipEntry entry = it.next();
+            if (entry.getName().equals(filename)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static class ArtStatsLogger {
+        public void write(
+                long sessionId,
+                int uid,
+                int compilationReason,
+                String compilerFilter,
+                int kind,
+                long value,
+                int dexMetadataType) {
+            ArtStatsLog.write(
+                    ArtStatsLog.ART_DATUM_REPORTED,
+                    sessionId,
+                    uid,
+                    COMPILE_FILTER_MAP.getOrDefault(compilerFilter, ArtStatsLog.
+                            ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_UNKNOWN),
+                    COMPILATION_REASON_MAP.getOrDefault(compilationReason, ArtStatsLog.
+                            ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_UNKNOWN),
+                    /*timestamp_millis=*/ 0L,
+                    ArtStatsLog.ART_DATUM_REPORTED__THREAD_TYPE__ART_THREAD_MAIN,
+                    kind,
+                    value,
+                    dexMetadataType);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index e3ccb75..27bf8a13 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -737,10 +737,12 @@
             }
         }
         if (locationExtraPackageNames != null) {
-            // Also grant location permission to location extra packages.
+            // Also grant location and activity recognition permission to location extra packages.
             for (String packageName : locationExtraPackageNames) {
                 grantPermissionsToSystemPackage(pm, packageName, userId,
                         ALWAYS_LOCATION_PERMISSIONS);
+                grantSystemFixedPermissionsToSystemPackage(pm, packageName, userId,
+                        ACTIVITY_RECOGNITION_PERMISSIONS);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java
index 1e8b8a5..4cc369f 100644
--- a/services/core/java/com/android/server/wm/SeamlessRotator.java
+++ b/services/core/java/com/android/server/wm/SeamlessRotator.java
@@ -102,6 +102,10 @@
      * window in the new orientation.
      */
     void finish(Transaction t, WindowContainer win) {
+        if (win.mSurfaceControl == null || !win.mSurfaceControl.isValid()) {
+            return;
+        }
+
         mTransform.reset();
         t.setMatrix(win.mSurfaceControl, mTransform, mFloat9);
         t.setPosition(win.mSurfaceControl, win.mLastSurfacePosition.x, win.mLastSurfacePosition.y);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 1a2eee0..ac2281a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8817,6 +8817,9 @@
                 return null;
             }
             final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor);
+            if (supervisorComponent == null) {
+                return null;
+            }
             if (supervisorComponent.equals(doComponent) || supervisorComponent.equals(
                     poComponent)) {
                 return supervisorComponent;
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java
new file mode 100644
index 0000000..3ab3448
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.dex;
+
+import static org.mockito.Mockito.inOrder;
+
+import com.android.internal.art.ArtStatsLog;
+import com.android.server.pm.dex.ArtStatsLogUtils.ArtStatsLogger;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.InOrder;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Unit tests for {@link com.android.server.pm.dex.ArtStatsLogUtils}.
+ *
+ * Run with "atest ArtStatsLogUtilsTest".
+ */
+@RunWith(JUnit4.class)
+public final class ArtStatsLogUtilsTest {
+    private static final String TAG = ArtStatsLogUtilsTest.class.getSimpleName();
+    private static final String COMPILER_FILTER = "space-profile";
+    private static final String PROFILE_DEX_METADATA = "primary.prof";
+    private static final String VDEX_DEX_METADATA = "primary.vdex";
+    private static final byte[] DEX_CONTENT = "dexData".getBytes();
+    private static final int COMPILATION_REASON = 1;
+    private static final int RESULT_CODE = 222;
+    private static final int UID = 111;
+    private static final long COMPILE_TIME = 333L;
+    private static final long SESSION_ID = 444L;
+
+    @Mock
+    ArtStatsLogger mockLogger;
+
+    private static Path TEST_DIR;
+    private static Path DEX;
+    private static Path NON_DEX;
+
+    @BeforeClass
+    public static void setUpAll() throws IOException {
+        TEST_DIR = Files.createTempDirectory(null);
+        DEX = Files.createFile(TEST_DIR.resolve("classes.dex"));
+        NON_DEX = Files.createFile(TEST_DIR.resolve("test.dex"));
+        Files.write(DEX, DEX_CONTENT);
+        Files.write(NON_DEX, "empty".getBytes());
+    }
+
+    @AfterClass
+    public static void tearnDownAll() {
+        deleteSliently(DEX);
+        deleteSliently(NON_DEX);
+        deleteSliently(TEST_DIR);
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testProfileAndVdexDexMetadata() throws IOException {
+        // Setup
+        Path dexMetadataPath = null;
+        Path apk = null;
+        try {
+            dexMetadataPath = createDexMetadata(PROFILE_DEX_METADATA, VDEX_DEX_METADATA);
+            apk = zipFiles(".apk", DEX, NON_DEX, dexMetadataPath);
+
+            // Act
+            ArtStatsLogUtils.writeStatsLog(
+                    mockLogger,
+                    SESSION_ID,
+                    apk.toString(),
+                    COMPILER_FILTER,
+                    UID,
+                    COMPILE_TIME,
+                    dexMetadataPath.toString(),
+                    COMPILATION_REASON,
+                    RESULT_CODE);
+
+            // Assert
+            verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__PROFILE_AND_VDEX);
+        } finally {
+            deleteSliently(dexMetadataPath);
+            deleteSliently(apk);
+        }
+    }
+
+    @Test
+    public void testProfileOnlyDexMetadata() throws IOException {
+        // Setup
+        Path dexMetadataPath = null;
+        Path apk = null;
+        try {
+            dexMetadataPath = createDexMetadata(PROFILE_DEX_METADATA);
+            apk = zipFiles(".apk", DEX, NON_DEX, dexMetadataPath);
+
+            // Act
+            ArtStatsLogUtils.writeStatsLog(
+                    mockLogger,
+                    SESSION_ID,
+                    apk.toString(),
+                    COMPILER_FILTER,
+                    UID,
+                    COMPILE_TIME,
+                    dexMetadataPath.toString(),
+                    COMPILATION_REASON,
+                    RESULT_CODE);
+
+            // Assert
+            verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__PROFILE);
+        } finally {
+            deleteSliently(dexMetadataPath);
+            deleteSliently(apk);
+        }
+    }
+
+    @Test
+    public void testVdexOnlyDexMetadata() throws IOException {
+        // Setup
+        Path dexMetadataPath = null;
+        Path apk = null;
+        try {
+            dexMetadataPath = createDexMetadata(VDEX_DEX_METADATA);
+            apk = zipFiles(".apk", DEX, NON_DEX, dexMetadataPath);
+
+            // Act
+            ArtStatsLogUtils.writeStatsLog(
+                    mockLogger,
+                    SESSION_ID,
+                    apk.toString(),
+                    COMPILER_FILTER,
+                    UID,
+                    COMPILE_TIME,
+                    dexMetadataPath.toString(),
+                    COMPILATION_REASON,
+                    RESULT_CODE);
+
+            // Assert
+            verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__VDEX);
+        } finally {
+            deleteSliently(dexMetadataPath);
+            deleteSliently(apk);
+        }
+    }
+
+    @Test
+    public void testNoneDexMetadata() throws IOException {
+        // Setup
+        Path apk = null;
+        try {
+            apk = zipFiles(".apk", DEX, NON_DEX);
+
+            // Act
+            ArtStatsLogUtils.writeStatsLog(
+                    mockLogger,
+                    SESSION_ID,
+                    apk.toString(),
+                    COMPILER_FILTER,
+                    UID,
+                    COMPILE_TIME,
+                    /*dexMetadataPath=*/ null,
+                    COMPILATION_REASON,
+                    RESULT_CODE);
+
+            // Assert
+            verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__NONE_DEX_METADATA);
+        } finally {
+            deleteSliently(apk);
+        }
+    }
+
+    @Test
+    public void testUnKnownDexMetadata() throws IOException {
+        // Setup
+        Path dexMetadataPath = null;
+        Path apk = null;
+        try {
+            dexMetadataPath = createDexMetadata("unknown");
+            apk = zipFiles(".apk", DEX, NON_DEX, dexMetadataPath);
+
+            // Act
+            ArtStatsLogUtils.writeStatsLog(
+                    mockLogger,
+                    SESSION_ID,
+                    apk.toString(),
+                    COMPILER_FILTER,
+                    UID,
+                    COMPILE_TIME,
+                    dexMetadataPath.toString(),
+                    COMPILATION_REASON,
+                    RESULT_CODE);
+
+            // Assert
+            verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__UNKNOWN_DEX_METADATA);
+        } finally {
+            deleteSliently(dexMetadataPath);
+            deleteSliently(apk);
+        }
+    }
+
+    private void verifyWrites(int dexMetadataType) {
+        InOrder inorder = inOrder(mockLogger);
+        inorder.verify(mockLogger).write(
+                SESSION_ID, UID,
+                COMPILATION_REASON,
+                COMPILER_FILTER,
+                ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_RESULT_CODE,
+                RESULT_CODE,
+                dexMetadataType);
+        inorder.verify(mockLogger).write(
+                SESSION_ID,
+                UID,
+                COMPILATION_REASON,
+                COMPILER_FILTER,
+                ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_DEX_CODE_BYTES,
+                DEX_CONTENT.length,
+                dexMetadataType);
+        inorder.verify(mockLogger).write(
+                SESSION_ID,
+                UID,
+                COMPILATION_REASON,
+                COMPILER_FILTER,
+                ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_TOTAL_TIME,
+                COMPILE_TIME,
+                dexMetadataType);
+    }
+
+    private Path zipFiles(String suffix, Path... files) throws IOException {
+        Path zipFile = Files.createTempFile(null, suffix);
+        try (final OutputStream os = Files.newOutputStream(zipFile)) {
+            try (final ZipOutputStream zos = new ZipOutputStream(os)) {
+                for (Path file : files) {
+                    ZipEntry zipEntry = new ZipEntry(file.getFileName().toString());
+                    zos.putNextEntry(zipEntry);
+                    zos.write(Files.readAllBytes(file));
+                    zos.closeEntry();
+                }
+            }
+        }
+        return zipFile;
+    }
+
+    private Path createDexMetadata(String... entryNames) throws IOException {
+        Path zipFile = Files.createTempFile(null, ".dm");
+        try (final OutputStream os = Files.newOutputStream(zipFile)) {
+            try (final ZipOutputStream zos = new ZipOutputStream(os)) {
+                for (String entryName : entryNames) {
+                    ZipEntry zipEntry = new ZipEntry(entryName);
+                    zos.putNextEntry(zipEntry);
+                    zos.write(entryName.getBytes());
+                    zos.closeEntry();
+                }
+            }
+        }
+        return zipFile;
+    }
+
+    private static void deleteSliently(Path file) {
+        if (file != null) {
+            try {
+                Files.deleteIfExists(file);
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/CallDiagnosticService.java b/telecomm/java/android/telecom/CallDiagnosticService.java
index 809f2bc..5fb6b33 100644
--- a/telecomm/java/android/telecom/CallDiagnosticService.java
+++ b/telecomm/java/android/telecom/CallDiagnosticService.java
@@ -27,6 +27,8 @@
 import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.telephony.Annotation;
+import android.telephony.ims.ImsReasonInfo;
 import android.util.ArrayMap;
 
 import com.android.internal.telecom.ICallDiagnosticService;
@@ -105,6 +107,12 @@
                 throws RemoteException {
             handleBluetoothCallQualityReport(qualityReport);
         }
+
+        @Override
+        public void notifyCallDisconnected(@NonNull String callId,
+                @NonNull DisconnectCause disconnectCause) throws RemoteException {
+            handleCallDisconnected(callId, disconnectCause);
+        }
     }
 
     /**
@@ -329,6 +337,32 @@
     }
 
     /**
+     * Handles a request from the Telecom framework to get a disconnect message from the
+     * {@link CallDiagnosticService}.
+     * @param callId The ID of the call.
+     * @param disconnectCause The telecom disconnect cause.
+     */
+    private void handleCallDisconnected(@NonNull String callId,
+            @NonNull DisconnectCause disconnectCause) {
+        Log.i(this, "handleCallDisconnected: call=%s; cause=%s", callId, disconnectCause);
+        DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId);
+        CharSequence message;
+        if (disconnectCause.getImsReasonInfo() != null) {
+            message = diagnosticCall.onCallDisconnected(disconnectCause.getImsReasonInfo());
+        } else {
+            message = diagnosticCall.onCallDisconnected(
+                    disconnectCause.getTelephonyDisconnectCause(),
+                    disconnectCause.getTelephonyPreciseDisconnectCause());
+        }
+        try {
+            mAdapter.overrideDisconnectMessage(callId, message);
+        } catch (RemoteException e) {
+            Log.w(this, "handleCallDisconnected: call=%s; cause=%s; %s",
+                    callId, disconnectCause, e);
+        }
+    }
+
+    /**
      * Handles an incoming bluetooth call quality report from Telecom.  Notifies via
      * {@link CallDiagnosticService#onBluetoothCallQualityReportReceived(
      * BluetoothCallQualityReport)}.
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 1472a4a..ed7b79f 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -16,9 +16,13 @@
 
 package android.telecom;
 
+import android.annotation.Nullable;
 import android.media.ToneGenerator;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.Annotation;
+import android.telephony.PreciseDisconnectCause;
+import android.telephony.ims.ImsReasonInfo;
 import android.text.TextUtils;
 
 import java.util.Objects;
@@ -112,6 +116,9 @@
     private CharSequence mDisconnectDescription;
     private String mDisconnectReason;
     private int mToneToPlay;
+    private int mTelephonyDisconnectCause;
+    private int mTelephonyPreciseDisconnectCause;
+    private ImsReasonInfo mImsReasonInfo;
 
     /**
      * Creates a new DisconnectCause.
@@ -155,11 +162,36 @@
      */
     public DisconnectCause(int code, CharSequence label, CharSequence description, String reason,
             int toneToPlay) {
+        this(code, label, description, reason, toneToPlay,
+                android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
+                PreciseDisconnectCause.ERROR_UNSPECIFIED,
+                null /* imsReasonInfo */);
+    }
+
+    /**
+     * Creates a new DisconnectCause instance.
+     * @param code The code for the disconnect cause.
+     * @param label The localized label to show to the user to explain the disconnect.
+     * @param description The localized description to show to the user to explain the disconnect.
+     * @param reason The reason for the disconnect.
+     * @param toneToPlay The tone to play on disconnect, as defined in {@link ToneGenerator}.
+     * @param telephonyDisconnectCause The Telephony disconnect cause.
+     * @param telephonyPreciseDisconnectCause The Telephony precise disconnect cause.
+     * @param imsReasonInfo The relevant {@link ImsReasonInfo}, or {@code null} if not available.
+     * @hide
+     */
+    public DisconnectCause(int code, CharSequence label, CharSequence description, String reason,
+            int toneToPlay, @Annotation.DisconnectCauses int telephonyDisconnectCause,
+            @Annotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause,
+            @Nullable ImsReasonInfo imsReasonInfo) {
         mDisconnectCode = code;
         mDisconnectLabel = label;
         mDisconnectDescription = description;
         mDisconnectReason = reason;
         mToneToPlay = toneToPlay;
+        mTelephonyDisconnectCause = telephonyDisconnectCause;
+        mTelephonyPreciseDisconnectCause = telephonyPreciseDisconnectCause;
+        mImsReasonInfo = imsReasonInfo;
     }
 
     /**
@@ -209,6 +241,33 @@
     }
 
     /**
+     * Returns the telephony {@link android.telephony.DisconnectCause} for the call.
+     * @return The disconnect cause.
+     * @hide
+     */
+    public @Annotation.DisconnectCauses int getTelephonyDisconnectCause() {
+        return mTelephonyDisconnectCause;
+    }
+
+    /**
+     * Returns the telephony {@link android.telephony.PreciseDisconnectCause} for the call.
+     * @return The precise disconnect cause.
+     * @hide
+     */
+    public @Annotation.PreciseDisconnectCauses int getTelephonyPreciseDisconnectCause() {
+        return mTelephonyPreciseDisconnectCause;
+    }
+
+    /**
+     * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection.
+     * @return The {@link ImsReasonInfo} or {@code null} if not known.
+     * @hide
+     */
+    public @Nullable ImsReasonInfo getImsReasonInfo() {
+        return mImsReasonInfo;
+    }
+
+    /**
      * Returns the tone to play when disconnected.
      *
      * @return the tone as defined in {@link ToneGenerator} to play when disconnected.
@@ -217,7 +276,8 @@
         return mToneToPlay;
     }
 
-    public static final @android.annotation.NonNull Creator<DisconnectCause> CREATOR = new Creator<DisconnectCause>() {
+    public static final @android.annotation.NonNull Creator<DisconnectCause> CREATOR
+            = new Creator<DisconnectCause>() {
         @Override
         public DisconnectCause createFromParcel(Parcel source) {
             int code = source.readInt();
@@ -225,7 +285,11 @@
             CharSequence description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
             String reason = source.readString();
             int tone = source.readInt();
-            return new DisconnectCause(code, label, description, reason, tone);
+            int telephonyDisconnectCause = source.readInt();
+            int telephonyPreciseDisconnectCause = source.readInt();
+            ImsReasonInfo imsReasonInfo = source.readParcelable(null);
+            return new DisconnectCause(code, label, description, reason, tone,
+                    telephonyDisconnectCause, telephonyPreciseDisconnectCause, imsReasonInfo);
         }
 
         @Override
@@ -241,6 +305,9 @@
         TextUtils.writeToParcel(mDisconnectDescription, destination, flags);
         destination.writeString(mDisconnectReason);
         destination.writeInt(mToneToPlay);
+        destination.writeInt(mTelephonyDisconnectCause);
+        destination.writeInt(mTelephonyPreciseDisconnectCause);
+        destination.writeParcelable(mImsReasonInfo, 0);
     }
 
     @Override
@@ -254,7 +321,10 @@
                 + Objects.hashCode(mDisconnectLabel)
                 + Objects.hashCode(mDisconnectDescription)
                 + Objects.hashCode(mDisconnectReason)
-                + Objects.hashCode(mToneToPlay);
+                + Objects.hashCode(mToneToPlay)
+                + Objects.hashCode(mTelephonyDisconnectCause)
+                + Objects.hashCode(mTelephonyPreciseDisconnectCause)
+                + Objects.hashCode(mImsReasonInfo);
     }
 
     @Override
@@ -265,7 +335,11 @@
                     && Objects.equals(mDisconnectLabel, d.getLabel())
                     && Objects.equals(mDisconnectDescription, d.getDescription())
                     && Objects.equals(mDisconnectReason, d.getReason())
-                    && Objects.equals(mToneToPlay, d.getTone());
+                    && Objects.equals(mToneToPlay, d.getTone())
+                    && Objects.equals(mTelephonyDisconnectCause, d.getTelephonyDisconnectCause())
+                    && Objects.equals(mTelephonyPreciseDisconnectCause,
+                    d.getTelephonyPreciseDisconnectCause())
+                    && Objects.equals(mImsReasonInfo, d.getImsReasonInfo());
         }
         return false;
     }
@@ -325,6 +399,11 @@
                 + " Label: (" + label + ")"
                 + " Description: (" + description + ")"
                 + " Reason: (" + reason + ")"
-                + " Tone: (" + mToneToPlay + ") ]";
+                + " Tone: (" + mToneToPlay + ") "
+                + " TelephonyCause: " + mTelephonyDisconnectCause + "/"
+                + mTelephonyPreciseDisconnectCause
+                + " ImsReasonInfo: "
+                + mImsReasonInfo
+                + "]";
     }
 }
diff --git a/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
index 65b4d19..fc9879a 100644
--- a/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
@@ -18,6 +18,7 @@
 
 import android.telecom.BluetoothCallQualityReport;
 import android.telecom.CallAudioState;
+import android.telecom.DisconnectCause;
 import android.telecom.ParcelableCall;
 import com.android.internal.telecom.ICallDiagnosticServiceAdapter;
 
@@ -34,4 +35,5 @@
     void removeDiagnosticCall(in String callId);
     void receiveDeviceToDeviceMessage(in String callId, int message, int value);
     void receiveBluetoothCallQualityReport(in BluetoothCallQualityReport qualityReport);
+    void notifyCallDisconnected(in String callId, in DisconnectCause disconnectCause);
 }
diff --git a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
index ce226fd..926ff4d 100644
--- a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
+++ b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
@@ -130,6 +130,9 @@
         return result;
     }
     sBuffers[slot] = anb;
+    if (timeoutMs == 0) {
+        return android::OK;
+    }
     android::sp<android::Fence> fence(new android::Fence(fenceFd));
     int waitResult = fence->wait(timeoutMs);
     if (waitResult != android::OK) {
@@ -197,6 +200,28 @@
     return result;
 }
 
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetAsyncMode(JNIEnv* /* env */,
+                                                                              jclass /* clazz */,
+                                                                              jboolean async) {
+    assert(sAnw);
+    android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+    return surface->setAsyncMode(async);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetDequeueTimeout(
+        JNIEnv* /* env */, jclass /* clazz */, jlong timeoutMs) {
+    assert(sAnw);
+    android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+    return surface->setDequeueTimeout(timeoutMs);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetMaxDequeuedBufferCount(
+        JNIEnv* /* env */, jclass /* clazz */, jint maxDequeuedBuffers) {
+    assert(sAnw);
+    android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+    return surface->setMaxDequeuedBufferCount(maxDequeuedBuffers);
+}
+
 JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_NativeWindowSetBufferCount(
         JNIEnv* /* env */, jclass /* clazz */, jint count) {
     assert(sAnw);
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
index 7d278dc..b67dc380 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
@@ -17,6 +17,7 @@
 
 import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
 import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -93,4 +94,80 @@
 
         assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
     }
+
+    @Test
+    // Leave IGBP in sync mode, try to dequeue and queue as fast as possible. Check that we
+    // occasionally get timeout errors.
+    fun testSyncMode_dequeueWithoutBlockingFails() {
+        val numFrames = 1000L
+        runOnUiThread { activity ->
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+            var failures = false
+            for (i in 1..numFrames) {
+                if (activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */) != 0) {
+                    failures = true
+                    break
+                }
+                activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+            }
+            assertTrue(failures)
+        }
+    }
+
+    @Test
+    // Set IGBP to be in async mode, try to dequeue and queue as fast as possible. Client should be
+    // able to dequeue and queue buffers without being blocked.
+    fun testAsyncMode_dequeueWithoutBlocking() {
+        val numFrames = 1000L
+        runOnUiThread { activity ->
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetAsyncMode(async = true))
+            for (i in 1..numFrames) {
+                assertEquals(0, activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */))
+                activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+            }
+        }
+    }
+
+    @Test
+    // Disable triple buffering in the system and leave IGBP in sync mode. Check that we
+    // occasionally get timeout errors.
+    fun testSyncModeWithDisabledTripleBuffering_dequeueWithoutBlockingFails() {
+        val numFrames = 1000L
+        runOnUiThread { activity ->
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetMaxDequeuedBufferCount(1))
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+            var failures = false
+            for (i in 1..numFrames) {
+                if (activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */) != 0) {
+                    failures = true
+                    break
+                }
+                activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+            }
+            assertTrue(failures)
+        }
+    }
+
+    @Test
+    // Disable triple buffering in the system and set IGBP to be in async mode. Try to dequeue and
+    // queue as fast as possible. Without triple buffering, the client does not have an extra buffer
+    // to dequeue and will not be able to dequeue and queue buffers without being blocked.
+    fun testAsyncModeWithDisabledTripleBuffering_dequeueWithoutBlockingFails() {
+        val numFrames = 1000L
+        runOnUiThread { activity ->
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetMaxDequeuedBufferCount(1))
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetAsyncMode(async = true))
+            var failures = false
+            for (i in 1..numFrames) {
+                if (activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */) != 0) {
+                    failures = true
+                    break
+                }
+                activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+            }
+            assertTrue(failures)
+        }
+    }
 }
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
index cfbd3ac..45a7094 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
@@ -54,7 +54,13 @@
     external fun SurfaceSetScalingMode(scalingMode: Int)
     external fun SurfaceDequeueBuffer(slot: Int, timeoutMs: Int): Int
     external fun SurfaceCancelBuffer(slot: Int)
-    external fun SurfaceQueueBuffer(slot: Int, freeSlot: Boolean = true)
+    external fun SurfaceQueueBuffer(slot: Int, freeSlot: Boolean = true): Int
+    external fun SurfaceSetAsyncMode(async: Boolean): Int
+    external fun SurfaceSetDequeueTimeout(timeout: Long): Int
+    external fun SurfaceQuery(what: Int): Int
+    external fun SurfaceSetMaxDequeuedBufferCount(maxDequeuedBuffers: Int): Int
+
+    // system/native_window.h functions
     external fun NativeWindowSetBufferCount(count: Int): Int
     external fun NativeWindowSetSharedBufferMode(shared: Boolean): Int
     external fun NativeWindowSetAutoRefresh(autoRefresh: Boolean): Int
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index a07bce3..8932892 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -30,12 +30,21 @@
  *     -e selectTest_verbose true \
  *     com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
  * </pre>
+ *
+ * <p>Use this filter when running FrameworksMockingCoreTests as
+ * <pre>
+ * adb shell am instrument -w \
+ *     -e filter com.android.server.wm.test.filters.FrameworksTestsFilter \
+ *     -e selectTest_verbose true \
+ *     com.android.frameworks.mockingcoretests/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
  */
 public final class FrameworksTestsFilter extends SelectTest {
 
     private static final String[] SELECTED_TESTS = {
             // Test specifications for FrameworksMockingCoreTests.
             "android.app.activity.ActivityThreadClientTest",
+            "android.view.DisplayTest",
             // Test specifications for FrameworksCoreTests.
             "android.app.servertransaction.", // all tests under the package.
             "android.view.CutoutSpecificationTest",