Merge "Import translations. DO NOT MERGE ANYWHERE" into rvc-qpr-dev
diff --git a/core/java/com/android/internal/app/NetInitiatedActivity.java b/core/java/com/android/internal/app/NetInitiatedActivity.java
index 92e9fe4..5efeb0f 100644
--- a/core/java/com/android/internal/app/NetInitiatedActivity.java
+++ b/core/java/com/android/internal/app/NetInitiatedActivity.java
@@ -17,18 +17,14 @@
 package com.android.internal.app;
 
 import android.app.AlertDialog;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.location.LocationManagerInternal;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.util.Log;
-import android.widget.Toast;
 
 import com.android.internal.R;
 import com.android.internal.location.GpsNetInitiatedHandler;
@@ -43,7 +39,6 @@
     private static final String TAG = "NetInitiatedActivity";
 
     private static final boolean DEBUG = true;
-    private static final boolean VERBOSE = false;
 
     private static final int POSITIVE_BUTTON = AlertDialog.BUTTON_POSITIVE;
     private static final int NEGATIVE_BUTTON = AlertDialog.BUTTON_NEGATIVE;
@@ -55,17 +50,6 @@
     private int default_response = -1;
     private int default_response_timeout = 6;
 
-    /** Used to detect when NI request is received */
-    private BroadcastReceiver mNetInitiatedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DEBUG) Log.d(TAG, "NetInitiatedReceiver onReceive: " + intent.getAction());
-            if (intent.getAction() == GpsNetInitiatedHandler.ACTION_NI_VERIFY) {
-                handleNIVerify(intent);
-            }
-        }
-    };
-
     private final Handler mHandler = new Handler() {
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -109,14 +93,12 @@
     protected void onResume() {
         super.onResume();
         if (DEBUG) Log.d(TAG, "onResume");
-        registerReceiver(mNetInitiatedReceiver, new IntentFilter(GpsNetInitiatedHandler.ACTION_NI_VERIFY));
     }
 
     @Override
     protected void onPause() {
         super.onPause();
         if (DEBUG) Log.d(TAG, "onPause");
-        unregisterReceiver(mNetInitiatedReceiver);
     }
 
     /**
@@ -141,17 +123,4 @@
         LocationManagerInternal lm = LocalServices.getService(LocationManagerInternal.class);
         lm.sendNiResponse(notificationId, response);
     }
-
-    @UnsupportedAppUsage
-    private void handleNIVerify(Intent intent) {
-        int notifId = intent.getIntExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_NOTIF_ID, -1);
-        notificationId = notifId;
-
-        if (DEBUG) Log.d(TAG, "handleNIVerify action: " + intent.getAction());
-    }
-
-    private void showNIError() {
-        Toast.makeText(this, "NI error" /* com.android.internal.R.string.usb_storage_error_message */,
-                Toast.LENGTH_LONG).show();
-    }
 }
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index c5a460b..dc89208 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -163,3 +163,10 @@
     src: "com.android.car.activityresolver.xml",
     filename_from_src: true,
 }
+
+prebuilt_etc {
+    name: "allowed_privapp_com.android.car.rotary",
+    sub_dir: "permissions",
+    src: "com.android.car.rotary.xml",
+    filename_from_src: true,
+}
diff --git a/data/etc/car/com.android.car.rotary.xml b/data/etc/car/com.android.car.rotary.xml
new file mode 100644
index 0000000..5752755
--- /dev/null
+++ b/data/etc/car/com.android.car.rotary.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<permissions>
+    <privapp-permissions package="com.android.car.rotary">
+        <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
+        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+    </privapp-permissions>
+</permissions>
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index 67a040d..a3765151 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -51,9 +51,6 @@
 
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    // NI verify activity for bringing up UI (not used yet)
-    public static final String ACTION_NI_VERIFY = "android.intent.action.NETWORK_INITIATED_VERIFY";
-
     // string constants for defining data fields in NI Intent
     public static final String NI_INTENT_KEY_NOTIF_ID = "notif_id";
     public static final String NI_INTENT_KEY_TITLE = "title";
diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_container.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_container.xml
index 3e35df9..f617ec0 100644
--- a/packages/CarSystemUI/res-keyguard/layout/keyguard_container.xml
+++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_container.xml
@@ -14,10 +14,7 @@
   ~ limitations under the License.
   -->
 
-<!-- Car customizations
-     Car has solid black background instead of a transparent one
--->
-<LinearLayout
+<com.android.car.ui.FocusArea
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/keyguard_container"
     android:layout_width="match_parent"
