Merge "Fix potential exceptions in CallDiagnosticService and missing locks." into sc-dev am: 4fab561f27 am: bc0014b019

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14687876

Change-Id: I718666b0159f007d557cb9dd0409f5785d5d95a3
diff --git a/core/api/current.txt b/core/api/current.txt
index f546f53..25a2fda 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -51440,6 +51440,7 @@
 
   public final class AutofillManager {
     method public void cancel();
+    method public void clearAutofillRequestCallback();
     method public void commit();
     method public void disableAutofillServices();
     method @Nullable public android.content.ComponentName getAutofillServiceComponentName();
@@ -51465,6 +51466,7 @@
     method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
     method public void requestAutofill(@NonNull android.view.View);
     method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect);
+    method public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback);
     method public void setUserData(@Nullable android.service.autofill.UserData);
     method public void unregisterCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
     field public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
@@ -51483,6 +51485,10 @@
     field public static final int EVENT_INPUT_UNAVAILABLE = 3; // 0x3
   }
 
+  public interface AutofillRequestCallback {
+    method public void onFillRequest(@Nullable android.view.inputmethod.InlineSuggestionsRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
+  }
+
   public final class AutofillValue implements android.os.Parcelable {
     method public int describeContents();
     method public static android.view.autofill.AutofillValue forDate(long);
@@ -51853,10 +51859,12 @@
     ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addInlinePresentationSpecs(@NonNull android.widget.inline.InlinePresentationSpec);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build();
+    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlineTooltipPresentationSpec(@NonNull android.widget.inline.InlinePresentationSpec);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int);
+    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList);
   }
 
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 483defa..0a4e822 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -16,6 +16,8 @@
 
 package android.accessibilityservice;
 
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+
 import android.accessibilityservice.GestureDescription.MotionEventGenerator;
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
@@ -27,6 +29,7 @@
 import android.app.Service;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Bitmap;
@@ -36,6 +39,7 @@
 import android.hardware.HardwareBuffer;
 import android.hardware.display.DisplayManager;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -961,30 +965,31 @@
         }
     }
 
+    @NonNull
     @Override
     public Context createDisplayContext(Display display) {
-        final Context context = super.createDisplayContext(display);
-        final int displayId = display.getDisplayId();
-        setDefaultTokenInternal(context, displayId);
-        return context;
+        return new AccessibilityContext(super.createDisplayContext(display), mConnectionId);
     }
 
-    private void setDefaultTokenInternal(Context context, int displayId) {
-        final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(WINDOW_SERVICE);
-        final IAccessibilityServiceConnection connection =
-                AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
-        IBinder token = null;
-        if (connection != null) {
-            synchronized (mLock) {
-                try {
-                    token = connection.getOverlayWindowToken(displayId);
-                } catch (RemoteException re) {
-                    Log.w(LOG_TAG, "Failed to get window token", re);
-                    re.rethrowFromSystemServer();
-                }
-            }
-            wm.setDefaultToken(token);
+    @NonNull
+    @Override
+    public Context createWindowContext(int type, @Nullable Bundle options) {
+        final Context context = super.createWindowContext(type, options);
+        if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+            return context;
         }
+        return new AccessibilityContext(context, mConnectionId);
+    }
+
+    @NonNull
+    @Override
+    public Context createWindowContext(@NonNull Display display, int type,
+            @Nullable Bundle options) {
+        final Context context = super.createWindowContext(display, type, options);
+        if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+            return context;
+        }
+        return new AccessibilityContext(context, mConnectionId);
     }
 
     /**
@@ -2675,4 +2680,58 @@
             }
         }
     }
+
+    private static class AccessibilityContext extends ContextWrapper {
+        private final int mConnectionId;
+
+        private AccessibilityContext(Context base, int connectionId) {
+            super(base);
+            mConnectionId = connectionId;
+            setDefaultTokenInternal(this, getDisplayId());
+        }
+
+        @NonNull
+        @Override
+        public Context createDisplayContext(Display display) {
+            return new AccessibilityContext(super.createDisplayContext(display), mConnectionId);
+        }
+
+        @NonNull
+        @Override
+        public Context createWindowContext(int type, @Nullable Bundle options) {
+            final Context context = super.createWindowContext(type, options);
+            if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+                return context;
+            }
+            return new AccessibilityContext(context, mConnectionId);
+        }
+
+        @NonNull
+        @Override
+        public Context createWindowContext(@NonNull Display display, int type,
+                @Nullable Bundle options) {
+            final Context context = super.createWindowContext(display, type, options);
+            if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+                return context;
+            }
+            return new AccessibilityContext(context, mConnectionId);
+        }
+
+        private void setDefaultTokenInternal(Context context, int displayId) {
+            final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(
+                    WINDOW_SERVICE);
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getConnection(mConnectionId);
+            IBinder token = null;
+            if (connection != null) {
+                try {
+                    token = connection.getOverlayWindowToken(displayId);
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to get window token", re);
+                    re.rethrowFromSystemServer();
+                }
+                wm.setDefaultToken(token);
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 4a7fcd2..a836625 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -476,6 +476,19 @@
     }
 
     /**
+     * Detaches the navigation bar from the app it was attached to during a transition.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+    public void detachNavigationBarFromApp(@NonNull IBinder transition) {
+        try {
+            getService().detachNavigationBarFromApp(transition);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Information you can retrieve about a root task in the system.
      * @hide
      */
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 74d51a0..9f97fad 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -330,4 +330,10 @@
      * When the Picture-in-picture state has changed.
      */
     void onPictureInPictureStateChanged(in PictureInPictureUiState pipState);
+
+    /**
+     * Re-attach navbar to the display during a recents transition.
+     * TODO(188595497): Remove this once navbar attachment is in shell.
+     */
+    void detachNavigationBarFromApp(in IBinder transition);
 }
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 62becc5..af846b6 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -96,6 +96,8 @@
      */
     public static final @RequestFlags int FLAG_VIEW_NOT_FOCUSED = 0x10;
 
+    // The flag value 0x20 has been defined in AutofillManager.
+
     /** @hide */
     public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
 
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c1e394d..52812ef 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -467,6 +467,13 @@
     int TRANSIT_FLAG_KEYGUARD_LOCKED = 0x40;
 
     /**
+     * Transition flag: Indicates that this transition is for recents animation.
+     * TODO(b/188669821): Remove once special-case logic moves to shell.
+     * @hide
+     */
+    int TRANSIT_FLAG_IS_RECENTS = 0x80;
+
+    /**
      * @hide
      */
     @IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = {
@@ -476,7 +483,8 @@
             TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION,
             TRANSIT_FLAG_APP_CRASHED,
             TRANSIT_FLAG_OPEN_BEHIND,
-            TRANSIT_FLAG_KEYGUARD_LOCKED
+            TRANSIT_FLAG_KEYGUARD_LOCKED,
+            TRANSIT_FLAG_IS_RECENTS
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface TransitionFlags {}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 4df8fd2..a169cb0 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -25,6 +25,7 @@
 import static android.view.autofill.Helper.toList;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -45,16 +46,21 @@
 import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.metrics.LogMaker;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.ICancellationSignal;
 import android.os.Looper;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
 import android.service.autofill.FillEventHistory;
+import android.service.autofill.IFillCallback;
 import android.service.autofill.UserData;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -74,6 +80,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 import android.widget.TextView;
@@ -99,6 +106,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 import sun.misc.Cleaner;
 
@@ -167,6 +175,12 @@
  * shows an autofill save UI if the value of savable views have changed. If the user selects the
  * option to Save, the current value of the views is then sent to the autofill service.
  *
+ * <p>There is another choice for the application to provide it's datasets to the Autofill framework
+ * by setting an {@link AutofillRequestCallback} through
+ * {@link #setAutofillRequestCallback(Executor, AutofillRequestCallback)}. The application can use
+ * its callback instead of the default {@link AutofillService}. See
+ * {@link AutofillRequestCallback} for more details.
+ *
  * <h3 id="additional-notes">Additional notes</h3>
  *
  * <p>It is safe to call <code>AutofillManager</code> methods from any thread.
@@ -292,6 +306,7 @@
     /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
     /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
     /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8;
+    /** @hide */ public static final int FLAG_ENABLED_CLIENT_SUGGESTIONS = 0x20;
 
     // NOTE: flag below is used by the session start receiver only, hence it can have values above
     /** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1;
@@ -592,6 +607,11 @@
     @GuardedBy("mLock")
     private boolean mEnabledForAugmentedAutofillOnly;
 
+    @GuardedBy("mLock")
+    @Nullable private AutofillRequestCallback mAutofillRequestCallback;
+    @GuardedBy("mLock")
+    @Nullable private Executor mRequestCallbackExecutor;
+
     /** @hide */
     public interface AutofillClient {
         /**
@@ -1836,6 +1856,32 @@
         return new AutofillId(parent.getAutofillViewId(), virtualId);
     }
 
+    /**
+     * Sets the client's suggestions callback for autofill.
+     *
+     * @see AutofillRequestCallback
+     *
+     * @param executor specifies the thread upon which the callbacks will be invoked.
+     * @param callback which handles autofill request to provide client's suggestions.
+     */
+    public void setAutofillRequestCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull AutofillRequestCallback callback) {
+        synchronized (mLock) {
+            mRequestCallbackExecutor = executor;
+            mAutofillRequestCallback = callback;
+        }
+    }
+
+    /**
+     * clears the client's suggestions callback for autofill.
+     */
+    public void clearAutofillRequestCallback() {
+        synchronized (mLock) {
+            mRequestCallbackExecutor = null;
+            mAutofillRequestCallback = null;
+        }
+    }
+
     @GuardedBy("mLock")
     private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
             @NonNull AutofillValue value, int flags) {
@@ -1896,6 +1942,13 @@
                 }
             }
 
+            if (mAutofillRequestCallback != null) {
+                if (sDebug) {
+                    Log.d(TAG, "startSession with the client suggestions provider");
+                }
+                flags |= FLAG_ENABLED_CLIENT_SUGGESTIONS;
+            }
+
             mService.startSession(client.autofillClientGetActivityToken(),
                     mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
                     mCallback != null, flags, clientActivity,
@@ -2245,6 +2298,28 @@
         }
     }
 
+    private void onFillRequest(InlineSuggestionsRequest request,
+            CancellationSignal cancellationSignal, FillCallback callback) {
+        final AutofillRequestCallback autofillRequestCallback;
+        final Executor executor;
+        synchronized (mLock) {
+            autofillRequestCallback = mAutofillRequestCallback;
+            executor = mRequestCallbackExecutor;
+        }
+        if (autofillRequestCallback != null && executor != null) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                executor.execute(() ->
+                        autofillRequestCallback.onFillRequest(
+                                request, cancellationSignal, callback));
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        } else {
+            callback.onSuccess(null);
+        }
+    }
+
     /** @hide */
     public static final int SET_STATE_FLAG_ENABLED = 0x01;
     /** @hide */
@@ -3624,6 +3699,23 @@
                 afm.post(() -> afm.requestShowSoftInput(id));
             }
         }
+
+        @Override
+        public void requestFillFromClient(int id, InlineSuggestionsRequest request,
+                IFillCallback callback) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                ICancellationSignal transport = CancellationSignal.createTransport();
+                try {
+                    callback.onCancellable(transport);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Error requesting a cancellation", e);
+                }
+
+                afm.onFillRequest(request, CancellationSignal.fromTransport(transport),
+                        new FillCallback(callback, id));
+            }
+        }
     }
 
     private static final class AugmentedAutofillManagerClient
diff --git a/core/java/android/view/autofill/AutofillRequestCallback.java b/core/java/android/view/autofill/AutofillRequestCallback.java
new file mode 100644
index 0000000..e632a58
--- /dev/null
+++ b/core/java/android/view/autofill/AutofillRequestCallback.java
@@ -0,0 +1,72 @@
+/*
+ * 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 android.view.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.service.autofill.FillCallback;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+/**
+ * <p>This class is used to provide some input suggestions to the Autofill framework.
+ *
+ * <P>When the user is requested to input something, Autofill will try to query input suggestions
+ * for the user choosing. If the application want to provide some internal input suggestions,
+ * implements this callback and register via
+ * {@link AutofillManager#setAutofillRequestCallback(java.util.concurrent.Executor,
+ * AutofillRequestCallback)}. Autofill will callback the
+ * {@link #onFillRequest(InlineSuggestionsRequest, CancellationSignal, FillCallback)} to request
+ * input suggestions.
+ *
+ * <P>To make sure the callback to take effect, must register before the autofill session starts.
+ * If the autofill session is started, calls {@link AutofillManager#cancel()} to finish current
+ * session, and then the callback will be used at the next restarted session.
+ *
+ * <P>To create a {@link android.service.autofill.FillResponse}, application should fetch
+ * {@link AutofillId}s from its view structure. Below is an example:
+ * <pre class="prettyprint">
+ * AutofillId usernameId = findViewById(R.id.username).getAutofillId();
+ * AutofillId passwordId = findViewById(R.id.password).getAutofillId();
+ * </pre>
+ * To learn more about creating a {@link android.service.autofill.FillResponse}, read
+ * <a href="/guide/topics/text/autofill-services#fill">Fill out client views</a>.
+ *
+ * <P>To fallback to the default {@link android.service.autofill.AutofillService}, just respond
+ * a null of the {@link android.service.autofill.FillResponse}. And then Autofill will do a fill
+ * request with the default {@link android.service.autofill.AutofillService}. Or clear the callback
+ * from {@link AutofillManager} via {@link AutofillManager#clearAutofillRequestCallback()}. If the
+ * client would like to keep no suggestions for the field, respond with an empty
+ * {@link android.service.autofill.FillResponse} which has no dataset.
+ *
+ * <P>IMPORTANT: This should not be used for displaying anything other than input suggestions, or
+ * the keyboard may choose to block your app from the inline strip.
+ */
+public interface AutofillRequestCallback {
+    /**
+     * Called by the Android system to decide if a screen can be autofilled by the callback.
+     *
+     * @param inlineSuggestionsRequest the {@link InlineSuggestionsRequest request} to handle if
+     *     currently inline suggestions are supported and can be displayed.
+     * @param cancellationSignal signal for observing cancellation requests. The system will use
+     *     this to notify you that the fill result is no longer needed and you should stop
+     *     handling this fill request in order to save resources.
+     * @param callback object used to notify the result of the request.
+     */
+    void onFillRequest(@Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
+            @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
+}
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 1f833f6..64507aa 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -24,9 +24,11 @@
 import android.content.IntentSender;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.service.autofill.IFillCallback;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutofillWindowPresenter;
+import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.KeyEvent;
 
 import com.android.internal.os.IResultReceiver;
@@ -140,4 +142,10 @@
     * Requests to show the soft input method if the focus is on the given id.
     */
    void requestShowSoftInput(in AutofillId id);
+
+    /**
+     * Requests to determine if a screen can be autofilled by the client app.
+     */
+    void requestFillFromClient(int id, in InlineSuggestionsRequest request,
+            in IFillCallback callback);
 }
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 1eb1a93..e1e1755 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -111,6 +111,22 @@
     private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
 
     /**
+     * Whether the IME supports inline suggestions from the default Autofill service that
+     * provides the input view.
+     *
+     * Note: The default value is {@code true}.
+     */
+    private boolean mServiceSupported;
+
+    /**
+     * Whether the IME supports inline suggestions from the application that provides the
+     * input view.
+     *
+     * Note: The default value is {@code true}.
+     */
+    private boolean mClientSupported;
+
+    /**
      * @hide
      * @see {@link #mHostInputToken}.
      */
@@ -204,6 +220,14 @@
         return Bundle.EMPTY;
     }
 
+    private static boolean defaultServiceSupported() {
+        return true;
+    }
+
+    private static boolean defaultClientSupported() {
+        return true;
+    }
+
     /** @hide */
     abstract static class BaseBuilder {
         abstract Builder setInlinePresentationSpecs(
@@ -216,15 +240,25 @@
         abstract Builder setHostDisplayId(int value);
     }
 
+    /** @hide */
+    public boolean isServiceSupported() {
+        return mServiceSupported;
+    }
+
+    /** @hide */
+    public boolean isClientSupported() {
+        return mClientSupported;
+    }
 
 
-    // Code below generated by codegen v1.0.23.
+
+    // Code below generated by codegen v1.0.22.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -240,7 +274,9 @@
             @NonNull Bundle extras,
             @Nullable IBinder hostInputToken,
             int hostDisplayId,
-            @Nullable InlinePresentationSpec inlineTooltipPresentationSpec) {
+            @Nullable InlinePresentationSpec inlineTooltipPresentationSpec,
+            boolean serviceSupported,
+            boolean clientSupported) {
         this.mMaxSuggestionCount = maxSuggestionCount;
         this.mInlinePresentationSpecs = inlinePresentationSpecs;
         com.android.internal.util.AnnotationValidations.validate(
@@ -257,6 +293,8 @@
         this.mHostInputToken = hostInputToken;
         this.mHostDisplayId = hostDisplayId;
         this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
+        this.mServiceSupported = serviceSupported;
+        this.mClientSupported = clientSupported;
 
         onConstructed();
     }
@@ -340,7 +378,9 @@
     }
 
     /**
-     * Specifies the UI specification for the inline suggestion tooltip in the response.
+     * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
+     *
+     * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)
      */
     @DataClass.Generated.Member
     public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() {
@@ -361,7 +401,9 @@
                 "extras = " + mExtras + ", " +
                 "hostInputToken = " + mHostInputToken + ", " +
                 "hostDisplayId = " + mHostDisplayId + ", " +
-                "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec +
+                "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + ", " +
+                "serviceSupported = " + mServiceSupported + ", " +
+                "clientSupported = " + mClientSupported +
         " }";
     }
 