diff --git a/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml b/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml
index 8306cb4..c5974e3 100644
--- a/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml
+++ b/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml
@@ -66,7 +66,6 @@
         android:src="@drawable/ic_backspace"
         android:clickable="true"
         android:tint="@android:color/white"
-        android:background="@drawable/ripple_drawable"
         android:contentDescription="@string/keyboardview_keycode_delete" />
     <com.android.keyguard.NumPadKey
         android:id="@+id/key0"
@@ -77,7 +76,6 @@
         style="@style/NumPadKeyButton.LastRow"
         android:src="@drawable/ic_done"
         android:tint="@android:color/white"
-        android:background="@drawable/ripple_drawable"
         android:contentDescription="@string/keyboardview_keycode_enter" />
 </merge>
 
diff --git a/packages/CarSystemUI/res-keyguard/values/dimens.xml b/packages/CarSystemUI/res-keyguard/values/dimens.xml
index 8dfe171..3c13958 100644
--- a/packages/CarSystemUI/res-keyguard/values/dimens.xml
+++ b/packages/CarSystemUI/res-keyguard/values/dimens.xml
@@ -17,10 +17,8 @@
 <resources>
     <dimen name="num_pad_margin_left">112dp</dimen>
     <dimen name="num_pad_margin_right">144dp</dimen>
-    <dimen name="num_pad_key_width">80dp</dimen>
+    <dimen name="num_pad_key_width">120dp</dimen>
     <dimen name="num_pad_key_height">80dp</dimen>
-    <dimen name="num_pad_key_margin_horizontal">@*android:dimen/car_padding_5</dimen>
-    <dimen name="num_pad_key_margin_bottom">@*android:dimen/car_padding_5</dimen>
     <dimen name="pin_entry_height">@dimen/num_pad_key_height</dimen>
     <dimen name="divider_height">1dp</dimen>
     <dimen name="key_enter_margin_top">128dp</dimen>
diff --git a/packages/CarSystemUI/res-keyguard/values/styles.xml b/packages/CarSystemUI/res-keyguard/values/styles.xml
index ecea30a..ca37428 100644
--- a/packages/CarSystemUI/res-keyguard/values/styles.xml
+++ b/packages/CarSystemUI/res-keyguard/values/styles.xml
@@ -23,12 +23,11 @@
         <item name="android:layout_width">@dimen/num_pad_key_width</item>
         <item name="android:layout_height">@dimen/num_pad_key_height</item>
         <item name="android:layout_marginBottom">@dimen/num_pad_key_margin_bottom</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
         <item name="textView">@id/pinEntry</item>
     </style>
 
     <style name="NumPadKeyButton.MiddleColumn">
-        <item name="android:layout_marginStart">@dimen/num_pad_key_margin_horizontal</item>
-        <item name="android:layout_marginEnd">@dimen/num_pad_key_margin_horizontal</item>
     </style>
 
     <style name="NumPadKeyButton.LastRow">
@@ -36,12 +35,10 @@
     </style>
 
     <style name="NumPadKeyButton.LastRow.MiddleColumn">
-        <item name="android:layout_marginStart">@dimen/num_pad_key_margin_horizontal</item>
-        <item name="android:layout_marginEnd">@dimen/num_pad_key_margin_horizontal</item>
     </style>
 
     <style name="KeyguardButton" parent="@android:style/Widget.DeviceDefault.Button">
-        <item name="android:background">@drawable/keyguard_button_background</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
         <item name="android:textColor">@color/button_text</item>
         <item name="android:textAllCaps">false</item>
     </style>
diff --git a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
index 99df6d5..f987b5a 100644
--- a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
+++ b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
@@ -14,29 +14,36 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.systemui.car.userswitcher.UserSwitcherContainer
+
+<com.android.car.ui.FocusArea
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/container"
+    android:id="@+id/user_switcher_container"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@color/car_user_switcher_background_color"
-    android:orientation="vertical">
-
-    <include
-        layout="@layout/car_status_bar_header"
-        android:layout_alignParentTop="true"
-        android:theme="@android:style/Theme"/>
-
-
-    <FrameLayout
+    android:gravity="center">
+    <com.android.systemui.car.userswitcher.UserSwitcherContainer
+        android:id="@+id/container"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
-        <com.android.systemui.car.userswitcher.UserGridRecyclerView
-            android:id="@+id/user_grid"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:layout_marginTop="@dimen/car_user_switcher_margin_top"/>
-    </FrameLayout>
+        android:layout_height="match_parent"
+        android:background="@color/car_user_switcher_background_color"
+        android:orientation="vertical">
 