@@ -385,7 +427,9 @@
                 && extrasEquals(that.mExtras)
                 && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
                 && mHostDisplayId == that.mHostDisplayId
-                && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec);
+                && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec)
+                && mServiceSupported == that.mServiceSupported
+                && mClientSupported == that.mClientSupported;
     }
 
     @Override
@@ -403,6 +447,8 @@
         _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
         _hash = 31 * _hash + mHostDisplayId;
         _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec);
+        _hash = 31 * _hash + Boolean.hashCode(mServiceSupported);
+        _hash = 31 * _hash + Boolean.hashCode(mClientSupported);
         return _hash;
     }
 
@@ -413,6 +459,8 @@
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
         int flg = 0;
+        if (mServiceSupported) flg |= 0x100;
+        if (mClientSupported) flg |= 0x200;
         if (mHostInputToken != null) flg |= 0x20;
         if (mInlineTooltipPresentationSpec != null) flg |= 0x80;
         dest.writeInt(flg);
@@ -438,6 +486,8 @@
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
         int flg = in.readInt();
+        boolean serviceSupported = (flg & 0x100) != 0;
+        boolean clientSupported = (flg & 0x200) != 0;
         int maxSuggestionCount = in.readInt();
         List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>();
         in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader());
@@ -464,6 +514,8 @@
         this.mHostInputToken = hostInputToken;
         this.mHostDisplayId = hostDisplayId;
         this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
+        this.mServiceSupported = serviceSupported;
+        this.mClientSupported = clientSupported;
 
         onConstructed();
     }
@@ -497,6 +549,8 @@
         private @Nullable IBinder mHostInputToken;
         private int mHostDisplayId;
         private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
+        private boolean mServiceSupported;
+        private boolean mClientSupported;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -629,7 +683,9 @@
         }
 
         /**
-         * Specifies the UI specification for the inline suggestion tooltip in the response.
+         * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
+         *
+         * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)s
          */
         @DataClass.Generated.Member
         public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) {
@@ -639,10 +695,38 @@
             return this;
         }
 
+        /**
+         * Whether the IME supports inline suggestions from the default Autofill service that
+         * provides the input view.
+         *
+         * Note: The default value is {@code true}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setServiceSupported(boolean value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x100;
+            mServiceSupported = value;
+            return this;
+        }
+
+        /**
+         * Whether the IME supports inline suggestions from the application that provides the
+         * input view.
+         *
+         * Note: The default value is {@code true}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setClientSupported(boolean value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x200;
+            mClientSupported = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull InlineSuggestionsRequest build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x100; // Mark builder used
+            mBuilderFieldsSet |= 0x400; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mMaxSuggestionCount = defaultMaxSuggestionCount();
@@ -665,6 +749,12 @@
             if ((mBuilderFieldsSet & 0x80) == 0) {
                 mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec();
             }
+            if ((mBuilderFieldsSet & 0x100) == 0) {
+                mServiceSupported = defaultServiceSupported();
+            }
+            if ((mBuilderFieldsSet & 0x200) == 0) {
+                mClientSupported = defaultClientSupported();
+            }
             InlineSuggestionsRequest o = new InlineSuggestionsRequest(
                     mMaxSuggestionCount,
                     mInlinePresentationSpecs,
@@ -673,12 +763,14 @@
                     mExtras,
                     mHostInputToken,
                     mHostDisplayId,
-                    mInlineTooltipPresentationSpec);
+                    mInlineTooltipPresentationSpec,
+                    mServiceSupported,
+                    mClientSupported);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x100) != 0) {
+            if ((mBuilderFieldsSet & 0x400) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -686,10 +778,10 @@
     }
 
     @DataClass.Generated(
-            time = 1621415989607L,
-            codegenVersion = "1.0.23",
+            time = 1615798784918L,
+            codegenVersion = "1.0.22",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
-            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\npublic  void filterContentTypes()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate  boolean mServiceSupported\nprivate  boolean mClientSupported\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\npublic  void filterContentTypes()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nprivate static  boolean defaultServiceSupported()\nprivate static  boolean defaultClientSupported()\npublic  boolean isServiceSupported()\npublic  boolean isClientSupported()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/window/DisplayAreaInfo.java b/core/java/android/window/DisplayAreaInfo.java
index 358467f..1a7aab6 100644
--- a/core/java/android/window/DisplayAreaInfo.java
+++ b/core/java/android/window/DisplayAreaInfo.java
@@ -16,6 +16,8 @@
 
 package android.window;
 
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
+
 import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.content.res.Configuration;
@@ -43,8 +45,17 @@
      */
     public final int displayId;
 
+    /**
+     * The feature id of this display area.
+     */
     public final int featureId;
 
+    /**
+     * The feature id of the root display area this display area is associated with.
+     * @hide
+     */
+    public int rootDisplayAreaId = FEATURE_UNDEFINED;
+
     public DisplayAreaInfo(@NonNull WindowContainerToken token, int displayId, int featureId) {
         this.token = token;
         this.displayId = displayId;
@@ -56,6 +67,7 @@
         configuration.readFromParcel(in);
         displayId = in.readInt();
         featureId = in.readInt();
+        rootDisplayAreaId = in.readInt();
     }
 
     @Override
@@ -64,6 +76,7 @@
         configuration.writeToParcel(dest, flags);
         dest.writeInt(displayId);
         dest.writeInt(featureId);
+        dest.writeInt(rootDisplayAreaId);
     }
 
     @NonNull
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index 8784399..e674655 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -34,6 +34,15 @@
 public class DisplayAreaOrganizer extends WindowOrganizer {
 
     /**
+     * Key to specify the {@link com.android.server.wm.RootDisplayArea} to attach a window to.
+     * It will be used by the function passed in from
+     * {@link com.android.server.wm.DisplayAreaPolicyBuilder#setSelectRootForWindowFunc(BiFunction)}
+     * to find the Root DA to attach the window.
+     * @hide
+     */
+    public static final String KEY_ROOT_DISPLAY_AREA_ID = "root_display_area_id";
+
+    /**
      * The value in display area indicating that no value has been set.
      */
     public static final int FEATURE_UNDEFINED = -1;
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 23b8ee4..6430394 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -16,6 +16,12 @@
 
 package android.window;
 
+import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
+import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
+import static android.app.ActivityOptions.ANIM_SCALE_UP;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -31,6 +37,7 @@
 import android.app.ActivityManager;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.Surface;
@@ -102,6 +109,8 @@
     private SurfaceControl mRootLeash;
     private final Point mRootOffset = new Point();
 
+    private AnimationOptions mOptions;
+
     /** @hide */
     public TransitionInfo(@WindowManager.TransitionOldType int type,
             @WindowManager.TransitionFlags int flags) {
@@ -116,6 +125,7 @@
         mRootLeash = new SurfaceControl();
         mRootLeash.readFromParcel(in);
         mRootOffset.readFromParcel(in);
+        mOptions = in.readTypedObject(AnimationOptions.CREATOR);
     }
 
     @Override
@@ -126,6 +136,7 @@
         dest.writeList(mChanges);
         mRootLeash.writeToParcel(dest, flags);
         mRootOffset.writeToParcel(dest, flags);
+        dest.writeTypedObject(mOptions, flags);
     }
 
     @NonNull
@@ -154,6 +165,10 @@
         mRootOffset.set(offsetLeft, offsetTop);
     }
 
+    public void setAnimationOptions(AnimationOptions options) {
+        mOptions = options;
+    }
+
     public int getType() {
         return mType;
     }
@@ -182,6 +197,10 @@
         return mRootOffset;
     }
 
+    public AnimationOptions getAnimationOptions() {
+        return mOptions;
+    }
+
     @NonNull
     public List<Change> getChanges() {
         return mChanges;
@@ -484,4 +503,146 @@
                     + mStartRotation + "->" + mEndRotation + "}";
         }
     }
+
+    /** Represents animation options during a transition */
+    public static final class AnimationOptions implements Parcelable {
+
+        private int mType;
+        private int mEnterResId;
+        private int mExitResId;
+        private boolean mOverrideTaskTransition;
+        private String mPackageName;
+        private final Rect mTransitionBounds = new Rect();
+        private HardwareBuffer mThumbnail;
+
+        private AnimationOptions(int type) {
+            mType = type;
+        }
+
+        public AnimationOptions(Parcel in) {
+            mType = in.readInt();
+            mEnterResId = in.readInt();
+            mExitResId = in.readInt();
+            mOverrideTaskTransition = in.readBoolean();
+            mPackageName = in.readString();
+            mTransitionBounds.readFromParcel(in);
+            mThumbnail = in.readTypedObject(HardwareBuffer.CREATOR);
+        }
+
+        public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId,
+                int exitResId, boolean overrideTaskTransition) {
+            AnimationOptions options = new AnimationOptions(ANIM_CUSTOM);
+            options.mPackageName = packageName;
+            options.mEnterResId = enterResId;
+            options.mExitResId = exitResId;
+            options.mOverrideTaskTransition = overrideTaskTransition;
+            return options;
+        }
+
+        public static AnimationOptions makeClipRevealAnimOptions(int startX, int startY, int width,
+                int height) {
+            AnimationOptions options = new AnimationOptions(ANIM_CLIP_REVEAL);
+            options.mTransitionBounds.set(startX, startY, startX + width, startY + height);
+            return options;
+        }
+
+        public static AnimationOptions makeScaleUpAnimOptions(int startX, int startY, int width,
+                int height) {
+            AnimationOptions options = new AnimationOptions(ANIM_SCALE_UP);
+            options.mTransitionBounds.set(startX, startY, startX + width, startY + height);
+            return options;
+        }
+
+        public static AnimationOptions makeThumnbnailAnimOptions(HardwareBuffer srcThumb,
+                int startX, int startY, boolean scaleUp) {
+            AnimationOptions options = new AnimationOptions(
+                    scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN);
+            options.mTransitionBounds.set(startX, startY, startX, startY);
+            options.mThumbnail = srcThumb;
+            return options;
+        }
+
+        public static AnimationOptions makeCrossProfileAnimOptions() {
+            AnimationOptions options = new AnimationOptions(ANIM_OPEN_CROSS_PROFILE_APPS);
+            return options;
+        }
+
+        public int getType() {
+            return mType;
+        }
+
+        public int getEnterResId() {
+            return mEnterResId;
+        }
+
+        public int getExitResId() {
+            return mExitResId;
+        }
+
+        public boolean getOverrideTaskTransition() {
+            return mOverrideTaskTransition;
+        }
+
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        public Rect getTransitionBounds() {
+            return mTransitionBounds;
+        }
+
+        public HardwareBuffer getThumbnail() {
+            return mThumbnail;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mType);
+            dest.writeInt(mEnterResId);
+            dest.writeInt(mExitResId);
+            dest.writeBoolean(mOverrideTaskTransition);
+            dest.writeString(mPackageName);
+            mTransitionBounds.writeToParcel(dest, flags);
+            dest.writeTypedObject(mThumbnail, flags);
+        }
+
+        @NonNull
+        public static final Creator<AnimationOptions> CREATOR =
+                new Creator<AnimationOptions>() {
+                    @Override
+                    public AnimationOptions createFromParcel(Parcel in) {
+                        return new AnimationOptions(in);
+                    }
+
+                    @Override
+                    public AnimationOptions[] newArray(int size) {
+                        return new AnimationOptions[size];
+                    }
+                };
+
+        /** @hide */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @NonNull
+        private static String typeToString(int mode) {
+            switch(mode) {
+                case ANIM_CUSTOM: return "ANIM_CUSTOM";
+                case ANIM_CLIP_REVEAL: return "ANIM_CLIP_REVEAL";
+                case ANIM_SCALE_UP: return "ANIM_SCALE_UP";
+                case ANIM_THUMBNAIL_SCALE_UP: return "ANIM_THUMBNAIL_SCALE_UP";
+                case ANIM_THUMBNAIL_SCALE_DOWN: return "ANIM_THUMBNAIL_SCALE_DOWN";
+                case ANIM_OPEN_CROSS_PROFILE_APPS: return "ANIM_OPEN_CROSS_PROFILE_APPS";
+                default: return "<unknown:" + mode + ">";
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "{ AnimationOtions type= " + typeToString(mType) + " package=" + mPackageName
+                    + " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}";
+        }
+    }
 }
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index 901625b..69d7b4c 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -57,8 +57,14 @@
      *
      * @param base Base {@link Context} for this new instance.
      * @param type Window type to be used with this context.
-     * @param options A bundle used to pass window-related options.
-     *
+     * @param options A bundle used to pass window-related options. For example, on device with
+     *                multiple DisplayAreaGroups, one may specify the RootDisplayArea for the window
+     *                using {@link DisplayAreaOrganizer#KEY_ROOT_DISPLAY_AREA_ID} in the options.
+     *                Example usage:
+     *                Bundle options = new Bundle();
+     *                options.put(KEY_ROOT_DISPLAY_AREA_ID, displayAreaInfo.rootDisplayAreaId);
+     *                Context windowContext = context.createWindowContext(display, type, options);
+     * @see DisplayAreaInfo#rootDisplayAreaId
      * @hide
      */
     public WindowContext(@NonNull Context base, int type, @Nullable Bundle options) {
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 60a8d80..d3224b1 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -16,16 +16,20 @@
 
 package com.android.internal.policy;
 
+import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
 import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
+import static android.view.WindowManager.TRANSIT_OPEN;
 
+import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -34,12 +38,18 @@
 import android.content.res.ResourceId;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Picture;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.hardware.HardwareBuffer;
 import android.os.SystemProperties;
 import android.util.Slog;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.TransitionOldType;
+import android.view.WindowManager.TransitionType;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.view.animation.AnimationSet;
@@ -56,11 +66,17 @@
 
 /** @hide */
 public class TransitionAnimation {
+    public static final int WALLPAPER_TRANSITION_NONE = 0;
+    public static final int WALLPAPER_TRANSITION_OPEN = 1;
+    public static final int WALLPAPER_TRANSITION_CLOSE = 2;
+    public static final int WALLPAPER_TRANSITION_INTRA_OPEN = 3;
+    public static final int WALLPAPER_TRANSITION_INTRA_CLOSE = 4;
+
     // These are the possible states for the enter/exit activities during a thumbnail transition
-    public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
-    public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1;
-    public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2;
-    public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3;
+    private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
+    private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1;
+    private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2;
+    private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3;
 
     /**
      * Maximum duration for the clip reveal animation. This is used when there is a lot of movement
@@ -72,9 +88,15 @@
 
     public static final int DEFAULT_APP_TRANSITION_DURATION = 336;
 
+    /** Fraction of animation at which the recents thumbnail stays completely transparent */
+    private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.5f;
     /** Fraction of animation at which the recents thumbnail becomes completely transparent */
     private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.5f;
 
+    /** Interpolator to be used for animations that respond directly to a touch */
+    static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
+            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
     private static final String DEFAULT_PACKAGE = "android";
 
     private final Context mContext;
@@ -86,7 +108,9 @@
             new PathInterpolator(0.3f, 0f, 0.1f, 1f);
     private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f);
     private final Interpolator mDecelerateInterpolator;
+    private final Interpolator mFastOutLinearInInterpolator;
     private final Interpolator mLinearOutSlowInInterpolator;
+    private final Interpolator mThumbnailFadeInInterpolator;
     private final Interpolator mThumbnailFadeOutInterpolator;
     private final Rect mTmpFromClipRect = new Rect();
     private final Rect mTmpToClipRect = new Rect();
@@ -107,8 +131,19 @@
 
         mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.decelerate_cubic);
+        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.fast_out_linear_in);
         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.linear_out_slow_in);
+        mThumbnailFadeInInterpolator = input -> {
+            // Linear response for first fraction, then complete after that.
+            if (input < RECENTS_THUMBNAIL_FADEIN_FRACTION) {
+                return 0f;
+            }
+            float t = (input - RECENTS_THUMBNAIL_FADEIN_FRACTION)
+                    / (1f - RECENTS_THUMBNAIL_FADEIN_FRACTION);
+            return mFastOutLinearInInterpolator.getInterpolation(t);
+        };
         mThumbnailFadeOutInterpolator = input -> {
             // Linear response for first fraction, then complete after that.
             if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) {
@@ -181,6 +216,13 @@
                 DEFAULT_PACKAGE, com.android.internal.R.anim.cross_profile_apps_thumbnail_enter);
     }
 
+    @Nullable
+    public Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
+        final Animation animation = loadCrossProfileAppThumbnailEnterAnimation();
+        return prepareThumbnailAnimationWithDuration(animation, appRect.width(),
+                appRect.height(), 0, null);
+    }
+
     /** Load animation by resource Id from specific package. */
     @Nullable
     public Animation loadAnimationRes(String packageName, int resId) {
@@ -347,8 +389,15 @@
         }
     }
 