-</com.android.systemui.car.userswitcher.UserSwitcherContainer>
+        <include
+            layout="@layout/car_status_bar_header"
+            android:layout_alignParentTop="true"
+            android:theme="@android:style/Theme"/>
+
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <com.android.systemui.car.userswitcher.UserGridRecyclerView
+                android:id="@+id/user_grid"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginTop="@dimen/car_user_switcher_margin_top"/>
+        </FrameLayout>
+
+    </com.android.systemui.car.userswitcher.UserSwitcherContainer>
+</com.android.car.ui.FocusArea>
diff --git a/packages/CarSystemUI/res/layout/notification_center_activity.xml b/packages/CarSystemUI/res/layout/notification_center_activity.xml
index 0e45e43..51d23db 100644
--- a/packages/CarSystemUI/res/layout/notification_center_activity.xml
+++ b/packages/CarSystemUI/res/layout/notification_center_activity.xml
@@ -22,10 +22,6 @@
     android:layout_height="match_parent"
     android:background="@color/notification_shade_background_color">
 
-    <com.android.car.ui.FocusParkingView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
-
     <View
         android:id="@+id/glass_pane"
         android:layout_width="match_parent"
@@ -37,20 +33,15 @@
         app:layout_constraintTop_toTopOf="parent"
     />
 
-    <com.android.car.ui.FocusArea
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:orientation="vertical"
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/notifications"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingBottom="@dimen/notification_shade_list_padding_bottom"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent">
-        <androidx.recyclerview.widget.RecyclerView
-            android:id="@+id/notifications"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:paddingBottom="@dimen/notification_shade_list_padding_bottom"/>
-    </com.android.car.ui.FocusArea>
+        app:layout_constraintTop_toTopOf="parent"/>
 
     <include layout="@layout/notification_handle_bar"/>
 
diff --git a/packages/CarSystemUI/res/layout/notification_panel_container.xml b/packages/CarSystemUI/res/layout/notification_panel_container.xml
index 3b53c6a..de69769 100644
--- a/packages/CarSystemUI/res/layout/notification_panel_container.xml
+++ b/packages/CarSystemUI/res/layout/notification_panel_container.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<FrameLayout
+<com.android.car.ui.FocusArea
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/notification_container"
     android:layout_width="match_parent"
diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
index e7295aa..3d6085c 100644
--- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
+++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
@@ -22,25 +22,29 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
+    <com.android.car.ui.FocusParkingView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
     <ViewStub android:id="@+id/notification_panel_stub"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:layout="@layout/notification_panel_container"
-              android:layout_marginBottom="@dimen/car_bottom_navigation_bar_height"/>
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/notification_panel_container"
+        android:layout_marginBottom="@dimen/car_bottom_navigation_bar_height"/>
 
     <ViewStub android:id="@+id/keyguard_stub"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:layout="@layout/keyguard_container" />
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/keyguard_container" />
 
     <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:layout="@layout/car_fullscreen_user_switcher"/>
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/car_fullscreen_user_switcher"/>
 
     <ViewStub android:id="@+id/user_switching_dialog_stub"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:layout="@layout/car_user_switching_dialog"/>
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/car_user_switching_dialog"/>
 
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
index ec018f9..53d2320 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -148,6 +148,11 @@
     }
 
     @Override
+    protected int getFocusAreaViewId() {
+        return R.id.keyguard_container;
+    }
+
+    @Override
     protected boolean shouldShowNavigationBarInsets() {
         return true;
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index b83fcf4..6597144 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -219,6 +219,11 @@
     }
 
     @Override
+    protected int getFocusAreaViewId() {
+        return R.id.notification_container;
+    }
+
+    @Override
     protected boolean shouldShowNavigationBarInsets() {
         return true;
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
index 5fc7299..dd59efa 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
@@ -76,7 +76,7 @@
             }
 
             if (event.getAction() == KeyEvent.ACTION_UP && getLayout().isVisibleToUser()) {
-                getLayout().setVisibility(View.GONE);
+                stop();
             }
             return true;
         });
@@ -92,6 +92,11 @@
     }
 
     @Override
+    protected int getFocusAreaViewId() {
+        return R.id.user_switcher_container;
+    }
+
+    @Override
     protected boolean shouldFocusWindow() {
         return true;
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
index 8adc1ad..7bc1776 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
@@ -17,12 +17,17 @@
 package com.android.systemui.car.window;
 
 import static android.view.WindowInsets.Type.statusBars;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
 
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.view.WindowInsets;
 
+import androidx.annotation.IdRes;
+
+import com.android.car.ui.FocusArea;
+
 /**
  * Owns a {@link View} that is present in SystemUIOverlayWindow.
  */
@@ -128,6 +133,66 @@
         return mOverlayViewGlobalStateController;
     }
 
+    /** Returns whether the view controlled by this controller is visible. */
+    public final boolean isVisible() {
+        return mLayout.getVisibility() == View.VISIBLE;
+    }
+
+    /**
+     * Returns the ID of the focus area that should receive focus when this view is the
+     * topmost view or {@link View#NO_ID} if there is no focus area.
+     */
+    @IdRes
+    protected int getFocusAreaViewId() {
+        return View.NO_ID;
+    }
+
+    /** Returns whether the view controlled by this controller has rotary focus. */
+    protected final boolean hasRotaryFocus() {
+        return !mLayout.isInTouchMode() && mLayout.hasFocus();
+    }
+
+    /**
+     * Sets whether this view allows rotary focus. This should be set to {@code true} for the
+     * topmost layer in the overlay window and {@code false} for the others.
+     */
+    public void setAllowRotaryFocus(boolean allowRotaryFocus) {
+        if (!isInflated()) {
+            return;
+        }
+
+        if (!(mLayout instanceof ViewGroup)) {
+            return;
+        }
+
+        ViewGroup viewGroup = (ViewGroup) mLayout;
+        viewGroup.setDescendantFocusability(allowRotaryFocus
+                ? ViewGroup.FOCUS_BEFORE_DESCENDANTS
+                : ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+    }
+
+    /**
+     * Refreshes the rotary focus in this view if we are in rotary mode. If the view already has
+     * rotary focus, it leaves the focus alone. Returns {@code true} if a new view was focused.
+     */
+    public boolean refreshRotaryFocusIfNeeded() {
+        if (mLayout.isInTouchMode()) {
+            return false;
+        }
+
+        if (hasRotaryFocus()) {
+            return false;
+        }
+
+        View view = mLayout.findViewById(getFocusAreaViewId());
+        if (view == null || !(view instanceof FocusArea)) {
+            return mLayout.requestFocus();
+        }
+
+        FocusArea focusArea = (FocusArea) view;
+        return focusArea.performAccessibilityAction(ACTION_FOCUS, /* arguments= */ null);
+    }
+
     /**
      * Returns {@code true} if heads up notifications should be displayed over this view.
      */
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
index 55f0975..204dde7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
@@ -29,6 +29,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
@@ -120,6 +121,7 @@
         refreshWindowFocus();
         refreshNavigationBarVisibility();
         refreshStatusBarVisibility();
+        refreshRotaryFocusIfNeeded();
 
         Log.d(TAG, "Content shown: " + viewController.getClass().getName());
         debugLog();
@@ -193,6 +195,7 @@
         refreshWindowFocus();
         refreshNavigationBarVisibility();
         refreshStatusBarVisibility();
+        refreshRotaryFocusIfNeeded();
 
         if (mZOrderVisibleSortedMap.isEmpty()) {
             setWindowVisible(false);
@@ -254,6 +257,17 @@
         }
     }
 