-    public Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame,
-            Rect displayFrame, Rect startRect) {
+    public Animation createClipRevealAnimationLocked(@TransitionType int transit,
+            int wallpaperTransit, boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) {
+        return createClipRevealAnimationLockedCompat(
+                getTransitCompatType(transit, wallpaperTransit), enter, appFrame, displayFrame,
+                startRect);
+    }
+
+    public Animation createClipRevealAnimationLockedCompat(@TransitionOldType int transit,
+            boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) {
         final Animation anim;
         if (enter) {
             final int appWidth = appFrame.width();
@@ -458,8 +507,14 @@
         return anim;
     }
 
-    public Animation createScaleUpAnimationLocked(int transit, boolean enter,
-            Rect containingFrame, Rect startRect) {
+    public Animation createScaleUpAnimationLocked(@TransitionType int transit, int wallpaperTransit,
+            boolean enter, Rect containingFrame, Rect startRect) {
+        return createScaleUpAnimationLockedCompat(getTransitCompatType(transit, wallpaperTransit),
+                enter, containingFrame, startRect);
+    }
+
+    public Animation createScaleUpAnimationLockedCompat(@TransitionOldType int transit,
+            boolean enter, Rect containingFrame, Rect startRect) {
         Animation a;
         setupDefaultNextAppTransitionStartRect(startRect, mTmpRect);
         final int appWidth = containingFrame.width();
@@ -514,12 +569,19 @@
         return a;
     }
 
+    public Animation createThumbnailEnterExitAnimationLocked(boolean enter, boolean scaleUp,
+            Rect containingFrame, @TransitionType int transit, int wallpaperTransit,
+            HardwareBuffer thumbnailHeader, Rect startRect) {
+        return createThumbnailEnterExitAnimationLockedCompat(enter, scaleUp, containingFrame,
+                getTransitCompatType(transit, wallpaperTransit), thumbnailHeader, startRect);
+    }
+
     /**
      * This animation is created when we are doing a thumbnail transition, for the activity that is
      * leaving, and the activity that is entering.
      */
-    public Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState,
-            Rect containingFrame, int transit, HardwareBuffer thumbnailHeader,
+    public Animation createThumbnailEnterExitAnimationLockedCompat(boolean enter, boolean scaleUp,
+            Rect containingFrame, @TransitionOldType int transit, HardwareBuffer thumbnailHeader,
             Rect startRect) {
         final int appWidth = containingFrame.width();
         final int appHeight = containingFrame.height();
@@ -529,6 +591,7 @@
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
         final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight;
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
+        final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp);
 
         switch (thumbTransitState) {
             case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
@@ -587,8 +650,8 @@
      * This alternate animation is created when we are doing a thumbnail transition, for the
      * activity that is leaving, and the activity that is entering.
      */
-    public Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
-            int orientation, int transit, Rect containingFrame, Rect contentInsets,
+    public Animation createAspectScaledThumbnailEnterExitAnimationLocked(boolean enter,
+            boolean scaleUp, int orientation, int transit, Rect containingFrame, Rect contentInsets,
             @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform,
             Rect startRect, Rect defaultStartRect) {
         Animation a;
@@ -601,11 +664,11 @@
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
         final int thumbStartX = mTmpRect.left - containingFrame.left - contentInsets.left;
         final int thumbStartY = mTmpRect.top - containingFrame.top;
+        final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp);
 
         switch (thumbTransitState) {
             case THUMBNAIL_TRANSITION_ENTER_SCALE_UP:
             case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
-                final boolean scaleUp = thumbTransitState == THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
                 if (freeform && scaleUp) {
                     a = createAspectScaledThumbnailEnterFreeformAnimationLocked(
                             containingFrame, surfaceInsets, startRect, defaultStartRect);
@@ -720,10 +783,151 @@
     }
 
     /**
+     * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
+     * when a thumbnail is specified with the pending animation override.
+     */
+    public Animation createThumbnailAspectScaleAnimationLocked(Rect appRect,
+            @Nullable Rect contentInsets, HardwareBuffer thumbnailHeader, int orientation,
+            Rect startRect, Rect defaultStartRect, boolean scaleUp) {
+        Animation a;
+        final int thumbWidthI = thumbnailHeader.getWidth();
+        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
+        final int thumbHeightI = thumbnailHeader.getHeight();
+        final int appWidth = appRect.width();
+
+        float scaleW = appWidth / thumbWidth;
+        getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect);
+        final float fromX;
+        float fromY;
+        final float toX;
+        float toY;
+        final float pivotX;
+        final float pivotY;
+        if (shouldScaleDownThumbnailTransition(orientation)) {
+            fromX = mTmpRect.left;
+            fromY = mTmpRect.top;
+
+            // For the curved translate animation to work, the pivot points needs to be at the
+            // same absolute position as the one from the real surface.
+            toX = mTmpRect.width() / 2 * (scaleW - 1f) + appRect.left;
+            toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top;
+            pivotX = mTmpRect.width() / 2;
+            pivotY = appRect.height() / 2 / scaleW;
+            if (mGridLayoutRecentsEnabled) {
+                // In the grid layout, the header is displayed above the thumbnail instead of
+                // overlapping it.
+                fromY -= thumbHeightI;
+                toY -= thumbHeightI * scaleW;
+            }
+        } else {
+            pivotX = 0;
+            pivotY = 0;
+            fromX = mTmpRect.left;
+            fromY = mTmpRect.top;
+            toX = appRect.left;
+            toY = appRect.top;
+        }
+        if (scaleUp) {
+            // Animation up from the thumbnail to the full screen
+            Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY);
+            scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+            scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+            Animation alpha = new AlphaAnimation(1f, 0f);
+            alpha.setInterpolator(mThumbnailFadeOutInterpolator);
+            alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+            Animation translate = createCurvedMotion(fromX, toX, fromY, toY);
+            translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+            translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+
+            mTmpFromClipRect.set(0, 0, thumbWidthI, thumbHeightI);
+            mTmpToClipRect.set(appRect);
+
+            // Containing frame is in screen space, but we need the clip rect in the
+            // app space.
+            mTmpToClipRect.offsetTo(0, 0);
+            mTmpToClipRect.right = (int) (mTmpToClipRect.right / scaleW);
+            mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom / scaleW);
+
+            if (contentInsets != null) {
+                mTmpToClipRect.inset((int) (-contentInsets.left * scaleW),
+                        (int) (-contentInsets.top * scaleW),
+                        (int) (-contentInsets.right * scaleW),
+                        (int) (-contentInsets.bottom * scaleW));
+            }
+
+            Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
+            clipAnim.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+            clipAnim.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+
+            // This AnimationSet uses the Interpolators assigned above.
+            AnimationSet set = new AnimationSet(false);
+            set.addAnimation(scale);
+            if (!mGridLayoutRecentsEnabled) {
+                // In the grid layout, the header should be shown for the whole animation.
+                set.addAnimation(alpha);
+            }
+            set.addAnimation(translate);
+            set.addAnimation(clipAnim);
+            a = set;
+        } else {
+            // Animation down from the full screen to the thumbnail
+            Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, pivotX, pivotY);
+            scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+            scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+            Animation alpha = new AlphaAnimation(0f, 1f);
+            alpha.setInterpolator(mThumbnailFadeInInterpolator);
+            alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+            Animation translate = createCurvedMotion(toX, fromX, toY, fromY);
+            translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+            translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+
+            // This AnimationSet uses the Interpolators assigned above.
+            AnimationSet set = new AnimationSet(false);
+            set.addAnimation(scale);
+            if (!mGridLayoutRecentsEnabled) {
+                // In the grid layout, the header should be shown for the whole animation.
+                set.addAnimation(alpha);
+            }
+            set.addAnimation(translate);
+            a = set;
+
+        }
+        return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0,
+                null);
+    }
+
+    /**
+     * Creates an overlay with a background color and a thumbnail for the cross profile apps
+     * animation.
+     */
+    public HardwareBuffer createCrossProfileAppsThumbnail(
+            @DrawableRes int thumbnailDrawableRes, Rect frame) {
+        final int width = frame.width();
+        final int height = frame.height();
+
+        final Picture picture = new Picture();
+        final Canvas canvas = picture.beginRecording(width, height);
+        canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
+        final int thumbnailSize = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
+        final Drawable drawable = mContext.getDrawable(thumbnailDrawableRes);
+        drawable.setBounds(
+                (width - thumbnailSize) / 2,
+                (height - thumbnailSize) / 2,
+                (width + thumbnailSize) / 2,
+                (height + thumbnailSize) / 2);
+        drawable.setTint(mContext.getColor(android.R.color.white));
+        drawable.draw(canvas);
+        picture.endRecording();
+
+        return Bitmap.createBitmap(picture).getHardwareBuffer();
+    }
+
+    /**
      * Prepares the specified animation with a standard duration, interpolator, etc.
      */
     private Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight,
-            int transit) {
+            @TransitionOldType int transit) {
         // Pick the desired duration.  If this is an inter-activity transition,
         // it  is the standard duration for that.  Otherwise we use the longer
         // task transition duration.
@@ -820,6 +1024,22 @@
         return anim;
     }
 
+    private static @TransitionOldType int getTransitCompatType(@TransitionType int transit,
+            int wallpaperTransit) {
+        if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
+            return TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
+            return TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
+        } else if (transit == TRANSIT_OPEN) {
+            return TRANSIT_OLD_ACTIVITY_OPEN;
+        } else if (transit == TRANSIT_CLOSE) {
+            return TRANSIT_OLD_ACTIVITY_CLOSE;
+        }
+
+        // We only do some special handle for above type, so use type NONE for default behavior.
+        return TRANSIT_OLD_NONE;
+    }
+
     /**
      * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that
      * the start rect is outside of the target rect, and there is a lot of movement going on.
@@ -843,10 +1063,33 @@
     }
 
     /**
+     * Return the current thumbnail transition state.
+     */
+    private int getThumbnailTransitionState(boolean enter, boolean scaleUp) {
+        if (enter) {
+            if (scaleUp) {
+                return THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
+            } else {
+                return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
+            }
+        } else {
+            if (scaleUp) {
+                return THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
+            } else {
+                return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
+            }
+        }
+    }
+
+    /**
      * Prepares the specified animation with a standard duration, interpolator, etc.
      */
-    private static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth,
+    public static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth,
             int appHeight, long duration, Interpolator interpolator) {
+        if (a == null) {
+            return null;
+        }
+
         if (duration > 0) {
             a.setDuration(duration);
         }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ab53b4c..6a42ca4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3561,8 +3561,8 @@
     -->
     <integer name="config_largeScreenSmallestScreenWidthDp">600</integer>
 
-    <!-- True if the device is using leagacy split. -->
-    <bool name="config_useLegacySplit">true</bool>
+    <!-- True if the device is using legacy split. -->
+    <bool name="config_useLegacySplit">false</bool>
 
     <!-- True if the device supports running activities on secondary displays. -->
     <bool name="config_supportsMultiDisplay">true</bool>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 552898b..4690294 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3243,10 +3243,84 @@
   </staging-public-group>
 
   <!-- ===============================================================
+    Resources added in version S-V2 of the platform
+
+    NOTE: add <public> elements within a <staging-public-group> like so:
+
+    <staging-public-group type="attr" first-id="0x01ff0000">
+        <public name="exampleAttr1" />
+        <public name="exampleAttr2" />
+    </staging-public-group>
+
+    To add a new <staging-public-group> block, find the id value for the
+    last <staging-public-group> block defined for thie API level, and
+    subtract 0x00010000 from it to get to the id of the new block.
+
+    For example, if the block closest to the end of this file has an id of
+    0x01ee0000, the id of the new block should be 0x01ed0000
+    (0x01ee0000 - 0x00010000 = 0x01ed0000).
+    =============================================================== -->
+  <eat-comment />
+
+  <staging-public-group type="attr" first-id="0x01ff0000">
+  </staging-public-group>
+
+  <staging-public-group type="id" first-id="0x01fe0000">
+  </staging-public-group>
+
+  <staging-public-group type="style" first-id="0x01fd0000">
+  </staging-public-group>
+
+  <staging-public-group type="string" first-id="0x01fc0000">
+  </staging-public-group>
+
+  <staging-public-group type="dimen" first-id="0x01fb0000">
+  </staging-public-group>
+
+  <staging-public-group type="color" first-id="0x01fa0000">
+  </staging-public-group>
+
+  <staging-public-group type="array" first-id="0x01f90000">
+  </staging-public-group>
+
+  <staging-public-group type="drawable" first-id="0x01f80000">
+  </staging-public-group>
+
+  <staging-public-group type="layout" first-id="0x01f70000">
+  </staging-public-group>
+
+  <staging-public-group type="anim" first-id="0x01f60000">
+  </staging-public-group>
+
+  <staging-public-group type="animator" first-id="0x01f50000">
+  </staging-public-group>
+
+  <staging-public-group type="interpolator" first-id="0x01f40000">
+  </staging-public-group>
+
+  <staging-public-group type="mipmap" first-id="0x01f30000">
+  </staging-public-group>
+
+  <staging-public-group type="integer" first-id="0x01f20000">
+  </staging-public-group>
+
+  <staging-public-group type="transition" first-id="0x01f10000">
+  </staging-public-group>
+
+  <staging-public-group type="raw" first-id="0x01f00000">
+  </staging-public-group>
+
+  <staging-public-group type="bool" first-id="0x01ef0000">
+  </staging-public-group>
+
+  <staging-public-group type="fraction" first-id="0x01ee0000">
+  </staging-public-group>
+
+  <!-- ===============================================================
        DO NOT ADD UN-GROUPED ITEMS HERE
 
        Any new items (attrs, styles, ids, etc.) *must* be added in a
-       public-group block, as the preceding comment explains.
+       staging-public-group block, as the preceding comment explains.
        Items added outside of a group may have their value recalculated
        every time something new is added to this file.
        =============================================================== -->
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java
index c65ef9a..53ba140 100644
--- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java
@@ -16,18 +16,40 @@
 
 package android.accessibilityservice;
 
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
 import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityEvent;
+import android.window.WindowTokenClient;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -42,6 +64,8 @@
 public class AccessibilityServiceTest {
     private static final String TAG = "AccessibilityServiceTest";
     private static final int CONNECTION_ID = 1;
+    private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(
+            TYPE_ACCESSIBILITY_OVERLAY);
 
     private static class AccessibilityServiceTestClass extends AccessibilityService {
         private IAccessibilityServiceClient mCallback;
@@ -49,7 +73,11 @@
 
         AccessibilityServiceTestClass() {
             super();
-            attachBaseContext(InstrumentationRegistry.getContext());
+            Context context = ApplicationProvider.getApplicationContext();
+            final Display display = context.getSystemService(DisplayManager.class)
+                    .getDisplay(DEFAULT_DISPLAY);
+
+            attachBaseContext(context.createTokenContext(new WindowTokenClient(), display));
             mLooper = InstrumentationRegistry.getContext().getMainLooper();
         }
 
@@ -78,14 +106,33 @@
     private @Mock IBinder mMockIBinder;
     private IAccessibilityServiceClient mServiceInterface;
     private AccessibilityServiceTestClass mService;
+    private final SparseArray<IBinder> mWindowTokens = new SparseArray<>();
 
     @Before
-    public void setUp() throws RemoteException {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mService = new AccessibilityServiceTestClass();
+        mService.onCreate();
         mService.setupCallback(mMockClientForCallback);
         mServiceInterface = (IAccessibilityServiceClient) mService.onBind(new Intent());
         mServiceInterface.init(mMockConnection, CONNECTION_ID, mMockIBinder);
+        doAnswer(invocation -> {
+            Object[] args = invocation.getArguments();
+            final int displayId = (int) args[0];
+            final IBinder token = new Binder();
+            WindowManagerGlobal.getWindowManagerService().addWindowToken(token,
+                    TYPE_ACCESSIBILITY_OVERLAY, displayId, null /* options */);
+            mWindowTokens.put(displayId, token);
+            return token;
+        }).when(mMockConnection).getOverlayWindowToken(anyInt());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        for (int i = mWindowTokens.size() - 1; i >= 0; --i) {
+            WindowManagerGlobal.getWindowManagerService().removeWindowToken(
+                    mWindowTokens.valueAt(i), mWindowTokens.keyAt(i));
+        }
     }
 
     @Test
@@ -101,4 +148,79 @@
 
         verify(mMockConnection).getSystemActions();
     }
+
+    @Test
+    public void testAddViewWithA11yServiceDerivedDisplayContext() throws Exception {
+        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+            final Context context = mService.createDisplayContext(session.getDisplay());
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                    () -> context.getSystemService(WindowManager.class)
+                            .addView(new View(context), mParams)
+            );
+        }
+    }
+
+    @Test
+    public void testAddViewWithA11yServiceDerivedWindowContext() throws Exception {
+        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+            final Context context = mService.createDisplayContext(session.getDisplay())
+                    .createWindowContext(TYPE_ACCESSIBILITY_OVERLAY, null /* options */);
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                    () -> context.getSystemService(WindowManager.class)
+                            .addView(new View(context), mParams)
+            );
+        }
+    }
+
+    @Test
+    public void testAddViewWithA11yServiceDerivedWindowContextWithDisplay() throws Exception {
+        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+            final Context context = mService.createWindowContext(session.getDisplay(),
+                    TYPE_ACCESSIBILITY_OVERLAY, null /* options */);
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                    () -> context.getSystemService(WindowManager.class)
+                            .addView(new View(context), mParams)
+            );
+        }
+    }
+
+    @Test(expected = WindowManager.BadTokenException.class)
+    public void testAddViewWithA11yServiceDerivedWindowContextWithDifferentType()
+            throws Exception {
+        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+            final Context context = mService.createWindowContext(session.getDisplay(),
+                    TYPE_APPLICATION_OVERLAY, null /* options */);
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                    () -> context.getSystemService(WindowManager.class)
+                            .addView(new View(context), mParams)
+            );
+        }
+    }
+
+
+    private static class VirtualDisplaySession implements AutoCloseable {
+        private final VirtualDisplay mVirtualDisplay;
+
+        VirtualDisplaySession() {
+            final DisplayManager displayManager = ApplicationProvider.getApplicationContext()
+                    .getSystemService(DisplayManager.class);
+            final int width = 800;
+            final int height = 480;
+            final int density = 160;
+            ImageReader reader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888,
+                    2 /* maxImages */);
+            mVirtualDisplay = displayManager.createVirtualDisplay(
+                    TAG, width, height, density, reader.getSurface(),
+                    VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+        }
+
+        private Display getDisplay() {
+            return mVirtualDisplay.getDisplay();
+        }
+
+        @Override
+        public void close() throws Exception {
+            mVirtualDisplay.release();
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
index d9409ec..b1fa2ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
@@ -204,7 +204,8 @@
 
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (transition != mPendingDismiss && transition != mPendingEnter) {
             // If we're not in split-mode, just abort
@@ -239,12 +240,12 @@
                 if (change.getParent() != null) {
                     // This is probably reparented, so we want the parent to be immediately visible
                     final TransitionInfo.Change parentChange = info.getChange(change.getParent());
-                    t.show(parentChange.getLeash());
-                    t.setAlpha(parentChange.getLeash(), 1.f);
+                    startTransaction.show(parentChange.getLeash());
+                    startTransaction.setAlpha(parentChange.getLeash(), 1.f);
                     // and then animate this layer outside the parent (since, for example, this is
                     // the home task animating from fullscreen to part-screen).
-                    t.reparent(leash, info.getRootLeash());
-                    t.setLayer(leash, info.getChanges().size() - i);
+                    startTransaction.reparent(leash, info.getRootLeash());
+                    startTransaction.setLayer(leash, info.getChanges().size() - i);
                     // build the finish reparent/reposition
                     mFinishTransaction.reparent(leash, parentChange.getLeash());
                     mFinishTransaction.setPosition(leash,
@@ -271,12 +272,12 @@
             if (transition == mPendingEnter
                     && mListener.mPrimary.token.equals(change.getContainer())
                     || mListener.mSecondary.token.equals(change.getContainer())) {
-                t.setWindowCrop(leash, change.getStartAbsBounds().width(),
+                startTransaction.setWindowCrop(leash, change.getStartAbsBounds().width(),
                         change.getStartAbsBounds().height());
                 if (mListener.mPrimary.token.equals(change.getContainer())) {
                     // Move layer to top since we want it above the oversized home task during
                     // animation even though home task is on top in hierarchy.
-                    t.setLayer(leash, info.getChanges().size() + 1);
+                    startTransaction.setLayer(leash, info.getChanges().size() + 1);
                 }
             }
             boolean isOpening = Transitions.isOpeningType(info.getType());
@@ -289,7 +290,7 @@
                     // Dismissing via snap-to-top/bottom means that the dismissed task is already
                     // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
                     // and don't animate it so it doesn't pop-in when reparented.
-                    t.setAlpha(leash, 0.f);
+                    startTransaction.setAlpha(leash, 0.f);
                 } else {
                     startExampleAnimation(leash, false /* show */);
                 }
@@ -311,7 +312,7 @@
             }
             mSplitScreen.finishEnterSplitTransition(homeIsVisible);
         }
-        t.apply();
+        startTransaction.apply();
         onFinish();
         return true;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 4759550..88ee9c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -35,6 +35,7 @@
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransactionCallback;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -68,7 +69,8 @@
     @Override
     public boolean startAnimation(@android.annotation.NonNull IBinder transition,
             @android.annotation.NonNull TransitionInfo info,
-            @android.annotation.NonNull SurfaceControl.Transaction t,
+            @android.annotation.NonNull SurfaceControl.Transaction startTransaction,
+            @android.annotation.NonNull SurfaceControl.Transaction finishTransaction,
             @android.annotation.NonNull Transitions.TransitionFinishCallback finishCallback) {
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
@@ -76,7 +78,8 @@
                     && change.getTaskInfo().configuration.windowConfiguration.getWindowingMode()
                     == WINDOWING_MODE_PINNED) {
                 mFinishCallback = finishCallback;
-                return startEnterAnimation(change.getTaskInfo(), change.getLeash(), t);
+                return startEnterAnimation(change.getTaskInfo(), change.getLeash(),
+                        startTransaction, finishTransaction);
             }
         }
         return false;
@@ -96,17 +99,25 @@
         WindowContainerTransaction wct = new WindowContainerTransaction();
         prepareFinishResizeTransaction(taskInfo, destinationBounds,
                 direction, tx, wct);
-        mFinishCallback.onTransitionFinished(wct, null);
+        mFinishCallback.onTransitionFinished(wct, new WindowContainerTransactionCallback() {
+            @Override
+            public void onTransactionReady(int id, @NonNull SurfaceControl.Transaction t) {
+                t.merge(tx);
+                t.apply();
+            }
+        });
         finishResizeForMenu(destinationBounds);
     }
 
     private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
-            final SurfaceControl.Transaction t) {
+            final SurfaceControl.Transaction startTransaction,
+            final SurfaceControl.Transaction finishTransaction) {
         setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
                 taskInfo.topActivityInfo);
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
         PipAnimationController.PipTransitionAnimator animator;
+        finishTransaction.setPosition(leash, destinationBounds.left, destinationBounds.top);
         if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
             final Rect sourceHintRect =
                     PipBoundsAlgorithm.getValidSourceHintRect(
@@ -115,8 +126,8 @@
                     currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
                     0 /* startingAngle */, Surface.ROTATION_0);
         } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
-            t.setAlpha(leash, 0f);
-            t.apply();
+            startTransaction.setAlpha(leash, 0f);
+            startTransaction.apply();
             animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
                     0f, 1f);
             mOneShotAnimationType = ANIM_TYPE_BOUNDS;
@@ -158,6 +169,5 @@
         }
 
         wct.setBounds(taskInfo.token, taskBounds);
-        wct.setBoundsChangeTransaction(taskInfo.token, tx);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index b7caf72..551476d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -58,7 +58,8 @@
 
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @android.annotation.NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         return false;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index c37789e..69d0be6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -84,17 +84,19 @@
     }
 
     void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
         mFinishCallback = finishCallback;
         mAnimatingTransition = transition;
         if (mRemoteHandler != null) {
-            mRemoteHandler.startAnimation(transition, info, t, mRemoteFinishCB);
+            mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
+                    mRemoteFinishCB);
             mRemoteHandler = null;
             return;
         }
-        playInternalAnimation(transition, info, t, mainRoot, sideRoot);
+        playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
     }
 
     private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 0264c5a..2545d6b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -672,7 +672,8 @@
     @Override
     public boolean startAnimation(@NonNull IBinder transition,
             @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (transition != mSplitTransitions.mPendingDismiss
                 && transition != mSplitTransitions.mPendingEnter) {
@@ -717,14 +718,14 @@
 
         boolean shouldAnimate = true;
         if (mSplitTransitions.mPendingEnter == transition) {
-            shouldAnimate = startPendingEnterAnimation(transition, info, t);
+            shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
         } else if (mSplitTransitions.mPendingDismiss == transition) {
-            shouldAnimate = startPendingDismissAnimation(transition, info, t);
+            shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction);
         }
         if (!shouldAnimate) return false;
 
-        mSplitTransitions.playAnimation(transition, info, t, finishCallback,
-                mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+        mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
+                finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
         return true;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index c6fb5af..4eadf8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -16,6 +16,13 @@
 
 package com.android.wm.shell.transition;
 
+import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
+import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.app.ActivityOptions.ANIM_NONE;
+import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
+import static android.app.ActivityOptions.ANIM_SCALE_UP;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
@@ -29,17 +36,28 @@
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.os.IBinder;
+import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.view.Choreographer;
 import android.view.SurfaceControl;
+import android.view.SurfaceSession;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.view.animation.Transformation;
@@ -61,30 +79,53 @@
 public class DefaultTransitionHandler implements Transitions.TransitionHandler {
     private static final int MAX_ANIMATION_DURATION = 3000;
 
+    /**
+     * Restrict ability of activities overriding transition animation in a way such that
+     * an activity can do it only when the transition happens within a same task.
+     *
+     * @see android.app.Activity#overridePendingTransition(int, int)
+     */
+    private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
+            "persist.wm.disable_custom_task_animation";
+
+    /**
+     * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
+     */
+    static boolean sDisableCustomTaskAnimationProperty =
+            SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
+
     private final TransactionPool mTransactionPool;
+    private final Context mContext;
     private final ShellExecutor mMainExecutor;
     private final ShellExecutor mAnimExecutor;
     private final TransitionAnimation mTransitionAnimation;
 
+    private final SurfaceSession mSurfaceSession = new SurfaceSession();
+
     /** Keeps track of the currently-running animations associated with each transition. */
     private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();
 
     private final Rect mInsets = new Rect(0, 0, 0, 0);
     private float mTransitionAnimationScaleSetting = 1.0f;
 
+    private final int mCurrentUserId;
+
     DefaultTransitionHandler(@NonNull TransactionPool transactionPool, Context context,
             @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
         mTransactionPool = transactionPool;
+        mContext = context;
         mMainExecutor = mainExecutor;
         mAnimExecutor = animExecutor;
         mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
+        mCurrentUserId = UserHandle.myUserId();
 
         AttributeCache.init(context);
     }
 
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                 "start default transition animation, info = %s", info);
@@ -100,16 +141,18 @@
             mAnimations.remove(transition);
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
         };
+
+        final int wallpaperTransit = getWallpaperTransitType(info);
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
             if (change.getMode() == TRANSIT_CHANGE) {
                 // No default animation for this, so just update bounds/position.
-                t.setPosition(change.getLeash(),
+                startTransaction.setPosition(change.getLeash(),
                         change.getEndAbsBounds().left - change.getEndRelOffset().x,
                         change.getEndAbsBounds().top - change.getEndRelOffset().y);
                 if (change.getTaskInfo() != null) {
                     // Skip non-tasks since those usually have null bounds.
-                    t.setWindowCrop(change.getLeash(),
+                    startTransaction.setWindowCrop(change.getLeash(),
                             change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
                 }
             }
@@ -117,12 +160,17 @@
             // Don't animate anything that isn't independent.
             if (!TransitionInfo.isIndependent(change, info)) continue;
 
-            Animation a = loadAnimation(info.getType(), info.getFlags(), change);
+            Animation a = loadAnimation(info, change, wallpaperTransit);
             if (a != null) {
-                startAnimInternal(animations, a, change.getLeash(), onAnimFinish);
+                startAnimInternal(animations, a, change.getLeash(), onAnimFinish,
+                        null /* position */);
+
+                if (info.getAnimationOptions() != null) {
+                    attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions());
+                }
             }
         }
-        t.apply();
+        startTransaction.apply();
         // run finish now in-case there are no animations
         onAnimFinish.run();
         return true;
@@ -141,68 +189,111 @@
     }
 
     @Nullable
-    private Animation loadAnimation(int type, int flags, TransitionInfo.Change change) {
-        // TODO(b/178678389): It should handle more type animation here
+    private Animation loadAnimation(TransitionInfo info, TransitionInfo.Change change,
+            int wallpaperTransit) {
         Animation a = null;
 
-        final boolean isOpening = Transitions.isOpeningType(type);
+        final int type = info.getType();
+        final int flags = info.getFlags();
         final int changeMode = change.getMode();
         final int changeFlags = change.getFlags();
+        final boolean isOpeningType = Transitions.isOpeningType(type);
+        final boolean enter = Transitions.isOpeningType(changeMode);
+        final boolean isTask = change.getTaskInfo() != null;
+        final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
+        final int overrideType = options != null ? options.getType() : ANIM_NONE;
+        final boolean canCustomContainer = isTask ? !sDisableCustomTaskAnimationProperty : true;
 
         if (type == TRANSIT_RELAUNCH) {
             a = mTransitionAnimation.createRelaunchAnimation(
-                    change.getStartAbsBounds(), mInsets, change.getEndAbsBounds());
+                    change.getEndAbsBounds(), mInsets, change.getEndAbsBounds());
         } else if (type == TRANSIT_KEYGUARD_GOING_AWAY) {
             a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
                     (changeFlags & FLAG_SHOW_WALLPAPER) != 0);
         } else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) {
             a = mTransitionAnimation.loadKeyguardUnoccludeAnimation();
-        } else if (changeMode == TRANSIT_OPEN && isOpening) {
-            if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
-                // This received a transferred starting window, so don't animate
-                return null;
-            }
-
-            if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
-                a = mTransitionAnimation.loadVoiceActivityOpenAnimation(true /** enter */);
-            } else if (change.getTaskInfo() != null) {
-                a = mTransitionAnimation.loadDefaultAnimationAttr(
-                        R.styleable.WindowAnimation_taskOpenEnterAnimation);
+        } else if (overrideType == ANIM_CUSTOM
+                && (canCustomContainer || options.getOverrideTaskTransition())) {
+            a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
+                    ? options.getEnterResId() : options.getExitResId());
+        } else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) {
+            a = mTransitionAnimation.loadCrossProfileAppEnterAnimation();
+        } else if (overrideType == ANIM_CLIP_REVEAL) {
+            a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter,
+                    change.getEndAbsBounds(), change.getEndAbsBounds(),
+                    options.getTransitionBounds());
+        } else if (overrideType == ANIM_SCALE_UP) {
+            a = mTransitionAnimation.createScaleUpAnimationLocked(type, wallpaperTransit, enter,
+                    change.getEndAbsBounds(), options.getTransitionBounds());
+        } else if (overrideType == ANIM_THUMBNAIL_SCALE_UP
+                || overrideType == ANIM_THUMBNAIL_SCALE_DOWN) {
+            final boolean scaleUp = overrideType == ANIM_THUMBNAIL_SCALE_UP;
+            a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(enter, scaleUp,
+                    change.getEndAbsBounds(), type, wallpaperTransit, options.getThumbnail(),
+                    options.getTransitionBounds());
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
+            a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                    ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
+                    : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation);
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
+            a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                    ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
+                    : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation);
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
+            a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                    ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
+                    : R.styleable.WindowAnimation_wallpaperOpenExitAnimation);
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
+            a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                    ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
+                    : R.styleable.WindowAnimation_wallpaperCloseExitAnimation);
+        } else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
+            if (isOpeningType) {
+                a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter);
             } else {
-                a = mTransitionAnimation.loadDefaultAnimationRes(
-                        (changeFlags & FLAG_TRANSLUCENT) == 0
-                        ? R.anim.activity_open_enter : R.anim.activity_translucent_open_enter);
+                a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter);
             }
-        } else if (changeMode == TRANSIT_TO_FRONT && isOpening) {
-            if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
-                // This received a transferred starting window, so don't animate
-                return null;
-            }
-
-            if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
-                a = mTransitionAnimation.loadVoiceActivityOpenAnimation(true /** enter */);
+        } else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) {
+            // This received a transferred starting window, so don't animate
+            return null;
+        } else if (type == TRANSIT_OPEN) {
+            if (isTask) {
+                a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                        ? R.styleable.WindowAnimation_taskOpenEnterAnimation
+                        : R.styleable.WindowAnimation_taskOpenExitAnimation);
             } else {
-                a = mTransitionAnimation.loadDefaultAnimationAttr(
-                        R.styleable.WindowAnimation_taskToFrontEnterAnimation);
+                if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
+                    a = mTransitionAnimation.loadDefaultAnimationRes(
+                            R.anim.activity_translucent_open_enter);
+                } else {
+                    a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                            ? R.styleable.WindowAnimation_activityOpenEnterAnimation
+                            : R.styleable.WindowAnimation_activityOpenExitAnimation);
+                }
             }
-        } else if (changeMode == TRANSIT_CLOSE && !isOpening) {
-            if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
-                a = mTransitionAnimation.loadVoiceActivityExitAnimation(false /** enter */);
-            } else if (change.getTaskInfo() != null) {
-                a = mTransitionAnimation.loadDefaultAnimationAttr(
-                        R.styleable.WindowAnimation_taskCloseExitAnimation);
+        } else if (type == TRANSIT_TO_FRONT) {
+            a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                    ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
+                    : R.styleable.WindowAnimation_taskToFrontExitAnimation);
+        } else if (type == TRANSIT_CLOSE) {
+            if (isTask) {
+                a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                        ? R.styleable.WindowAnimation_taskCloseEnterAnimation
+                        : R.styleable.WindowAnimation_taskCloseExitAnimation);
             } else {
-                a = mTransitionAnimation.loadDefaultAnimationRes(
-                        (changeFlags & FLAG_TRANSLUCENT) == 0
-                        ? R.anim.activity_close_exit : R.anim.activity_translucent_close_exit);
+                if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
+                    a = mTransitionAnimation.loadDefaultAnimationRes(
+                            R.anim.activity_translucent_close_exit);
+                } else {
+                    a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                            ? R.styleable.WindowAnimation_activityCloseEnterAnimation
+                            : R.styleable.WindowAnimation_activityCloseExitAnimation);
+                }
             }