+    private void refreshRotaryFocusIfNeeded() {
+        for (OverlayViewController controller : mZOrderVisibleSortedMap.values()) {
+            boolean isTop = Objects.equals(controller, mHighestZOrder);
+            controller.setAllowRotaryFocus(isTop);
+        }
+
+        if (!mZOrderVisibleSortedMap.isEmpty()) {
+            mHighestZOrder.refreshRotaryFocusIfNeeded();
+        }
+    }
+
     /** Returns {@code true} is the window is visible. */
     public boolean isWindowVisible() {
         return mSystemUIOverlayWindowController.isWindowVisible();
diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
index c9ec34f..bbab438 100644
--- a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
@@ -16,12 +16,14 @@
 
 package com.android.systemui.wm;
 
+import android.content.Context;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.IDisplayWindowInsetsController;
+import android.view.IWindowManager;
 import android.view.InsetsController;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
@@ -48,30 +50,32 @@
 
     private static final String TAG = "DisplaySystemBarsController";
 
+    private final Context mContext;
+    private final Handler mHandler;
+
     private SparseArray<PerDisplay> mPerDisplaySparseArray;
 
     @Inject
     public DisplaySystemBarsController(
-            SystemWindows syswin,
+            Context context,
+            IWindowManager wmService,
             DisplayController displayController,
             @Main Handler mainHandler,
             TransactionPool transactionPool) {
-        super(syswin, displayController, mainHandler, transactionPool);
+        super(wmService, displayController, mainHandler::post, transactionPool);
+        mContext = context;
+        mHandler = mainHandler;
     }
 
     @Override
     public void onDisplayAdded(int displayId) {
         PerDisplay pd = new PerDisplay(displayId);
-        try {
-            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Unable to set insets controller on display " + displayId);
-        }
+        pd.register();
         // Lazy loading policy control filters instead of during boot.
         if (mPerDisplaySparseArray == null) {
             mPerDisplaySparseArray = new SparseArray<>();
-            BarControlPolicy.reloadFromSetting(mSystemWindows.mContext);
-            BarControlPolicy.registerContentObserver(mSystemWindows.mContext, mHandler, () -> {
+            BarControlPolicy.reloadFromSetting(mContext);
+            BarControlPolicy.registerContentObserver(mContext, mHandler, () -> {
                 int size = mPerDisplaySparseArray.size();
                 for (int i = 0; i < size; i++) {
                     mPerDisplaySparseArray.valueAt(i).modifyDisplayWindowInsets();
@@ -84,7 +88,7 @@
     @Override
     public void onDisplayRemoved(int displayId) {
         try {
-            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null);
+            mWmService.setDisplayWindowInsetsController(displayId, null);
         } catch (RemoteException e) {
             Slog.w(TAG, "Unable to remove insets controller on display " + displayId);
         }
@@ -100,11 +104,10 @@
         String mPackageName;
 
         PerDisplay(int displayId) {
-            super(displayId,
-                    mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation());
+            super(displayId, mDisplayController.getDisplayLayout(displayId).rotation());
             mDisplayId = displayId;
             mInsetsController = new InsetsController(
-                    new DisplaySystemBarsInsetsControllerHost(mHandler, this));
+                    new DisplaySystemBarsInsetsControllerHost(mHandler, mInsetsControllerImpl));
         }
 
         @Override
@@ -166,7 +169,7 @@
             showInsets(barVisibilities[0], /* fromIme= */ false);
             hideInsets(barVisibilities[1], /* fromIme= */ false);
             try {
-                mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
+                mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Unable to update window manager service.");
             }
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
index 294aa0d..d97b232 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
@@ -215,6 +215,16 @@
     }
 
     @Test
+    public void showView_nothingAlreadyShown_newHighestZOrder_isVisible() {
+        setupOverlayViewController1();
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsKey(
+                OVERLAY_VIEW_CONTROLLER_1_Z_ORDER)).isTrue();
+    }
+
+    @Test
     public void showView_nothingAlreadyShown_newHighestZOrder() {
         setupOverlayViewController1();
 
@@ -225,13 +235,12 @@
     }
 
     @Test
-    public void showView_nothingAlreadyShown_newHighestZOrder_isVisible() {
+    public void showView_nothingAlreadyShown_descendantsFocusable() {
         setupOverlayViewController1();
 
         mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
 
-        assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsKey(
-                OVERLAY_VIEW_CONTROLLER_1_Z_ORDER)).isTrue();
+        verify(mOverlayViewController1).setAllowRotaryFocus(true);
     }
 
     @Test
@@ -332,6 +341,30 @@
     }
 
     @Test
+    public void showView_newHighestZOrder_topDescendantsFocusable() {
+        setupOverlayViewController1();
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        setupOverlayViewController2();
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
+
+        verify(mOverlayViewController1).setAllowRotaryFocus(false);
+        verify(mOverlayViewController2).setAllowRotaryFocus(true);
+    }
+
+    @Test
+    public void showView_newHighestZOrder_refreshTopFocus() {
+        setupOverlayViewController1();
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        setupOverlayViewController2();
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
+
+        verify(mOverlayViewController1, never()).refreshRotaryFocusIfNeeded();
+        verify(mOverlayViewController2).refreshRotaryFocusIfNeeded();
+    }
+
+    @Test
     public void showView_oldHighestZOrder() {
         setupOverlayViewController2();
         setOverlayViewControllerAsShowing(mOverlayViewController2);
@@ -345,9 +378,9 @@
     @Test
     public void showView_oldHighestZOrder_shouldShowNavBarFalse_navigationBarsHidden() {
         setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
-        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(true);
         when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(false);
         reset(mWindowInsetsController);
@@ -360,11 +393,12 @@
     @Test
     public void showView_oldHighestZOrder_shouldShowNavBarTrue_navigationBarsShown() {
         setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
-        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(false);
         when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(true);
+        reset(mWindowInsetsController);
 
         mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
 
@@ -374,9 +408,9 @@
     @Test
     public void showView_oldHighestZOrder_shouldShowStatusBarFalse_statusBarsHidden() {
         setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
-        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(true);
         when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(false);
         reset(mWindowInsetsController);
@@ -389,11 +423,12 @@
     @Test
     public void showView_oldHighestZOrder_shouldShowStatusBarTrue_statusBarsShown() {
         setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
-        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(false);
         when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(true);
+        reset(mWindowInsetsController);
 
         mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
 
@@ -426,6 +461,30 @@
     }
 
     @Test
+    public void showView_oldHighestZOrder_topDescendantsFocusable() {
+        setupOverlayViewController1();
+        setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mOverlayViewController1).setAllowRotaryFocus(false);
+        verify(mOverlayViewController2).setAllowRotaryFocus(true);
+    }
+
+    @Test
+    public void showView_oldHighestZOrder_refreshTopFocus() {
+        setupOverlayViewController1();
+        setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mOverlayViewController1, never()).refreshRotaryFocusIfNeeded();
+        verify(mOverlayViewController2).refreshRotaryFocusIfNeeded();
+    }
+
+    @Test
     public void showView_somethingAlreadyShown_windowVisibleNotCalled() {
         setupOverlayViewController1();
         setOverlayViewControllerAsShowing(mOverlayViewController1);
@@ -577,10 +636,10 @@
     public void hideView_newHighestZOrder_shouldShowNavBarFalse_navigationBarHidden() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(false);
         reset(mWindowInsetsController);
 
@@ -593,10 +652,10 @@
     public void hideView_newHighestZOrder_shouldShowNavBarTrue_navigationBarShown() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(true);
         reset(mWindowInsetsController);
 
@@ -609,10 +668,10 @@
     public void hideView_newHighestZOrder_shouldShowStatusBarFalse_statusBarHidden() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(false);
         reset(mWindowInsetsController);
 
@@ -625,10 +684,10 @@
     public void hideView_newHighestZOrder_shouldShowStatusBarTrue_statusBarShown() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(true);
         reset(mWindowInsetsController);
 
@@ -668,10 +727,10 @@
     public void hideView_oldHighestZOrder_shouldShowNavBarFalse_navigationBarHidden() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(false);
         reset(mWindowInsetsController);
 
@@ -684,11 +743,12 @@
     public void hideView_oldHighestZOrder_shouldShowNavBarTrue_navigationBarShown() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(true);
+        reset(mWindowInsetsController);
 
         mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
 
@@ -699,10 +759,10 @@
     public void hideView_oldHighestZOrder_shouldShowStatusBarFalse_statusBarHidden() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(false);
         reset(mWindowInsetsController);
 
@@ -715,11 +775,12 @@
     public void hideView_oldHighestZOrder_shouldShowStatusBarTrue_statusBarShown() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(true);
+        reset(mWindowInsetsController);
 
         mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
 
@@ -917,7 +978,11 @@
 
     private void setOverlayViewControllerAsShowing(OverlayViewController overlayViewController) {
         mOverlayViewGlobalStateController.showView(overlayViewController, /* show= */ null);
+        View layout = overlayViewController.getLayout();
         reset(mSystemUIOverlayWindowController);
+        reset(overlayViewController);
         when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout);
+        when(overlayViewController.getLayout()).thenReturn(layout);
+        when(overlayViewController.isInflated()).thenReturn(true);
     }
 }
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java
index 29cc8ee..765a4e7 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.car.settings.CarSettings;
 import android.os.Handler;
@@ -29,6 +30,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.IWindowManager;
+import android.view.Surface;
 
 import androidx.test.filters.SmallTest;
 
@@ -60,15 +62,20 @@
     private Handler mHandler;
     @Mock
     private TransactionPool mTransactionPool;
+    @Mock
+    private DisplayLayout mDisplayLayout;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mSystemWindows.mContext = mContext;
         mSystemWindows.mWmService = mIWindowManager;
+        when(mDisplayLayout.rotation()).thenReturn(Surface.ROTATION_0);
+        when(mDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mDisplayLayout);
 
         mController = new DisplaySystemBarsController(
-                mSystemWindows,
+                mContext,
+                mIWindowManager,
                 mDisplayController,
                 mHandler,
                 mTransactionPool
@@ -81,7 +88,8 @@
         mController.onDisplayAdded(DISPLAY_ID);
 
         verify(mIWindowManager).setDisplayWindowInsetsController(
-                eq(DISPLAY_ID), any(DisplaySystemBarsController.PerDisplay.class));
+                eq(DISPLAY_ID),
+                any(DisplayImeController.PerDisplay.DisplayWindowInsetsControllerImpl.class));
     }
 
     @Test
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
index ab4da83..20e09a2 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -24,12 +24,12 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.IDisplayWindowInsetsController;
+import android.view.IWindowManager;
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
@@ -39,11 +39,15 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
+import androidx.annotation.BinderThread;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.view.IInputMethodManager;
 import com.android.systemui.TransactionPool;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -66,20 +70,22 @@
     private static final int DIRECTION_HIDE = 2;
     private static final int FLOATING_IME_BOTTOM_INSET = -80;
 
-    SystemWindows mSystemWindows;
-    final Handler mHandler;
+    protected final IWindowManager mWmService;
+    protected final Executor mMainExecutor;
     final TransactionPool mTransactionPool;
+    final DisplayController mDisplayController;
 
     final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>();
 
     final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
 
     @Inject
-    public DisplayImeController(SystemWindows syswin, DisplayController displayController,
-            @Main Handler mainHandler, TransactionPool transactionPool) {
-        mHandler = mainHandler;
-        mSystemWindows = syswin;
+    public DisplayImeController(IWindowManager wmService, DisplayController displayController,
+            @Main Executor mainExecutor, TransactionPool transactionPool) {
+        mWmService = wmService;
+        mMainExecutor = mainExecutor;
         mTransactionPool = transactionPool;
+        mDisplayController = displayController;
         displayController.addDisplayWindowListener(this);
     }
 
@@ -88,12 +94,8 @@
         // Add's a system-ui window-manager specifically for ime. This type is special because
         // WM will defer IME inset handling to it in multi-window scenarious.
         PerDisplay pd = new PerDisplay(displayId,
-                mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation());
-        try {
-            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Unable to set insets controller on display " + displayId);
-        }
+                mDisplayController.getDisplayLayout(displayId).rotation());
+        pd.register();
         mImePerDisplay.put(displayId, pd);
     }
 
@@ -103,7 +105,7 @@
         if (pd == null) {
             return;
         }
-        if (mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation()
+        if (mDisplayController.getDisplayLayout(displayId).rotation()
                 != pd.mRotation && isImeShowing(displayId)) {
             pd.startAnimation(true, false /* forceRestart */);
         }
@@ -112,7 +114,7 @@
     @Override
     public void onDisplayRemoved(int displayId) {
         try {
-            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null);
+            mWmService.setDisplayWindowInsetsController(displayId, null);
         } catch (RemoteException e) {
             Slog.w(TAG, "Unable to remove insets controller on display " + displayId);
         }
@@ -180,9 +182,12 @@
         }
     }
 
-    class PerDisplay extends IDisplayWindowInsetsController.Stub {
+    /** An implementation of {@link IDisplayWindowInsetsController} for a given display id. */
+    public class PerDisplay {
         final int mDisplayId;
         final InsetsState mInsetsState = new InsetsState();
+        protected final DisplayWindowInsetsControllerImpl mInsetsControllerImpl =
+                new DisplayWindowInsetsControllerImpl();
         InsetsSourceControl mImeSourceControl = null;
         int mAnimationDirection = DIRECTION_NONE;
         ValueAnimator mAnimation = null;
@@ -196,28 +201,32 @@
             mRotation = initialRotation;
         }
 
-        @Override
-        public void insetsChanged(InsetsState insetsState) {
-            mHandler.post(() -> {
-                if (mInsetsState.equals(insetsState)) {
-                    return;
-                }
-
-                mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME);
-
-                final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
-                final Rect newFrame = newSource.getFrame();
-                final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame();
-
-                mInsetsState.set(insetsState, true /* copySources */);
-                if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
-                    if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
-                    startAnimation(mImeShowing, true /* forceRestart */);
-                }
-            });
+        public void register() {
+            try {
+                mWmService.setDisplayWindowInsetsController(mDisplayId, mInsetsControllerImpl);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Unable to set insets controller on display " + mDisplayId);
+            }
         }
 
-        @Override
+        public void insetsChanged(InsetsState insetsState) {
+            if (mInsetsState.equals(insetsState)) {
+                return;
+            }
+
+            mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME);
+
+            final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
+            final Rect newFrame = newSource.getFrame();
+            final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame();
+
+            mInsetsState.set(insetsState, true /* copySources */);
+            if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
+                if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
+                startAnimation(mImeShowing, true /* forceRestart */);
+            }
+        }
+
         public void insetsControlChanged(InsetsState insetsState,
                 InsetsSourceControl[] activeControls) {
             insetsChanged(insetsState);
@@ -227,27 +236,25 @@
                         continue;
                     }
                     if (activeControl.getType() == InsetsState.ITYPE_IME) {
-                        mHandler.post(() -> {
-                            final Point lastSurfacePosition = mImeSourceControl != null
-                                    ? mImeSourceControl.getSurfacePosition() : null;
-                            final boolean positionChanged =
-                                    !activeControl.getSurfacePosition().equals(lastSurfacePosition);
-                            final boolean leashChanged =
-                                    !haveSameLeash(mImeSourceControl, activeControl);
-                            mImeSourceControl = activeControl;
-                            if (mAnimation != null) {
-                                if (positionChanged) {
-                                    startAnimation(mImeShowing, true /* forceRestart */);
-                                }
-                            } else {
-                                if (leashChanged) {
-                                    applyVisibilityToLeash();
-                                }
-                                if (!mImeShowing) {
-                                    removeImeSurface();
-                                }
+                        final Point lastSurfacePosition = mImeSourceControl != null
+                                ? mImeSourceControl.getSurfacePosition() : null;
+                        final boolean positionChanged =
+                                !activeControl.getSurfacePosition().equals(lastSurfacePosition);
+                        final boolean leashChanged =
+                                !haveSameLeash(mImeSourceControl, activeControl);
+                        mImeSourceControl = activeControl;
+                        if (mAnimation != null) {
+                            if (positionChanged) {
+                                startAnimation(mImeShowing, true /* forceRestart */);
                             }
-                        });
+                        } else {
+                            if (leashChanged) {
+                                applyVisibilityToLeash();
+                            }
+                            if (!mImeShowing) {
+                                removeImeSurface();
+                            }
+                        }
                     }
                 }
             }
@@ -267,25 +274,22 @@
             }
         }
 
-        @Override
         public void showInsets(int types, boolean fromIme) {
             if ((types & WindowInsets.Type.ime()) == 0) {
                 return;
             }
             if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
-            mHandler.post(() -> startAnimation(true /* show */, false /* forceRestart */));
+            startAnimation(true /* show */, false /* forceRestart */);
         }
 
-        @Override
         public void hideInsets(int types, boolean fromIme) {
             if ((types & WindowInsets.Type.ime()) == 0) {
                 return;
             }
             if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
-            mHandler.post(() -> startAnimation(false /* show */, false /* forceRestart */));
+            startAnimation(false /* show */, false /* forceRestart */);
         }
 
-        @Override
         public void topFocusedWindowChanged(String packageName) {
             // no-op
         }
@@ -296,7 +300,7 @@
         private void setVisibleDirectly(boolean visible) {
             mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
             try {
-                mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
+                mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
             } catch (RemoteException e) {
             }
         }
@@ -315,7 +319,7 @@
             // an IME inset). For now, we assume that no non-floating IME will be <= this nav bar
             // frame height so any reported frame that is <= nav-bar frame height is assumed to
             // be floating.
-            return frame.height() <= mSystemWindows.mDisplayController.getDisplayLayout(mDisplayId)
+            return frame.height() <= mDisplayController.getDisplayLayout(mDisplayId)
                     .navBarFrameHeight();
         }
 