-        } else if (changeMode == TRANSIT_TO_BACK && !isOpening) {
-            if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
-                a = mTransitionAnimation.loadVoiceActivityExitAnimation(false /** enter */);
-            } else {
-                a = mTransitionAnimation.loadDefaultAnimationAttr(
-                        R.styleable.WindowAnimation_taskToBackExitAnimation);
-            }
+        } else if (type == TRANSIT_TO_BACK) {
+            a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                    ? R.styleable.WindowAnimation_taskToBackEnterAnimation
+                    : R.styleable.WindowAnimation_taskToBackExitAnimation);
         } else if (changeMode == TRANSIT_CHANGE) {
             // In the absence of a specific adapter, we just want to keep everything stationary.
             a = new AlphaAnimation(1.f, 1.f);
@@ -210,17 +301,19 @@
         }
 
         if (a != null) {
-            Rect start = change.getStartAbsBounds();
-            Rect end = change.getEndAbsBounds();
+            if (!a.isInitialized()) {
+                Rect end = change.getEndAbsBounds();
+                a.initialize(end.width(), end.height(), end.width(), end.height());
+            }
             a.restrictDuration(MAX_ANIMATION_DURATION);
-            a.initialize(end.width(), end.height(), start.width(), start.height());
             a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         }
         return a;
     }
 
     private void startAnimInternal(@NonNull ArrayList<Animator> animations, @NonNull Animation anim,
-            @NonNull SurfaceControl leash, @NonNull Runnable finishCallback) {
+            @NonNull SurfaceControl leash, @NonNull Runnable finishCallback,
+            @Nullable Point position) {
         final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
         final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
         final Transformation transformation = new Transformation();
@@ -231,11 +324,13 @@
         va.addUpdateListener(animation -> {
             final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
 
-            applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix);
+            applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
+                    position);
         });
 
         final Runnable finisher = () -> {
-            applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix);
+            applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
+                    position);
 
             mTransactionPool.release(transaction);
             mMainExecutor.execute(() -> {
@@ -258,9 +353,112 @@
         mAnimExecutor.execute(va::start);
     }
 
+    private void attachThumbnail(@NonNull ArrayList<Animator> animations,
+            @NonNull Runnable finishCallback, TransitionInfo.Change change,
+            TransitionInfo.AnimationOptions options) {
+        final boolean isTask = change.getTaskInfo() != null;
+        final boolean isOpen = Transitions.isOpeningType(change.getMode());
+        final boolean isClose = Transitions.isClosingType(change.getMode());
+        if (isOpen) {
+            if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) {
+                attachCrossProfileThunmbnailAnimation(animations, finishCallback, change);
+            } else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) {
+                attachThumbnailAnimation(animations, finishCallback, change, options);
+            }
+        } else if (isClose && options.getType() == ANIM_THUMBNAIL_SCALE_DOWN) {
+            attachThumbnailAnimation(animations, finishCallback, change, options);
+        }
+    }
+
+    private void attachCrossProfileThunmbnailAnimation(@NonNull ArrayList<Animator> animations,
+            @NonNull Runnable finishCallback, TransitionInfo.Change change) {
+        final int thumbnailDrawableRes = change.getTaskInfo().userId == mCurrentUserId
+                ? R.drawable.ic_account_circle : R.drawable.ic_corp_badge;
+        final Rect bounds = change.getEndAbsBounds();
+        final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail(
+                thumbnailDrawableRes, bounds);
+        if (thumbnail == null) {
+            return;
+        }
+
+        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+        final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession,
+                change.getLeash(), thumbnail, transaction);
+        final Animation a =
+                mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(bounds);
+        if (a == null) {
+            return;
+        }
+
+        final Runnable finisher = () -> {
+            wt.destroy(transaction);
+            mTransactionPool.release(transaction);
+
+            finishCallback.run();
+        };
+        a.restrictDuration(MAX_ANIMATION_DURATION);
+        a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+        startAnimInternal(animations, a, wt.getSurface(), finisher,
+                new Point(bounds.left, bounds.top));
+    }
+
+    private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations,
+            @NonNull Runnable finishCallback, TransitionInfo.Change change,
+            TransitionInfo.AnimationOptions options) {
+        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+        final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession,
+                change.getLeash(), options.getThumbnail(), transaction);
+        final Rect bounds = change.getEndAbsBounds();
+        final int orientation = mContext.getResources().getConfiguration().orientation;
+        final Animation a = mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(bounds,
+                mInsets, options.getThumbnail(), orientation, null /* startRect */,
+                options.getTransitionBounds(), options.getType() == ANIM_THUMBNAIL_SCALE_UP);
+
+        final Runnable finisher = () -> {
+            wt.destroy(transaction);
+            mTransactionPool.release(transaction);
+
+            finishCallback.run();
+        };
+        a.restrictDuration(MAX_ANIMATION_DURATION);
+        a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+        startAnimInternal(animations, a, wt.getSurface(), finisher, null /* position */);
+    }
+
+    private static int getWallpaperTransitType(TransitionInfo info) {
+        boolean hasOpenWallpaper = false;
+        boolean hasCloseWallpaper = false;
+
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) {
+                if (Transitions.isOpeningType(change.getMode())) {
+                    hasOpenWallpaper = true;
+                } else if (Transitions.isClosingType(change.getMode())) {
+                    hasCloseWallpaper = true;
+                }
+            }
+        }
+
+        if (hasOpenWallpaper && hasCloseWallpaper) {
+            return Transitions.isOpeningType(info.getType())
+                    ? WALLPAPER_TRANSITION_INTRA_OPEN : WALLPAPER_TRANSITION_INTRA_CLOSE;
+        } else if (hasOpenWallpaper) {
+            return WALLPAPER_TRANSITION_OPEN;
+        } else if (hasCloseWallpaper) {
+            return WALLPAPER_TRANSITION_CLOSE;
+        } else {
+            return WALLPAPER_TRANSITION_NONE;
+        }
+    }
+
     private static void applyTransformation(long time, SurfaceControl.Transaction t,
-            SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix) {
+            SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix,
+            Point position) {
         anim.getTransformation(time, transformation);
+        if (position != null) {
+            transformation.getMatrix().postTranslate(position.x, position.y);
+        }
         t.setMatrix(leash, transformation.getMatrix(), matrix);
         t.setAlpha(leash, transformation.getAlpha());
         t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4da6664..977941c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -57,7 +57,8 @@
 
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (mTransition != transition) return false;
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
@@ -82,7 +83,7 @@
             if (mRemote.asBinder() != null) {
                 mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
             }
-            mRemote.startAnimation(transition, info, t, cb);
+            mRemote.startAnimation(transition, info, startTransaction, cb);
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error running remote transition.", e);
             if (mRemote.asBinder() != null) {
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 9bfb261..8bc62ae 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
@@ -99,7 +99,8 @@
 
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         IRemoteTransition pendingRemote = mRequestedRemotes.get(transition);
         if (pendingRemote == null) {
@@ -146,7 +147,7 @@
             if (remote.asBinder() != null) {
                 remote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
             }
-            remote.startAnimation(transition, info, t, cb);
+            remote.startAnimation(transition, info, startTransaction, cb);
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error running remote transition.", e);
             if (remote.asBinder() != null) {
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 60707cc..8130943 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
@@ -382,7 +382,7 @@
     }
 
     boolean startAnimation(@NonNull ActiveTransition active, TransitionHandler handler) {
-        return handler.startAnimation(active.mToken, active.mInfo, active.mStartT,
+        return handler.startAnimation(active.mToken, active.mInfo, active.mStartT, active.mFinishT,
                 (wct, cb) -> onFinish(active.mToken, wct, cb));
     }
 
@@ -566,12 +566,19 @@
          * Starts a transition animation. This is always called if handleRequest returned non-null
          * for a particular transition. Otherwise, it is only called if no other handler before
          * it handled the transition.
-         *
+         * @param startTransaction the transaction given to the handler to be applied before the
+         *                         transition animation. Note the handler is expected to call on
+         *                         {@link SurfaceControl.Transaction#apply()} for startTransaction.
+         * @param finishTransaction the transaction given to the handler to be applied after the
+         *                       transition animation. Unlike startTransaction, the handler is NOT
+         *                       expected to apply this transaction. The Transition system will
+         *                       apply it when finishCallback is called.
          * @param finishCallback Call this when finished. This MUST be called on main thread.
          * @return true if transition was handled, false if not (falls-back to default).
          */
         boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction t,
+                @NonNull SurfaceControl.Transaction startTransaction,
+                @NonNull SurfaceControl.Transaction finishTransaction,
                 @NonNull TransitionFinishCallback finishCallback);
 
         /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/WindowThumbnail.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/WindowThumbnail.java
new file mode 100644
index 0000000..2c668ed
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/WindowThumbnail.java
@@ -0,0 +1,71 @@
+/*
+ * 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.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.hardware.HardwareBuffer;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+/**
+ * Represents a surface that is displayed over a transition surface.
+ */
+class WindowThumbnail {
+
+    private SurfaceControl mSurfaceControl;
+
+    private WindowThumbnail() {}
+
+    /** Create a thumbnail surface and attach it over a parent surface. */
+    static WindowThumbnail createAndAttach(SurfaceSession surfaceSession, SurfaceControl parent,
+            HardwareBuffer thumbnailHeader, SurfaceControl.Transaction t) {
+        WindowThumbnail windowThumbnail = new WindowThumbnail();
+        windowThumbnail.mSurfaceControl = new SurfaceControl.Builder(surfaceSession)
+                .setParent(parent)
+                .setName("WindowThumanil : " + parent.toString())
+                .setCallsite("WindowThumanil")
+                .setFormat(PixelFormat.TRANSLUCENT)
+                .build();
+
+        GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(thumbnailHeader);
+        t.setBuffer(windowThumbnail.mSurfaceControl, graphicBuffer);
+        t.setColorSpace(windowThumbnail.mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB));
+        t.setLayer(windowThumbnail.mSurfaceControl, Integer.MAX_VALUE);
+        t.show(windowThumbnail.mSurfaceControl);
+        t.apply();
+
+        return windowThumbnail;
+    }
+
+    SurfaceControl getSurface() {
+        return mSurfaceControl;
+    }
+
+    /** Remove the thumbnail surface and release the surface. */
+    void destroy(SurfaceControl.Transaction t) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+
+        t.remove(mSurfaceControl);
+        t.apply();
+        mSurfaceControl.release();
+        mSurfaceControl = null;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index aca80f3..4298dc8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -131,6 +131,7 @@
         mSideStage.onTaskAppeared(mSideChild, createMockSurface());
         boolean accepted = mStageCoordinator.startAnimation(transition, info,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         assertTrue(accepted);
 
@@ -168,6 +169,7 @@
         mSideStage.onTaskAppeared(newTask, createMockSurface());
         boolean accepted = mStageCoordinator.startAnimation(transition, info,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         assertFalse(accepted);
         assertTrue(mStageCoordinator.isSplitScreenVisible());
@@ -188,6 +190,7 @@
         mSideStage.onTaskVanished(newTask);
         accepted = mStageCoordinator.startAnimation(transition, info,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         assertFalse(accepted);
         assertTrue(mStageCoordinator.isSplitScreenVisible());
@@ -223,6 +226,7 @@
         mSideStage.onTaskVanished(mSideChild);
         mStageCoordinator.startAnimation(transition, info,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         assertFalse(mStageCoordinator.isSplitScreenVisible());
     }
@@ -244,6 +248,7 @@
         mSideStage.onTaskVanished(mSideChild);
         boolean accepted = mStageCoordinator.startAnimation(transition, info,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         assertTrue(accepted);
         assertFalse(mStageCoordinator.isSplitScreenVisible());
@@ -274,6 +279,7 @@
         mSideStage.onTaskVanished(mSideChild);
         boolean accepted = mStageCoordinator.startAnimation(transition, info,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         assertTrue(accepted);
         assertFalse(mStageCoordinator.isSplitScreenVisible());
@@ -298,6 +304,7 @@
         mSideStage.onTaskAppeared(mSideChild, createMockSurface());
         mStageCoordinator.startAnimation(enterTransit, enterInfo,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction());
     }
@@ -335,7 +342,8 @@
 
         @Override
         public void startAnimation(IBinder transition, TransitionInfo info,
-                SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback)
+                SurfaceControl.Transaction startTransaction,
+                IRemoteTransitionFinishedCallback finishCallback)
                 throws RemoteException {
             mCalled = true;
             finishCallback.onTransitionFinished(mRemoteFinishWCT);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 2d2ab2c..eb19a49 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -127,11 +127,13 @@
         TestTransitionHandler testHandler = new TestTransitionHandler() {
             @Override
             public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-                    @NonNull SurfaceControl.Transaction t,
+                    @NonNull SurfaceControl.Transaction startTransaction,
+                    @NonNull SurfaceControl.Transaction finishTransaction,
                     @NonNull Transitions.TransitionFinishCallback finishCallback) {
                 for (TransitionInfo.Change chg : info.getChanges()) {
                     if (chg.getMode() == TRANSIT_CHANGE) {
-                        return super.startAnimation(transition, info, t, finishCallback);
+                        return super.startAnimation(transition, info, startTransaction,
+                                finishTransaction, finishCallback);
                     }
                 }
                 return false;
@@ -358,9 +360,11 @@
         oneShot.setTransition(transitToken);
         IBinder anotherToken = new Binder();
         assertFalse(oneShot.startAnimation(anotherToken, new TransitionInfo(transitType, 0),
-                mock(SurfaceControl.Transaction.class), testFinish));
+                mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class),
+                testFinish));
         assertTrue(oneShot.startAnimation(transitToken, new TransitionInfo(transitType, 0),
-                mock(SurfaceControl.Transaction.class), testFinish));
+                mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class),
+                testFinish));
     }
 
     @Test
@@ -477,7 +481,8 @@
 
         @Override
         public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction t,
+                @NonNull SurfaceControl.Transaction startTransaction,
+                @NonNull SurfaceControl.Transaction finishTransaction,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
             mFinishes.add(finishCallback);
             return true;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 653d730..291a95a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -25,6 +25,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
+import android.app.ActivityTaskManager;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.Parcelable;
@@ -135,7 +136,7 @@
                 }
                 t.apply();
                 mRecentsSession.setup(controller, info, finishedCallback, pausingTask,
-                        leashMap);
+                        leashMap, mToken);
                 recents.onAnimationStart(mRecentsSession, apps, wallpapers, new Rect(0, 0, 0, 0),
                         new Rect());
             }
@@ -178,10 +179,11 @@
         private TransitionInfo mInfo = null;
         private SurfaceControl mOpeningLeash = null;
         private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
+        private IBinder mTransition = null;
 
         void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
                 IRemoteTransitionFinishedCallback finishCB, WindowContainerToken pausingTask,
-                ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
+                ArrayMap<SurfaceControl, SurfaceControl> leashMap, IBinder transition) {
             if (mInfo != null) {
                 throw new IllegalStateException("Trying to run a new recents animation while"
                         + " recents is already active.");
@@ -191,6 +193,7 @@
             mFinishCB = finishCB;
             mPausingTask = pausingTask;
             mLeashMap = leashMap;
+            mTransition = transition;
         }
 
         @SuppressLint("NewApi")
@@ -298,6 +301,7 @@
             mInfo = null;
             mOpeningLeash = null;
             mLeashMap = null;
+            mTransition = null;
         }
 
         @Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
@@ -318,6 +322,17 @@
         @Override public boolean removeTask(int taskId) {
             return mWrapped != null ? mWrapped.removeTask(taskId) : false;
         }
+
+        /**
+         * @see IRecentsAnimationController#detachNavigationBarFromApp
+         */
+        @Override public void detachNavigationBarFromApp(boolean moveHomeToTop) {
+            try {
+                ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to detach the navigation bar from app", e);
+            }
+        }
     }
 
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index ff79469..8820487 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -373,6 +373,20 @@
             }
         }
 