@@ -331,7 +335,7 @@
                 // pretend the ime has some size just below the screen.
                 mImeFrame.set(newFrame);
                 final int floatingInset = (int) (
-                        mSystemWindows.mDisplayController.getDisplayLayout(mDisplayId).density()
+                        mDisplayController.getDisplayLayout(mDisplayId).density()
                                 * FLOATING_IME_BOTTOM_INSET);
                 mImeFrame.bottom -= floatingInset;
             } else if (newFrame.height() != 0) {
@@ -448,6 +452,47 @@
                 setVisibleDirectly(true /* visible */);
             }
         }
+
+        @VisibleForTesting
+        @BinderThread
+        public class DisplayWindowInsetsControllerImpl
+                extends IDisplayWindowInsetsController.Stub {
+            @Override
+            public void topFocusedWindowChanged(String packageName) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.topFocusedWindowChanged(packageName);
+                });
+            }
+
+            @Override
+            public void insetsChanged(InsetsState insetsState) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.insetsChanged(insetsState);
+                });
+            }
+
+            @Override
+            public void insetsControlChanged(InsetsState insetsState,
+                    InsetsSourceControl[] activeControls) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.insetsControlChanged(insetsState, activeControls);
+                });
+            }
+
+            @Override
+            public void showInsets(int types, boolean fromIme) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.showInsets(types, fromIme);
+                });
+            }
+
+            @Override
+            public void hideInsets(int types, boolean fromIme) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.hideInsets(types, fromIme);
+                });
+            }
+        }
     }
 
     void removeImeSurface() {