+        /**
+         * Called when the display is reparented and becomes an embedded
+         * display.
+         *
+         * @param embeddedDisplayId The embedded display Id.
+         */
+        @Override
+        public void onDisplayReparented(int embeddedDisplayId) {
+            // Removes the un-used window observer for the embedded display.
+            synchronized (mLock) {
+                mDisplayWindowsObservers.remove(embeddedDisplayId);
+            }
+        }
+
         private boolean shouldUpdateWindowsLocked(boolean forceSend,
                 @NonNull List<WindowInfo> windows) {
             if (forceSend) {
diff --git a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java b/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
new file mode 100644
index 0000000..715697d
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
@@ -0,0 +1,293 @@
+/*
+ * 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.autofill;
+
+import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
+
+import static com.android.server.autofill.Helper.sVerbose;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.ICancellationSignal;
+import android.os.RemoteException;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.service.autofill.IFillCallback;
+import android.service.autofill.SaveInfo;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.Slog;
+import android.view.autofill.AutofillId;
+import android.view.autofill.IAutoFillManagerClient;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Maintains a client suggestions session with the
+ * {@link android.view.autofill.AutofillRequestCallback} through the {@link IAutoFillManagerClient}.
+ *
+ */
+final class ClientSuggestionsSession {
+
+    private static final String TAG = "ClientSuggestionsSession";
+    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 15 * DateUtils.SECOND_IN_MILLIS;
+
+    private final int mSessionId;
+    private final IAutoFillManagerClient mClient;
+    private final Handler mHandler;
+    private final ComponentName mComponentName;
+
+    private final RemoteFillService.FillServiceCallbacks mCallbacks;
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private AndroidFuture<FillResponse> mPendingFillRequest;
+    @GuardedBy("mLock")
+    private int mPendingFillRequestId = INVALID_REQUEST_ID;
+
+    ClientSuggestionsSession(int sessionId, IAutoFillManagerClient client, Handler handler,
+            ComponentName componentName, RemoteFillService.FillServiceCallbacks callbacks) {
+        mSessionId = sessionId;
+        mClient = client;
+        mHandler = handler;
+        mComponentName = componentName;
+        mCallbacks = callbacks;
+    }
+
+    void onFillRequest(int requestId, InlineSuggestionsRequest inlineRequest, int flags) {
+        final AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+        final AtomicReference<AndroidFuture<FillResponse>> futureRef = new AtomicReference<>();
+        final AndroidFuture<FillResponse> fillRequest = new AndroidFuture<>();
+
+        mHandler.post(() -> {
+            if (sVerbose) {
+                Slog.v(TAG, "calling onFillRequest() for id=" + requestId);
+            }
+
+            try {
+                mClient.requestFillFromClient(requestId, inlineRequest,
+                        new FillCallbackImpl(fillRequest, futureRef, cancellationSink));
+            } catch (RemoteException e) {
+                fillRequest.completeExceptionally(e);
+            }
+        });
+
+        fillRequest.orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+        futureRef.set(fillRequest);
+
+        synchronized (mLock) {
+            mPendingFillRequest = fillRequest;
+            mPendingFillRequestId = requestId;
+        }
+
+        fillRequest.whenComplete((res, err) -> mHandler.post(() -> {
+            synchronized (mLock) {
+                mPendingFillRequest = null;
+                mPendingFillRequestId = INVALID_REQUEST_ID;
+            }
+            if (err == null) {
+                processAutofillId(res);
+                mCallbacks.onFillRequestSuccess(requestId, res,
+                        mComponentName.getPackageName(), flags);
+            } else {
+                Slog.e(TAG, "Error calling on  client fill request", err);
+                if (err instanceof TimeoutException) {
+                    dispatchCancellationSignal(cancellationSink.get());
+                    mCallbacks.onFillRequestTimeout(requestId);
+                } else if (err instanceof CancellationException) {
+                    dispatchCancellationSignal(cancellationSink.get());
+                } else {
+                    mCallbacks.onFillRequestFailure(requestId, err.getMessage());
+                }
+            }
+        }));
+    }
+
+    /**
+     * Gets the application info for the component.
+     */
+    @Nullable
+    static ApplicationInfo getAppInfo(ComponentName comp, @UserIdInt int userId) {
+        try {
+            ApplicationInfo si = AppGlobals.getPackageManager().getApplicationInfo(
+                    comp.getPackageName(),
+                    PackageManager.GET_META_DATA,
+                    userId);
+            if (si != null) {
+                return si;
+            }
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
+
+    /**
+     * Gets the user-visible name of the application.
+     */
+    @Nullable
+    @GuardedBy("mLock")
+    static CharSequence getAppLabelLocked(Context context, ApplicationInfo appInfo) {
+        return appInfo == null ? null : appInfo.loadSafeLabel(
+                context.getPackageManager(), 0 /* do not ellipsize */,
+                TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
+    }
+
+    /**
+     * Gets the user-visible icon of the application.
+     */
+    @Nullable
+    @GuardedBy("mLock")
+    static Drawable getAppIconLocked(Context context, ApplicationInfo appInfo) {
+        return appInfo == null ? null : appInfo.loadIcon(context.getPackageManager());
+    }
+
+    int cancelCurrentRequest() {
+        synchronized (mLock) {
+            return mPendingFillRequest != null && mPendingFillRequest.cancel(false)
+                    ? mPendingFillRequestId
+                    : INVALID_REQUEST_ID;
+        }
+    }
+
+    /**
+     * The {@link AutofillId} which the client gets from its view is not contain the session id,
+     * but Autofill framework is using the {@link AutofillId} with a session id. So before using
+     * those ids in the Autofill framework, applies the current session id.
+     *
+     * @param res which response need to apply for a session id
+     */
+    private void processAutofillId(FillResponse res) {
+        if (res == null) {
+            return;
+        }
+
+        final List<Dataset> datasets = res.getDatasets();
+        if (datasets != null && !datasets.isEmpty()) {
+            for (int i = 0; i < datasets.size(); i++) {
+                final Dataset dataset = datasets.get(i);
+                if (dataset != null) {
+                    applySessionId(dataset.getFieldIds());
+                }
+            }
+        }
+
+        final SaveInfo saveInfo = res.getSaveInfo();
+        if (saveInfo != null) {
+            applySessionId(saveInfo.getOptionalIds());
+            applySessionId(saveInfo.getRequiredIds());
+            applySessionId(saveInfo.getSanitizerValues());
+            applySessionId(saveInfo.getTriggerId());
+        }
+    }
+
+    private void applySessionId(List<AutofillId> ids) {
+        if (ids == null || ids.isEmpty()) {
+            return;
+        }
+
+        for (int i = 0; i < ids.size(); i++) {
+            applySessionId(ids.get(i));
+        }
+    }
+
+    private void applySessionId(AutofillId[][] ids) {
+        if (ids == null) {
+            return;
+        }
+        for (int i = 0; i < ids.length; i++) {
+            applySessionId(ids[i]);
+        }
+    }
+
+    private void applySessionId(AutofillId[] ids) {
+        if (ids == null) {
+            return;
+        }
+        for (int i = 0; i < ids.length; i++) {
+            applySessionId(ids[i]);
+        }
+    }
+
+    private void applySessionId(AutofillId id) {
+        if (id == null) {
+            return;
+        }
+        id.setSessionId(mSessionId);
+    }
+
+    private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
+        if (signal == null) {
+            return;
+        }
+        try {
+            signal.cancel();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error requesting a cancellation", e);
+        }
+    }
+
+    private class FillCallbackImpl extends IFillCallback.Stub {
+        final AndroidFuture<FillResponse> mFillRequest;
+        final AtomicReference<AndroidFuture<FillResponse>> mFutureRef;
+        final AtomicReference<ICancellationSignal> mCancellationSink;
+
+        FillCallbackImpl(AndroidFuture<FillResponse> fillRequest,
+                AtomicReference<AndroidFuture<FillResponse>> futureRef,
+                AtomicReference<ICancellationSignal> cancellationSink) {
+            mFillRequest = fillRequest;
+            mFutureRef = futureRef;
+            mCancellationSink = cancellationSink;
+        }
+
+        @Override
+        public void onCancellable(ICancellationSignal cancellation) {
+            AndroidFuture<FillResponse> future = mFutureRef.get();
+            if (future != null && future.isCancelled()) {
+                dispatchCancellationSignal(cancellation);
+            } else {
+                mCancellationSink.set(cancellation);
+            }
+        }
+
+        @Override
+        public void onSuccess(FillResponse response) {
+            mFillRequest.complete(response);
+        }
+
+        @Override
+        public void onFailure(int requestId, CharSequence message) {
+            String errorMessage = message == null ? "" : String.valueOf(message);
+            mFillRequest.completeExceptionally(
+                    new RuntimeException(errorMessage));
+        }
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 320047f..042631d 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -26,6 +26,7 @@
 import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
+import static android.view.autofill.AutofillManager.FLAG_ENABLED_CLIENT_SUGGESTIONS;
 import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
 import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
 
@@ -53,6 +54,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.pm.ApplicationInfo;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -346,6 +348,9 @@
      */
     private final AssistDataReceiverImpl mAssistReceiver = new AssistDataReceiverImpl();
 
+    @Nullable
+    private ClientSuggestionsSession mClientSuggestionsSession;
+
     void onSwitchInputMethodLocked() {
         // One caveat is that for the case where the focus is on a field for which regular autofill
         // returns null, and augmented autofill is triggered,  and then the user switches the input
@@ -416,6 +421,10 @@
         /** Whether the current {@link FillResponse} is expired. */
         @GuardedBy("mLock")
         private boolean mExpiredResponse;
+
+        /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */
+        @GuardedBy("mLock")
+        private boolean mClientSuggestionsEnabled;
     }
 
     /**
@@ -441,13 +450,19 @@
                         return;
                     }
                     mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
-                    maybeRequestFillLocked();
+                    maybeRequestFillFromServiceLocked();
                     viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
                 }
             } : null;
         }
 
-        void maybeRequestFillLocked() {
+        void newAutofillRequestLocked(@Nullable InlineSuggestionsRequest inlineRequest) {
+            mPendingFillRequest = null;
+            mWaitForInlineRequest = inlineRequest != null;
+            mPendingInlineSuggestionsRequest = inlineRequest;
+        }
+
+        void maybeRequestFillFromServiceLocked() {
             if (mPendingFillRequest == null) {
                 return;
             }
@@ -457,9 +472,12 @@
                     return;
                 }
 
-                mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
-                        mPendingFillRequest.getFillContexts(), mPendingFillRequest.getClientState(),
-                        mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest);
+                if (mPendingInlineSuggestionsRequest.isServiceSupported()) {
+                    mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
+                            mPendingFillRequest.getFillContexts(),
+                            mPendingFillRequest.getClientState(),
+                            mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest);
+                }
             }
 
             mRemoteFillService.onFillRequest(mPendingFillRequest);
@@ -566,7 +584,7 @@
                         /*inlineSuggestionsRequest=*/null);
 
                 mPendingFillRequest = request;
-                maybeRequestFillLocked();
+                maybeRequestFillFromServiceLocked();
             }
 
             if (mActivityToken != null) {
@@ -728,30 +746,39 @@
     }
 
     /**
-     * Cancels the last request sent to the {@link #mRemoteFillService}.
+     * Cancels the last request sent to the {@link #mRemoteFillService} or the
+     * {@link #mClientSuggestionsSession}.
      */
     @GuardedBy("mLock")
     private void cancelCurrentRequestLocked() {
-        if (mRemoteFillService == null) {
-            wtf(null, "cancelCurrentRequestLocked() called without a remote service. "
-                    + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
+        if (mRemoteFillService == null && mClientSuggestionsSession == null) {
+            wtf(null, "cancelCurrentRequestLocked() called without a remote service or a "
+                    + "client suggestions session.  mForAugmentedAutofillOnly: %s",
+                    mSessionFlags.mAugmentedAutofillOnly);
             return;
         }
-        final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
 
-        // Remove the FillContext as there will never be a response for the service
-        if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
-            final int numContexts = mContexts.size();
+        if (mRemoteFillService != null) {
+            final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
 
-            // It is most likely the last context, hence search backwards
-            for (int i = numContexts - 1; i >= 0; i--) {
-                if (mContexts.get(i).getRequestId() == canceledRequest) {
-                    if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
-                    mContexts.remove(i);
-                    break;
+            // Remove the FillContext as there will never be a response for the service
+            if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
+                final int numContexts = mContexts.size();
+
+                // It is most likely the last context, hence search backwards
+                for (int i = numContexts - 1; i >= 0; i--) {
+                    if (mContexts.get(i).getRequestId() == canceledRequest) {
+                        if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
+                        mContexts.remove(i);
+                        break;
+                    }
                 }
             }
         }
+
+        if (mClientSuggestionsSession != null) {
+            mClientSuggestionsSession.cancelCurrentRequest();
+        }
     }
 
     private boolean isViewFocusedLocked(int flags) {
@@ -816,17 +843,30 @@
         // structure is taken. This causes only one fill request per burst of focus changes.
         cancelCurrentRequestLocked();
 
-        // Only ask IME to create inline suggestions request if Autofill provider supports it and
-        // the render service is available except the autofill is triggered manually and the view
-        // is also not focused.
+        // Only ask IME to create inline suggestions request when
+        // 1. Autofill provider supports it or client enabled client suggestions.
+        // 2. The render service is available.
+        // 3. The view is focused. (The view may not be focused if the autofill is triggered
+        //    manually.)
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
-        if (mSessionFlags.mInlineSupportedByService
+        if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled)
                 && remoteRenderService != null
                 && isViewFocusedLocked(flags)) {
-            Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
-                    mAssistReceiver.newAutofillRequestLocked(viewState,
-                            /* isInlineRequest= */ true);
+            Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer;
+            if (mSessionFlags.mClientSuggestionsEnabled) {
+                final int finalRequestId = requestId;
+                inlineSuggestionsRequestConsumer = (inlineSuggestionsRequest) -> {
+                    // Using client suggestions
+                    synchronized (mLock) {
+                        onClientFillRequestLocked(finalRequestId, inlineSuggestionsRequest);
+                    }
+                    viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
+                };
+            } else {
+                inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked(
+                        viewState, /* isInlineRequest= */ true);
+            }
             if (inlineSuggestionsRequestConsumer != null) {
                 final AutofillId focusedId = mCurrentViewId;
                 final int requestIdCopy = requestId;
@@ -842,11 +882,24 @@
                 );
                 viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
             }
+        } else if (mSessionFlags.mClientSuggestionsEnabled) {
+            // Request client suggestions for the dropdown mode
+            onClientFillRequestLocked(requestId, null);
         } else {
             mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ false);
         }
 
+        if (mSessionFlags.mClientSuggestionsEnabled) {
+            // Using client suggestions, unnecessary request AssistStructure
+            return;
+        }
+
         // Now request the assist structure data.
+        requestAssistStructureLocked(requestId, flags);
+    }
+
+    @GuardedBy("mLock")
+    private void requestAssistStructureLocked(int requestId, int flags) {
         try {
             final Bundle receiverExtras = new Bundle();
             receiverExtras.putInt(EXTRA_REQUEST_ID, requestId);
@@ -895,10 +948,13 @@
         mComponentName = componentName;
         mCompatMode = compatMode;
         mSessionState = STATE_ACTIVE;
+
         synchronized (mLock) {
             mSessionFlags = new SessionFlags();
             mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly;
             mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked();
+            mSessionFlags.mClientSuggestionsEnabled =
+                    (mFlags & FLAG_ENABLED_CLIENT_SUGGESTIONS) != 0;
             setClientLocked(client);
         }
 
@@ -1010,12 +1066,13 @@
                 if (requestLog != null) {
                     requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1);
                 }
-                processNullResponseLocked(requestId, requestFlags);
+                processNullResponseOrFallbackLocked(requestId, requestFlags);
                 return;
             }
 
             fieldClassificationIds = response.getFieldClassificationIds();
-            if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
+            if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null
+                    && !mService.isFieldClassificationEnabledLocked()) {
                 Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
                 processNullResponseLocked(requestId, requestFlags);
                 return;
@@ -1094,6 +1151,26 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void processNullResponseOrFallbackLocked(int requestId, int flags) {
+        if (!mSessionFlags.mClientSuggestionsEnabled) {
+            processNullResponseLocked(requestId, flags);
+            return;
+        }
+
+        // fallback to the default platform password manager
+        mSessionFlags.mClientSuggestionsEnabled = false;
+
+        final InlineSuggestionsRequest inlineRequest =
+                (mLastInlineSuggestionsRequest != null
+                        && mLastInlineSuggestionsRequest.first == requestId)
+                        ? mLastInlineSuggestionsRequest.second : null;
+        mAssistReceiver.newAutofillRequestLocked(inlineRequest);
+        requestAssistStructureLocked(requestId,
+                flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
+        return;
+    }
+
     // FillServiceCallbacks
     @Override
     public void onFillRequestFailure(int requestId, @Nullable CharSequence message) {
@@ -3032,13 +3109,22 @@
             filterText = value.getTextValue().toString();
         }
 
-        final CharSequence serviceLabel;
-        final Drawable serviceIcon;
+        final CharSequence targetLabel;
+        final Drawable targetIcon;
         synchronized (mLock) {
-            serviceLabel = mService.getServiceLabelLocked();
-            serviceIcon = mService.getServiceIconLocked();
+            if (mSessionFlags.mClientSuggestionsEnabled) {
+                final ApplicationInfo appInfo = ClientSuggestionsSession.getAppInfo(mComponentName,
+                        mService.getUserId());
+                targetLabel = ClientSuggestionsSession.getAppLabelLocked(
+                        mService.getMaster().getContext(), appInfo);
+                targetIcon = ClientSuggestionsSession.getAppIconLocked(
+                        mService.getMaster().getContext(), appInfo);
+            } else {
+                targetLabel = mService.getServiceLabelLocked();
+                targetIcon = mService.getServiceIconLocked();
+            }
         }
-        if (serviceLabel == null || serviceIcon == null) {
+        if (targetLabel == null || targetIcon == null) {
             wtf(null, "onFillReady(): no service label or icon");
             return;
         }
@@ -3058,7 +3144,7 @@
 
         getUiForShowing().showFillUi(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName,
-                serviceLabel, serviceIcon, this, id, mCompatMode);
+                targetLabel, targetIcon, this, id, mCompatMode);
 
         mService.logDatasetShown(id, mClientState);
 
@@ -3105,6 +3191,17 @@
             return false;
         }
 
+        final InlineSuggestionsRequest request = inlineSuggestionsRequest.get();
+        if (mSessionFlags.mClientSuggestionsEnabled && !request.isClientSupported()
+                || !mSessionFlags.mClientSuggestionsEnabled && !request.isServiceSupported()) {
+            if (sDebug) {
+                Slog.d(TAG, "Inline suggestions not supported for "
+                        + (mSessionFlags.mClientSuggestionsEnabled ? "client" : "service")
+                        + ". Falling back to dropdown.");
+            }
+            return false;
+        }
+
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
         if (remoteRenderService == null) {
@@ -3113,7 +3210,7 @@
         }
 
         final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
-                new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId,
+                new InlineFillUi.InlineFillUiInfo(request, focusedId,
                         filterText, remoteRenderService, userId, id);
         InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response,
                 new InlineFillUi.InlineSuggestionUiCallback() {
@@ -3715,6 +3812,25 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void onClientFillRequestLocked(int requestId,
+            InlineSuggestionsRequest inlineSuggestionsRequest) {
+        if (mClientSuggestionsSession == null) {
+            mClientSuggestionsSession = new ClientSuggestionsSession(id, mClient, mHandler,
+                    mComponentName, this);
+        }
+
+        if (mContexts == null) {
+            mContexts = new ArrayList<>(1);
+        }
+
+        if (inlineSuggestionsRequest != null && !inlineSuggestionsRequest.isClientSupported()) {
+            inlineSuggestionsRequest = null;
+        }
+
+        mClientSuggestionsSession.onFillRequest(requestId, inlineSuggestionsRequest, mFlags);
+    }
+
     /**
      * The result of checking whether to show the save dialog, when session can be saved.
      *
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index a97c080..6b8ec4c 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -90,6 +90,7 @@
 import com.android.internal.R;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.TraceBuffer;
+import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal;
@@ -172,11 +173,15 @@
 
     /**
      * Sets a callback for observing which windows are touchable for the purposes
-     * of accessibility on specified display.
+     * of accessibility on specified display. When a display is reparented and becomes
+     * an embedded one, the {@link WindowsForAccessibilityCallback#onDisplayReparented(int)}
+     * will notify the accessibility framework to remove the un-used window observer of
+     * this embedded display.
      *
      * @param displayId The logical display id.
      * @param callback The callback.
-     * @return {@code false} if display id is not valid or an embedded display.
+     * @return {@code false} if display id is not valid or an embedded display when the callback
+     * isn't null.
      */
     boolean setWindowsForAccessibilityCallback(int displayId,
             WindowsForAccessibilityCallback callback) {
@@ -185,12 +190,13 @@
                     TAG + ".setWindowsForAccessibilityCallback",
                     "displayId=" + displayId + "; callback={" + callback + "}");
         }
-        final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
-        if (dc == null) {
-            return false;
-        }
 
         if (callback != null) {
+            final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+            if (dc == null) {
+                return false;
+            }
+
             WindowsForAccessibilityObserver observer =
                     mWindowsForAccessibilityObserver.get(displayId);
             if (isEmbeddedDisplay(dc)) {
@@ -209,21 +215,13 @@
                 if (Build.IS_DEBUGGABLE) {
                     throw new IllegalStateException(errorMessage);
                 }
-                removeObserverOfEmbeddedDisplay(observer);
+                removeObserversForEmbeddedChildDisplays(observer);
                 mWindowsForAccessibilityObserver.remove(displayId);
             }
             observer = new WindowsForAccessibilityObserver(mService, displayId, callback);
             mWindowsForAccessibilityObserver.put(displayId, observer);
             mAllObserversInitialized &= observer.mInitialized;
         } else {
-            if (isEmbeddedDisplay(dc)) {
-                // If this display is an embedded one, its window observer should be removed along
-                // with the window observer of its parent display removed because the window
-                // observer of the embedded display and its parent display is the same, and would
-                // be removed together when stopping the window tracking of its parent display. So
-                // here don't need to do removing window observer of the embedded display again.
-                return true;
-            }
             final WindowsForAccessibilityObserver windowsForA11yObserver =
                     mWindowsForAccessibilityObserver.get(displayId);
             if (windowsForA11yObserver == null) {
@@ -234,7 +232,7 @@
                     throw new IllegalStateException(errorMessage);
                 }
             }
-            removeObserverOfEmbeddedDisplay(windowsForA11yObserver);
+            removeObserversForEmbeddedChildDisplays(windowsForA11yObserver);
             mWindowsForAccessibilityObserver.remove(displayId);
         }
         return true;
@@ -507,22 +505,34 @@
         if (embeddedDisplayId == Display.DEFAULT_DISPLAY || parentWindow == null) {
             return;
         }
-        // Finds the parent display of this embedded display
-        final int parentDisplayId;
-        WindowState candidate = parentWindow;
-        while (candidate != null) {
-            parentWindow = candidate;
-            candidate = parentWindow.getDisplayContent().getParentWindow();
+        mService.mH.sendMessage(PooledLambda.obtainMessage(
+                AccessibilityController::updateWindowObserverOfEmbeddedDisplay,
+                this, embeddedDisplayId, parentWindow));
+    }
+
+    private void updateWindowObserverOfEmbeddedDisplay(int embeddedDisplayId,
+            WindowState parentWindow) {
+        final WindowsForAccessibilityObserver windowsForA11yObserver;
+
+        synchronized (mService.mGlobalLock) {
+            // Finds the parent display of this embedded display
+            WindowState candidate = parentWindow;
+            while (candidate != null) {
+                parentWindow = candidate;
+                candidate = parentWindow.getDisplayContent().getParentWindow();
+            }
+            final int parentDisplayId = parentWindow.getDisplayId();
+            // Uses the observer of parent display
+            windowsForA11yObserver = mWindowsForAccessibilityObserver.get(parentDisplayId);
         }
-        parentDisplayId = parentWindow.getDisplayId();
-        // Uses the observer of parent display
-        final WindowsForAccessibilityObserver windowsForA11yObserver =
-                mWindowsForAccessibilityObserver.get(parentDisplayId);
 
         if (windowsForA11yObserver != null) {
+            windowsForA11yObserver.notifyDisplayReparented(embeddedDisplayId);
             windowsForA11yObserver.addEmbeddedDisplay(embeddedDisplayId);
-            // Replaces the observer of embedded display to the one of parent display
-            mWindowsForAccessibilityObserver.put(embeddedDisplayId, windowsForA11yObserver);
+            synchronized (mService.mGlobalLock) {
+                // Replaces the observer of embedded display to the one of parent display
+                mWindowsForAccessibilityObserver.put(embeddedDisplayId, windowsForA11yObserver);
+            }
         }
     }
 
@@ -555,7 +565,7 @@
                 + "mWindowsForAccessibilityObserver=" + mWindowsForAccessibilityObserver);
     }
 
-    private void removeObserverOfEmbeddedDisplay(WindowsForAccessibilityObserver
+    private void removeObserversForEmbeddedChildDisplays(WindowsForAccessibilityObserver
             observerOfParentDisplay) {
         final IntArray embeddedDisplayIdList =
                 observerOfParentDisplay.getAndClearEmbeddedDisplayIdList();
@@ -1541,6 +1551,13 @@
             mEmbeddedDisplayIdList.add(displayId);
         }
 
+        void notifyDisplayReparented(int embeddedDisplayId) {
+            // Notifies the A11y framework the display is reparented and
+            // becomes an embedded display for removing the un-used
+            // displayWindowObserver of this embedded one.
+            mCallback.onDisplayReparented(embeddedDisplayId);
+        }
+
         /**
          * Check if windows have changed, and send them to the accessibility subsystem if they have.
          *
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 26f475e..579d620 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -68,6 +68,7 @@
 import android.util.Slog;
 import android.view.RemoteAnimationDefinition;
 import android.window.SizeConfigurationBuckets;
+import android.window.TransitionInfo;
 
 import com.android.internal.app.AssistUtils;
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -1014,6 +1015,9 @@
                 r.mDisplayContent.mAppTransition.overridePendingAppTransition(
                         packageName, enterAnim, exitAnim, null, null,
                         r.mOverrideTaskTransition);
+                mService.getTransitionController().setOverrideAnimation(
+                        TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
+                                enterAnim, exitAnim, r.mOverrideTaskTransition));
             }
         }
         Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 065dc6e..84782c4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -313,6 +313,7 @@
 import android.window.SizeConfigurationBuckets;
 import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.TaskSnapshot;
+import android.window.TransitionInfo.AnimationOptions;
 import android.window.WindowContainerToken;
 
 import com.android.internal.R;
@@ -4166,6 +4167,7 @@
     private void applyOptionsAnimation(ActivityOptions pendingOptions, Intent intent) {
         final int animationType = pendingOptions.getAnimationType();
         final DisplayContent displayContent = getDisplayContent();
+        AnimationOptions options = null;
         switch (animationType) {
             case ANIM_CUSTOM:
                 displayContent.mAppTransition.overridePendingAppTransition(
@@ -4175,11 +4177,17 @@
                         pendingOptions.getAnimationStartedListener(),
                         pendingOptions.getAnimationFinishedListener(),
                         pendingOptions.getOverrideTaskTransition());
+                options = AnimationOptions.makeCustomAnimOptions(pendingOptions.getPackageName(),
+                        pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(),
+                        pendingOptions.getOverrideTaskTransition());
                 break;
             case ANIM_CLIP_REVEAL:
                 displayContent.mAppTransition.overridePendingAppTransitionClipReveal(
                         pendingOptions.getStartX(), pendingOptions.getStartY(),
                         pendingOptions.getWidth(), pendingOptions.getHeight());
+                options = AnimationOptions.makeClipRevealAnimOptions(
+                        pendingOptions.getStartX(), pendingOptions.getStartY(),
+                        pendingOptions.getWidth(), pendingOptions.getHeight());
                 if (intent.getSourceBounds() == null) {
                     intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
                             pendingOptions.getStartY(),
@@ -4191,6 +4199,9 @@
                 displayContent.mAppTransition.overridePendingAppTransitionScaleUp(
                         pendingOptions.getStartX(), pendingOptions.getStartY(),
                         pendingOptions.getWidth(), pendingOptions.getHeight());
+                options = AnimationOptions.makeScaleUpAnimOptions(
+                        pendingOptions.getStartX(), pendingOptions.getStartY(),
+                        pendingOptions.getWidth(), pendingOptions.getHeight());
                 if (intent.getSourceBounds() == null) {
                     intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
                             pendingOptions.getStartY(),
@@ -4206,6 +4217,8 @@
                         pendingOptions.getStartX(), pendingOptions.getStartY(),
                         pendingOptions.getAnimationStartedListener(),
                         scaleUp);
+                options = AnimationOptions.makeThumnbnailAnimOptions(buffer,
+                        pendingOptions.getStartX(), pendingOptions.getStartY(), scaleUp);
                 if (intent.getSourceBounds() == null && buffer != null) {
                     intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
                             pendingOptions.getStartY(),
@@ -4245,6 +4258,7 @@
             case ANIM_OPEN_CROSS_PROFILE_APPS:
                 displayContent.mAppTransition
                         .overridePendingAppTransitionStartCrossProfileApps();
+                options = AnimationOptions.makeCrossProfileAnimOptions();
                 break;
             case ANIM_NONE:
             case ANIM_UNDEFINED:
@@ -4253,6 +4267,10 @@
                 Slog.e(TAG_WM, "applyOptionsLocked: Unknown animationType=" + animationType);
                 break;
         }
+
+        if (options != null) {
+            mAtmService.getTransitionController().setOverrideAnimation(options);
+        }
     }
 
     void clearAllDrawn() {
@@ -6597,8 +6615,7 @@
         }
         final Configuration displayConfig = mDisplayContent.getConfiguration();
         return getDisplayContent().mAppTransition.createThumbnailAspectScaleAnimationLocked(
-                appRect, insets, thumbnailHeader, task, displayConfig.uiMode,
-                displayConfig.orientation);
+                appRect, insets, thumbnailHeader, task, displayConfig.orientation);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 66e55e0..7424e78 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -28,6 +28,7 @@
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -1547,6 +1548,11 @@
             newTransition.setRemoteTransition(remoteTransition);
         }
         mService.getTransitionController().collect(r);
+        // TODO(b/188669821): Remove when navbar reparenting moves to shell
+        if (r.getActivityType() == ACTIVITY_TYPE_HOME && r.getOptions() != null
+                && r.getOptions().getTransientLaunch()) {
+            mService.getTransitionController().setIsLegacyRecents();
+        }
         try {
             mService.deferWindowLayout();
             Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index c7e4abb..a4baf4d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3726,6 +3726,20 @@
         }
     }
 
+    @Override
+    public void detachNavigationBarFromApp(@NonNull IBinder transition) {
+        mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
+                "detachNavigationBarFromApp");
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                getTransitionController().legacyDetachNavigationBarFromApp(transition);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     void dumpLastANRLocked(PrintWriter pw) {
         pw.println("ACTIVITY MANAGER LAST ANR (dumpsys activity lastanr)");
         if (mLastANRState == null) {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index c1b287f..ac687dc 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -78,10 +78,7 @@
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
-import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
-import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
-import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
-import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
+import static com.android.internal.policy.TransitionAnimation.prepareThumbnailAnimationWithDuration;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE;
@@ -102,12 +99,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Picture;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.hardware.HardwareBuffer;
 import android.os.Binder;
 import android.os.Debug;
@@ -132,7 +124,6 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationSet;
 import android.view.animation.AnimationUtils;
-import android.view.animation.ClipRectAnimation;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 import android.view.animation.ScaleAnimation;
@@ -641,24 +632,6 @@
     /**
      * Prepares the specified animation with a standard duration, interpolator, etc.
      */
-    Animation prepareThumbnailAnimationWithDuration(@Nullable Animation a, int appWidth,
-            int appHeight, long duration, Interpolator interpolator) {
-        if (a != null) {
-            if (duration > 0) {
-                a.setDuration(duration);
-            }
-            a.setFillAfter(true);
-            if (interpolator != null) {
-                a.setInterpolator(interpolator);
-            }
-            a.initialize(appWidth, appHeight, appWidth, appHeight);
-        }
-        return a;
-    }
-
-    /**
-     * Prepares the specified animation with a standard duration, interpolator, etc.
-     */
     Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight, int transit) {
         // Pick the desired duration.  If this is an inter-activity transition,
         // it  is the standard duration for that.  Otherwise we use the longer
@@ -678,56 +651,16 @@
     }
 
     /**
-     * Return the current thumbnail transition state.
-     */
-    int getThumbnailTransitionState(boolean enter) {
-        if (enter) {
-            if (mNextAppTransitionScaleUp) {
-                return THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
-            } else {
-                return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
-            }
-        } else {
-            if (mNextAppTransitionScaleUp) {
-                return THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
-            } else {
-                return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
-            }
-        }
-    }
-
-    /**
      * Creates an overlay with a background color and a thumbnail for the cross profile apps
      * animation.
      */
     HardwareBuffer createCrossProfileAppsThumbnail(
             @DrawableRes int thumbnailDrawableRes, Rect frame) {
-        final int width = frame.width();
-        final int height = frame.height();
-
-        final Picture picture = new Picture();
-        final Canvas canvas = picture.beginRecording(width, height);
-        canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
-        final int thumbnailSize = mService.mContext.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
-        final Drawable drawable = mService.mContext.getDrawable(thumbnailDrawableRes);
-        drawable.setBounds(
-                (width - thumbnailSize) / 2,
-                (height - thumbnailSize) / 2,
-                (width + thumbnailSize) / 2,
-                (height + thumbnailSize) / 2);
-        drawable.setTint(mContext.getColor(android.R.color.white));
-        drawable.draw(canvas);
-        picture.endRecording();
-
-        return Bitmap.createBitmap(picture).getHardwareBuffer();
+        return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
     }
 
     Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
-        final Animation animation =
-                mTransitionAnimation.loadCrossProfileAppThumbnailEnterAnimation();
-        return prepareThumbnailAnimationWithDuration(animation, appRect.width(),
-                appRect.height(), 0, null);
+        return mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(appRect);
     }
 
     /**
@@ -735,115 +668,14 @@
      * when a thumbnail is specified with the pending animation override.
      */
     Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, @Nullable Rect contentInsets,
-            HardwareBuffer thumbnailHeader, WindowContainer container, int uiMode,
-            int orientation) {
-        Animation a;
-        final int thumbWidthI = thumbnailHeader.getWidth();
-        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = thumbnailHeader.getHeight();
-        final int appWidth = appRect.width();
-
-        float scaleW = appWidth / thumbWidth;
-        getNextAppTransitionStartRect(container, mTmpRect);
-        final float fromX;
-        float fromY;
-        final float toX;
-        float toY;
-        final float pivotX;
-        final float pivotY;
-        if (shouldScaleDownThumbnailTransition(uiMode, orientation)) {
-            fromX = mTmpRect.left;
-            fromY = mTmpRect.top;
-
-            // For the curved translate animation to work, the pivot points needs to be at the
-            // same absolute position as the one from the real surface.
-            toX = mTmpRect.width() / 2 * (scaleW - 1f) + appRect.left;
-            toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top;
-            pivotX = mTmpRect.width() / 2;
-            pivotY = appRect.height() / 2 / scaleW;
-            if (mGridLayoutRecentsEnabled) {
-                // In the grid layout, the header is displayed above the thumbnail instead of
-                // overlapping it.
-                fromY -= thumbHeightI;
-                toY -= thumbHeightI * scaleW;
-            }
-        } else {
-            pivotX = 0;
-            pivotY = 0;
-            fromX = mTmpRect.left;
-            fromY = mTmpRect.top;
-            toX = appRect.left;
-            toY = appRect.top;
-        }
-        final long duration = getAspectScaleDuration();
-        final Interpolator interpolator = getAspectScaleInterpolator();
-        if (mNextAppTransitionScaleUp) {
-            // Animation up from the thumbnail to the full screen
-            Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY);
-            scale.setInterpolator(interpolator);
-            scale.setDuration(duration);
-            Animation alpha = new AlphaAnimation(1f, 0f);
-            alpha.setInterpolator(mThumbnailFadeOutInterpolator);
-            alpha.setDuration(duration);
-            Animation translate = createCurvedMotion(fromX, toX, fromY, toY);
-            translate.setInterpolator(interpolator);
-            translate.setDuration(duration);
-
-            mTmpFromClipRect.set(0, 0, thumbWidthI, thumbHeightI);
-            mTmpToClipRect.set(appRect);
-
-            // Containing frame is in screen space, but we need the clip rect in the
-            // app space.
-            mTmpToClipRect.offsetTo(0, 0);
-            mTmpToClipRect.right = (int) (mTmpToClipRect.right / scaleW);
-            mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom / scaleW);
-
-            if (contentInsets != null) {
-                mTmpToClipRect.inset((int) (-contentInsets.left * scaleW),
-                        (int) (-contentInsets.top * scaleW),
-                        (int) (-contentInsets.right * scaleW),
-                        (int) (-contentInsets.bottom * scaleW));
-            }
-
-            Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
-            clipAnim.setInterpolator(interpolator);
-            clipAnim.setDuration(duration);
-
-            // This AnimationSet uses the Interpolators assigned above.
-            AnimationSet set = new AnimationSet(false);
-            set.addAnimation(scale);
-            if (!mGridLayoutRecentsEnabled) {
-                // In the grid layout, the header should be shown for the whole animation.
-                set.addAnimation(alpha);
-            }
-            set.addAnimation(translate);
-            set.addAnimation(clipAnim);
-            a = set;
-        } else {
-            // Animation down from the full screen to the thumbnail
-            Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, pivotX, pivotY);
-            scale.setInterpolator(interpolator);
-            scale.setDuration(duration);
-            Animation alpha = new AlphaAnimation(0f, 1f);
-            alpha.setInterpolator(mThumbnailFadeInInterpolator);
-            alpha.setDuration(duration);
-            Animation translate = createCurvedMotion(toX, fromX, toY, fromY);
-            translate.setInterpolator(interpolator);
-            translate.setDuration(duration);
-
-            // This AnimationSet uses the Interpolators assigned above.
-            AnimationSet set = new AnimationSet(false);
-            set.addAnimation(scale);
-            if (!mGridLayoutRecentsEnabled) {
-                // In the grid layout, the header should be shown for the whole animation.
-                set.addAnimation(alpha);
-            }
-            set.addAnimation(translate);
-            a = set;
-
-        }
-        return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0,
-                null);
+            HardwareBuffer thumbnailHeader, WindowContainer container, int orientation) {
+        AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
+                container.hashCode());
+        return mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(appRect,
+                contentInsets, thumbnailHeader, orientation, spec != null ? spec.rect : null,
+                mDefaultNextAppTransitionAnimationSpec != null
+                        ? mDefaultNextAppTransitionAnimationSpec.rect : null,
+                mNextAppTransitionScaleUp);
     }
 
     private Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) {
@@ -1043,7 +875,7 @@
                             + "transit=%s Callers=%s",
                     a, appTransitionOldToString(transit), Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
-            a = mTransitionAnimation.createClipRevealAnimationLocked(
+            a = mTransitionAnimation.createClipRevealAnimationLockedCompat(
                     transit, enter, frame, displayFrame,
                     mDefaultNextAppTransitionAnimationSpec != null
                             ? mDefaultNextAppTransitionAnimationSpec.rect : null);
@@ -1052,7 +884,7 @@
                             + "transit=%s Callers=%s",
                     a, appTransitionOldToString(transit), Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
-            a = mTransitionAnimation.createScaleUpAnimationLocked(transit, enter, frame,
+            a = mTransitionAnimation.createScaleUpAnimationLockedCompat(transit, enter, frame,
                     mDefaultNextAppTransitionAnimationSpec != null
                             ? mDefaultNextAppTransitionAnimationSpec.rect : null);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
@@ -1064,8 +896,8 @@
             mNextAppTransitionScaleUp =
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
             final HardwareBuffer thumbnailHeader = getAppTransitionThumbnailHeader(container);
-            a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(
-                    getThumbnailTransitionState(enter), frame, transit, thumbnailHeader,
+            a = mTransitionAnimation.createThumbnailEnterExitAnimationLockedCompat(enter,
+                    mNextAppTransitionScaleUp, frame, transit, thumbnailHeader,
                     mDefaultNextAppTransitionAnimationSpec != null
                             ? mDefaultNextAppTransitionAnimationSpec.rect : null);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
@@ -1080,9 +912,9 @@
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
             AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
                     container.hashCode());
-            a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked(
-                    getThumbnailTransitionState(enter), orientation, transit, frame,
-                    insets, surfaceInsets, stableInsets, freeform, spec != null ? spec.rect : null,
+            a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked(enter,
+                    mNextAppTransitionScaleUp, orientation, transit, frame, insets, surfaceInsets,
+                    stableInsets, freeform, spec != null ? spec.rect : null,
                     mDefaultNextAppTransitionAnimationSpec != null
                             ? mDefaultNextAppTransitionAnimationSpec.rect : null);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b24ab93..86bbd1f 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -495,8 +495,10 @@
 
 
     DisplayAreaInfo getDisplayAreaInfo() {
-        DisplayAreaInfo info = new DisplayAreaInfo(mRemoteToken.toWindowContainerToken(),
+        final DisplayAreaInfo info = new DisplayAreaInfo(mRemoteToken.toWindowContainerToken(),
                 getDisplayContent().getDisplayId(), mFeatureId);
+        final RootDisplayArea root = getRootDisplayArea();
+        info.rootDisplayAreaId = root == null ? getDisplayContent().mFeatureId : root.mFeatureId;
         info.configuration.setTo(getConfiguration());
         return info;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index 47d7c9d..3d7ac6c 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST;
+import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
 
 import android.annotation.Nullable;
 import android.os.Bundle;
@@ -135,12 +136,6 @@
  */
 class DisplayAreaPolicyBuilder {
 
-    /**
-     * Key to specify the {@link RootDisplayArea} to attach the window to. Should be used by the
-     * function passed in from {@link #setSelectRootForWindowFunc(BiFunction)}
-     */
-    static final String KEY_ROOT_DISPLAY_AREA_ID = "root_display_area_id";
-
     @Nullable private HierarchyBuilder mRootHierarchyBuilder;
     private final ArrayList<HierarchyBuilder> mDisplayAreaGroupHierarchyBuilders =
             new ArrayList<>();
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index 747d365..f3b9cdf 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
+import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
 
 import android.animation.ArgbEvaluator;
 import android.animation.ValueAnimator;
@@ -420,7 +421,7 @@
         }
 
         final Bundle options = new Bundle();
-        options.putInt(DisplayAreaPolicyBuilder.KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId);
+        options.putInt(KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId);
         return options;
     }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 42e2d2f..e84ae65 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -16,11 +16,13 @@
 
 package com.android.server.wm;
 
-
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
@@ -124,9 +126,16 @@
     /** The final animation targets derived from participants after promotion. */
     private ArraySet<WindowContainer> mTargets = null;
 
+    private TransitionInfo.AnimationOptions mOverrideOptions;
+
     private @TransitionState int mState = STATE_COLLECTING;
     private boolean mReadyCalled = false;
 
+    // TODO(b/188595497): remove when not needed.
+    /** @see RecentsAnimationController#mNavigationBarAttachedToApp */
+    private boolean mNavBarAttachedToApp = false;
+    private int mNavBarDisplayId = INVALID_DISPLAY;
+
     Transition(@WindowManager.TransitionType int type, @WindowManager.TransitionFlags int flags,
             TransitionController controller, BLASTSyncEngine syncEngine) {
         mType = type;
@@ -136,6 +145,10 @@
         mSyncId = mSyncEngine.startSyncSet(this);
     }
 
+    void addFlag(int flag) {
+        mFlags |= flag;
+    }
+
     @VisibleForTesting
     int getSyncId() {
         return mSyncId;
@@ -207,6 +220,15 @@
     }
 
     /**
+     * Set animation options for collecting transition by ActivityRecord.
+     * @param options AnimationOptions captured from ActivityOptions
+     */
+    void setOverrideAnimation(TransitionInfo.AnimationOptions options) {
+        if (mSyncId < 0) return;
+        mOverrideOptions = options;
+    }
+
+    /**
      * Call this when all known changes related to this transition have been applied. Until
      * all participants have finished drawing, the transition can still collect participants.
      *
@@ -289,6 +311,8 @@
                 wt.commitVisibility(false /* visible */);
             }
         }
+
+        legacyRestoreNavigationBarFromApp();
     }
 
     void abort() {
@@ -327,6 +351,7 @@
             mController.mAtm.mRootWindowContainer.getDisplayContent(displayId)
                     .getPendingTransaction().merge(transaction);
             mSyncId = -1;
+            mOverrideOptions = null;
             return;
         }
 
@@ -340,6 +365,10 @@
         // Resolve the animating targets from the participants
         mTargets = calculateTargets(mParticipants, mChanges);
         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges);
+        info.setAnimationOptions(mOverrideOptions);
+
+        // TODO(b/188669821): Move to animation impl in shell.
+        handleLegacyRecentsStartBehavior(displayId, info);
 
         handleNonAppWindowsInTransition(displayId, mType, mFlags);
 
@@ -375,6 +404,7 @@
             cleanUpOnFailure();
         }
         mSyncId = -1;
+        mOverrideOptions = null;
     }
 
     /**
@@ -394,6 +424,100 @@
         finishTransition();
     }
 
+    /** @see RecentsAnimationController#attachNavigationBarToApp */
+    private void handleLegacyRecentsStartBehavior(int displayId, TransitionInfo info) {
+        if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
+            return;
+        }
+        final DisplayContent dc =
+                mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
+        if (dc == null || !dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
+                // Skip the case where the nav bar is controlled by fade rotation.
+                || dc.getFadeRotationAnimationController() != null) {
+            return;
+        }
+
+        WindowContainer topWC = null;
+        // Find the top-most non-home, closing app.
+        for (int i = 0; i < info.getChanges().size(); ++i) {
+            final TransitionInfo.Change c = info.getChanges().get(i);
+            if (c.getTaskInfo() == null || c.getTaskInfo().displayId != displayId
+                    || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD
+                    || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) {
+                continue;
+            }
+            topWC = WindowContainer.fromBinder(c.getContainer().asBinder());
+            break;
+        }
+        if (topWC == null || topWC.inMultiWindowMode()) {
+            return;
+        }
+
+        final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
+        if (navWindow == null || navWindow.mToken == null) {
+            return;
+        }
+        mNavBarAttachedToApp = true;
+        mNavBarDisplayId = displayId;
+        navWindow.mToken.cancelAnimation();
+        final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
+        final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
+        t.reparent(navSurfaceControl, topWC.getSurfaceControl());
+        t.show(navSurfaceControl);
+
+        final WindowContainer imeContainer = dc.getImeContainer();
+        if (imeContainer.isVisible()) {
+            t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1);
+        } else {
+            // Place the nav bar on top of anything else in the top activity.
+            t.setLayer(navSurfaceControl, Integer.MAX_VALUE);
+        }
+        if (mController.mStatusBar != null) {
+            mController.mStatusBar.setNavigationBarLumaSamplingEnabled(displayId, false);
+        }
+    }
+
+    /** @see RecentsAnimationController#restoreNavigationBarFromApp */
+    void legacyRestoreNavigationBarFromApp() {
+        if (!mNavBarAttachedToApp) return;
+        mNavBarAttachedToApp = false;
+
+        if (mController.mStatusBar != null) {
+            mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mNavBarDisplayId, true);
+        }
+
+        final DisplayContent dc =
+                mController.mAtm.mRootWindowContainer.getDisplayContent(mNavBarDisplayId);
+        final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
+        if (navWindow == null) return;
+        navWindow.setSurfaceTranslationY(0);
+
+        final WindowToken navToken = navWindow.mToken;
+        if (navToken == null) return;
+        final SurfaceControl.Transaction t = dc.getPendingTransaction();
+        final WindowContainer parent = navToken.getParent();
+        t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer());
+
+        boolean animate = false;
+        // Search for the home task. If it is supposed to be visible, then the navbar is not at
+        // the bottom of the screen, so we need to animate it.
+        for (int i = 0; i < mTargets.size(); ++i) {
+            final Task task = mTargets.valueAt(i).asTask();
+            if (task == null || !task.isHomeOrRecentsRootTask()) continue;
+            animate = task.isVisibleRequested();
+            break;
+        }
+
+        if (animate) {
+            final NavBarFadeAnimationController controller =
+                    new NavBarFadeAnimationController(dc);
+            controller.fadeWindowToken(true);
+        } else {
+            // Reparent the SurfaceControl of nav bar token back.
+            t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
+        }
+    }
+
     private void handleNonAppWindowsInTransition(int displayId,
             @WindowManager.TransitionType int transit, int flags) {
         final DisplayContent dc =
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index cc63c49..37de269 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
 import android.annotation.NonNull;
@@ -28,10 +29,13 @@
 import android.view.WindowManager;
 import android.window.IRemoteTransition;
 import android.window.ITransitionPlayer;
+import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
 
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.LocalServices;
+import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.util.ArrayList;
 
@@ -62,8 +66,12 @@
     /** The transition currently being constructed (collecting participants). */
     private Transition mCollectingTransition = null;
 
+    // TODO(b/188595497): remove when not needed.
+    final StatusBarManagerInternal mStatusBar;
+
     TransitionController(ActivityTaskManagerService atm) {
         mAtm = atm;
+        mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
     }
 
     /** @see #createTransition(int, int) */
@@ -240,6 +248,12 @@
         mCollectingTransition.collectExistenceChange(wc);
     }
 
+    /** @see Transition#setOverrideAnimation */
+    void setOverrideAnimation(TransitionInfo.AnimationOptions options) {
+        if (mCollectingTransition == null) return;
+        mCollectingTransition.setOverrideAnimation(options);
+    }
+
     /** @see Transition#setReady */
     void setReady(boolean ready) {
         if (mCollectingTransition == null) return;
@@ -279,4 +293,22 @@
         mCollectingTransition = null;
     }
 
+    /**
+     * Explicitly mark the collectingTransition as being part of recents gesture. Used for legacy
+     * behaviors.
+     * TODO(b/188669821): Remove once legacy recents behavior is moved to shell.
+     */
+    void setIsLegacyRecents() {
+        if (mCollectingTransition == null) return;
+        mCollectingTransition.addFlag(TRANSIT_FLAG_IS_RECENTS);
+    }
+
+    void legacyDetachNavigationBarFromApp(@NonNull IBinder token) {
+        final Transition transition = Transition.fromBinder(token);
+        if (transition == null || !mPlayingTransitions.contains(transition)) {
+            Slog.e(TAG, "Transition isn't playing: " + token);
+            return;
+        }
+        transition.legacyRestoreNavigationBarFromApp();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 47087cf..1d48827 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -115,6 +115,16 @@
          */
         void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId,
                 IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows);
+
+        /**
+         * Called when the display is reparented and becomes an embedded
+         * display. The {@link WindowsForAccessibilityCallback} with the given embedded
+         * display will be replaced by the {@link WindowsForAccessibilityCallback}
+         * associated with its parent display at the same time.
+         *
+         * @param embeddedDisplayId The embedded display Id.
+         */
+        void onDisplayReparented(int embeddedDisplayId);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index e4d51e4..5eaa964 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -834,6 +834,19 @@
         assertNull(token);
     }
 
+    @Test
+    public void onDisplayReparented_shouldRemoveObserver() throws RemoteException {
+        // Starts tracking window of second display.
+        startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
+        assertTrue(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID));
+        // Notifies the second display is an embedded one of the default display.
+        final WindowsForAccessibilityCallback callbacks =
+                mCallbackOfWindows.get(Display.DEFAULT_DISPLAY);
+        callbacks.onDisplayReparented(SECONDARY_DISPLAY_ID);
+        // Makes sure the observer of the second display is removed.
+        assertFalse(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID));
+    }
+
     private void registerLeashedTokenAndWindowId() {
         mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID);
         mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
index 31d4612..af21e02 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -35,10 +35,10 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST;
 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
+import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
 
 import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
 import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature;
-import static com.android.server.wm.DisplayAreaPolicyBuilder.KEY_ROOT_DISPLAY_AREA_ID;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index d5628fc..c4faaa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -61,6 +61,7 @@
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
+import android.window.DisplayAreaInfo;
 import android.window.IDisplayAreaOrganizer;
 
 import com.google.android.collect.Lists;
@@ -566,6 +567,31 @@
                 .onDisplayAreaVanished(mockDisplayAreaOrganizer, displayArea);
     }
 
+    @Test
+    public void testGetDisplayAreaInfo() {
+        final DisplayArea<WindowContainer> displayArea = new DisplayArea<>(
+                mWm, BELOW_TASKS, "NewArea", FEATURE_VENDOR_FIRST);
+        mDisplayContent.addChild(displayArea, 0);
+        final DisplayAreaInfo info = displayArea.getDisplayAreaInfo();
+
+        assertThat(info.token).isEqualTo(displayArea.mRemoteToken.toWindowContainerToken());
+        assertThat(info.configuration).isEqualTo(displayArea.getConfiguration());
+        assertThat(info.displayId).isEqualTo(mDisplayContent.getDisplayId());
+        assertThat(info.featureId).isEqualTo(displayArea.mFeatureId);
+        assertThat(info.rootDisplayAreaId).isEqualTo(mDisplayContent.mFeatureId);
+
+        final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea();
+        final int tdaIndex = tda.getParent().mChildren.indexOf(tda);
+        final RootDisplayArea root =
+                new DisplayAreaGroup(mWm, "TestRoot", FEATURE_VENDOR_FIRST + 1);
+        mDisplayContent.addChild(root, tdaIndex + 1);
+        displayArea.reparent(root, 0);
+
+        final DisplayAreaInfo info2 = displayArea.getDisplayAreaInfo();
+
+        assertThat(info2.rootDisplayAreaId).isEqualTo(root.mFeatureId);
+    }
+
     private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
         private TestDisplayArea(WindowManagerService wms, Rect bounds) {
             super(wms, ANY, "half display area");