Merge "Move to android_mallopt for malloc debug calls." into qt-dev
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 5fee2c9..8f8e4d8 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -48,8 +48,7 @@
     void setIconVisibility(String slot, boolean visible);
     @UnsupportedAppUsage
     void removeIcon(String slot);
-    // TODO(b/117478341): support back button change when IME is showing on a external display.
-    void setImeWindowStatus(in IBinder token, int vis, int backDisposition,
+    void setImeWindowStatus(int displayId, in IBinder token, int vis, int backDisposition,
             boolean showImeSwitcher);
     void expandSettingsPanel(String subPanel);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index eca3926..2e9b03c 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -383,7 +383,7 @@
         }
 
         if (TextUtils.isEmpty(displayText) && !airplaneMode) {
-            displayText = TextUtils.join(mSeparator, carrierNames);
+            displayText = joinNotEmpty(mSeparator, carrierNames);
         }
         final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
                 displayText,
@@ -546,6 +546,25 @@
         }
     }
 
+    /**
+     * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
+     * separator added so there are no extra separators that are not needed.
+     */
+    private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
+        int length = sequences.length;
+        if (length == 0) return "";
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < length; i++) {
+            if (!TextUtils.isEmpty(sequences[i])) {
+                if (!TextUtils.isEmpty(sb)) {
+                    sb.append(separator);
+                }
+                sb.append(sequences[i]);
+            }
+        }
+        return sb.toString();
+    }
+
     private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
         if (!TextUtils.isEmpty(string)) {
             list.add(string);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 2f99cf3..d584959 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -18,7 +18,10 @@
 
 import static android.app.StatusBarManager.DISABLE2_NONE;
 import static android.app.StatusBarManager.DISABLE_NONE;
+import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
+import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.systemui.statusbar.phone.StatusBar.ONLY_CORE_APPS;
 
@@ -40,6 +43,7 @@
 import android.os.Message;
 import android.util.Pair;
 import android.util.SparseArray;
+import android.view.inputmethod.InputMethodSystemProperty;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -127,6 +131,11 @@
     private Handler mHandler = new H(Looper.getMainLooper());
     /** A map of display id - disable flag pair */
     private SparseArray<Pair<Integer, Integer>> mDisplayDisabled = new SparseArray<>();
+    /**
+     * The last ID of the display where IME window for which we received setImeWindowStatus
+     * event.
+     */
+    private int mLastUpdatedImeDisplayId = INVALID_DISPLAY;
 
     /**
      * These methods are called back on the main thread.
@@ -785,6 +794,32 @@
         }
     }
 
+    private void handleShowImeButton(int displayId, IBinder token, int vis, int backDisposition,
+            boolean showImeSwitcher) {
+        if (displayId == INVALID_DISPLAY) return;
+
+        if (!InputMethodSystemProperty.MULTI_CLIENT_IME_ENABLED
+                && mLastUpdatedImeDisplayId != displayId
+                && mLastUpdatedImeDisplayId != INVALID_DISPLAY) {
+            // Set previous NavBar's IME window status as invisible when IME
+            // window switched to another display for single-session IME case.
+            sendImeInvisibleStatusForPrevNavBar();
+        }
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            mCallbacks.get(i).setImeWindowStatus(displayId, token, vis, backDisposition,
+                    showImeSwitcher);
+        }
+        mLastUpdatedImeDisplayId = displayId;
+    }
+
+    private void sendImeInvisibleStatusForPrevNavBar() {
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            mCallbacks.get(i).setImeWindowStatus(mLastUpdatedImeDisplayId,
+                    null /* token */, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT,
+                    false /* showImeSwitcher */);
+        }
+    }
+
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
@@ -852,10 +887,9 @@
                     break;
                 case MSG_SHOW_IME_BUTTON:
                     args = (SomeArgs) msg.obj;
-                    for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).setImeWindowStatus(args.argi1, (IBinder) args.arg1,
-                                args.argi2, args.argi3, args.argi4 != 0 /* showImeSwitcher */);
-                    }
+                    handleShowImeButton(args.argi1 /* displayId */, (IBinder) args.arg1 /* token */,
+                            args.argi2 /* vis */, args.argi3 /* backDisposition */,
+                            args.argi4 != 0 /* showImeSwitcher */);
                     break;
                 case MSG_SHOW_RECENT_APPS:
                     for (int i = 0; i < mCallbacks.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 4d2b56c..6c1a4fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -1063,4 +1063,9 @@
         context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
         return navigationBarView;
     }
+
+    @VisibleForTesting
+    int getNavigationIconHints() {
+        return mNavigationIconHints;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index a381bbc..3f33ba6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3926,6 +3926,10 @@
                 mScrimController.setWakeLockScreenSensorActive(true);
             }
 
+            if (reason == DozeLog.PULSE_REASON_DOCKING && mStatusBarWindow != null) {
+                mStatusBarWindow.suppressWakeUpGesture(true);
+            }
+
             boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_NOTIFICATION;
             // Set the state to pulsing, so ScrimController will know what to do once we ask it to
             // execute the transition. The pulse callback will then be invoked when the scrims
@@ -3945,6 +3949,9 @@
                     callback.onPulseFinished();
                     updateNotificationPanelTouchState();
                     mScrimController.setWakeLockScreenSensorActive(false);
+                    if (mStatusBarWindow != null) {
+                        mStatusBarWindow.suppressWakeUpGesture(false);
+                    }
                     setPulsing(false);
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 28db28c..44996ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -107,12 +107,13 @@
     private boolean mTouchActive;
     private boolean mExpandAnimationRunning;
     private boolean mExpandAnimationPending;
+    private boolean mSuppressingWakeUpGesture;
 
     private final GestureDetector.SimpleOnGestureListener mGestureListener =
             new GestureDetector.SimpleOnGestureListener() {
         @Override
         public boolean onSingleTapConfirmed(MotionEvent e) {
-            if (mSingleTapEnabled) {
+            if (mSingleTapEnabled && !mSuppressingWakeUpGesture) {
                 mService.wakeUpIfDozing(SystemClock.uptimeMillis(), StatusBarWindowView.this,
                         "SINGLE_TAP");
                 return true;
@@ -327,6 +328,10 @@
         mTouchActive = touchActive;
     }
 
+    void suppressWakeUpGesture(boolean suppress) {
+        mSuppressingWakeUpGesture = suppress;
+    }
+
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index df534d7..9f91a17 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -104,7 +104,8 @@
 
         mCarrierTextCallbackInfo = new CarrierTextController.CarrierTextCallbackInfo("",
                 new CharSequence[]{}, false, new int[]{});
-        when(mTelephonyManager.getPhoneCount()).thenReturn(2);
+        when(mTelephonyManager.getPhoneCount()).thenReturn(3);
+
         mCarrierTextController = new TestCarrierTextController(mContext, SEPARATOR, true, true,
                 mKeyguardUpdateMonitor);
         // This should not start listening on any of the real dependencies
@@ -130,6 +131,12 @@
         reset(mCarrierTextCallback);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(
                 new ArrayList<>());
+
+        // STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the
+        // same answer as KeyguardUpdateMonitor. Remove when this is addressed
+        when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(
+                new ArrayList<>());
+
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
                 IccCardConstants.State.CARD_IO_ERROR);
         // This should not produce an out of bounds error, even though there are no subscriptions
@@ -173,7 +180,11 @@
         list.add(TEST_SUBSCRIPTION);
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+
+        // STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the
+        // same answer as KeyguardUpdateMonitor. Remove when this is addressed
         when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list);
+
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
@@ -197,7 +208,11 @@
         list.add(TEST_SUBSCRIPTION_ROAMING);
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+
+        // STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the
+        // same answer as KeyguardUpdateMonitor. Remove when this is addressed
         when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list);
+
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
@@ -219,6 +234,12 @@
         reset(mCarrierTextCallback);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(
                 new ArrayList<>());
+
+        // STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the
+        // same answer as KeyguardUpdateMonitor. Remove when this is addressed
+        when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(
+                new ArrayList<>());
+
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
                         CarrierTextController.CarrierTextCallbackInfo.class);
@@ -233,6 +254,121 @@
 
     }
 
+    @Test
+    public void testCarrierText_twoValidSubscriptions() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+
+        // STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the
+        // same answer as KeyguardUpdateMonitor. Remove when this is addressed
+        when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list);
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextController.CarrierTextCallbackInfo.class);
+
+        mCarrierTextController.updateCarrierText();
+        mTestableLooper.processAllMessages();
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER,
+                captor.getValue().carrierText);
+    }
+
+    @Test
+    public void testCarrierText_oneDisabledSub() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimState(anyInt()))
+                .thenReturn(IccCardConstants.State.READY)
+                .thenReturn(IccCardConstants.State.NOT_READY);
+        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+
+        // STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the
+        // same answer as KeyguardUpdateMonitor. Remove when this is addressed
+        when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list);
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextController.CarrierTextCallbackInfo.class);
+
+        mCarrierTextController.updateCarrierText();
+        mTestableLooper.processAllMessages();
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertEquals(TEST_CARRIER,
+                captor.getValue().carrierText);
+    }
+
+    @Test
+    public void testCarrierText_firstDisabledSub() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimState(anyInt()))
+                .thenReturn(IccCardConstants.State.NOT_READY)
+                .thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+
+        // STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the
+        // same answer as KeyguardUpdateMonitor. Remove when this is addressed
+        when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list);
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextController.CarrierTextCallbackInfo.class);
+
+        mCarrierTextController.updateCarrierText();
+        mTestableLooper.processAllMessages();
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertEquals(TEST_CARRIER,
+                captor.getValue().carrierText);
+    }
+
+    @Test
+    public void testCarrierText_threeSubsMiddleDisabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        list.add(TEST_SUBSCRIPTION);
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimState(anyInt()))
+                .thenReturn(IccCardConstants.State.READY)
+                .thenReturn(IccCardConstants.State.NOT_READY)
+                .thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        // STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the
+        // same answer as KeyguardUpdateMonitor. Remove when this is addressed
+        when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list);
+
+        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextController.CarrierTextCallbackInfo.class);
+
+        mCarrierTextController.updateCarrierText();
+        mTestableLooper.processAllMessages();
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER,
+                captor.getValue().carrierText);
+    }
+
     public static class TestCarrierTextController extends CarrierTextController {
         private KeyguardUpdateMonitor mKUM;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 6a0d61d..3ae57e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -14,18 +14,38 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
+import static android.inputmethodservice.InputMethodService.IME_VISIBLE;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.annotation.LayoutRes;
+import android.annotation.Nullable;
 import android.app.Fragment;
+import android.app.FragmentController;
+import android.app.FragmentHostCallback;
 import android.content.Context;
+import android.hardware.display.DisplayManagerGlobal;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.Looper;
 import android.testing.AndroidTestingRunner;
 import android.testing.LeakCheck.Tracker;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
 
@@ -34,6 +54,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiBaseFragmentTest;
+import com.android.systemui.SysuiTestableContext;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
@@ -50,9 +71,16 @@
 @RunWithLooper()
 @SmallTest
 public class NavigationBarFragmentTest extends SysuiBaseFragmentTest {
+    private static final int EXTERNAL_DISPLAY_ID = 2;
+    private static final int NAV_BAR_VIEW_ID = 43;
 
+    private Fragment mFragmentExternalDisplay;
+    private FragmentController mControllerExternalDisplay;
+
+    private SysuiTestableContext mSysuiTestableContextExternal;
     private OverviewProxyService mOverviewProxyService =
             mDependency.injectMockDependency(OverviewProxyService.class);
+    private CommandQueue mCommandQueue;
     private AccessibilityManagerWrapper mAccessibilityWrapper =
             new AccessibilityManagerWrapper(mContext) {
                 Tracker mTracker = mLeakCheck.getTracker("accessibility_manager");
@@ -73,15 +101,51 @@
     }
 
     protected void createRootView() {
-        mView = new NavigationBarFrame(mContext);
+        mView = new NavigationBarFrame(mSysuiContext);
+        mView.setId(NAV_BAR_VIEW_ID);
     }
 
     @Before
-    public void setup() {
-        mSysuiContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
+    public void setupFragment() throws Exception {
+        setupSysuiDependency();
+        createRootView();
+        TestableLooper.get(this).runWithLooper(() -> {
+            mHandler = new Handler();
+
+            mFragment = instantiate(mSysuiContext, NavigationBarFragment.class.getName(), null);
+            mFragments = FragmentController.createController(
+                    new HostCallbacksForExternalDisplay(mSysuiContext));
+            mFragments.attachHost(null);
+            mFragments.getFragmentManager().beginTransaction()
+                    .replace(NAV_BAR_VIEW_ID, mFragment)
+                    .commit();
+            mControllerExternalDisplay = FragmentController.createController(
+                    new HostCallbacksForExternalDisplay(mSysuiTestableContextExternal));
+            mControllerExternalDisplay.attachHost(null);
+            mFragmentExternalDisplay = instantiate(mSysuiTestableContextExternal,
+                    NavigationBarFragment.class.getName(), null);
+            mControllerExternalDisplay.getFragmentManager().beginTransaction()
+                    .replace(NAV_BAR_VIEW_ID, mFragmentExternalDisplay)
+                    .commit();
+        });
+    }
+
+    private void setupSysuiDependency() {
+        mCommandQueue = new CommandQueue(mContext);
+        mSysuiContext.putComponent(CommandQueue.class, mCommandQueue);
         mSysuiContext.putComponent(StatusBar.class, mock(StatusBar.class));
         mSysuiContext.putComponent(Recents.class, mock(Recents.class));
         mSysuiContext.putComponent(Divider.class, mock(Divider.class));
+
+        Display display = new Display(DisplayManagerGlobal.getInstance(), EXTERNAL_DISPLAY_ID,
+                new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
+        mSysuiTestableContextExternal = (SysuiTestableContext) mSysuiContext.createDisplayContext(
+                display);
+        mSysuiTestableContextExternal.putComponent(CommandQueue.class, mCommandQueue);
+        mSysuiTestableContextExternal.putComponent(StatusBar.class, mock(StatusBar.class));
+        mSysuiTestableContextExternal.putComponent(Recents.class, mock(Recents.class));
+        mSysuiTestableContextExternal.putComponent(Divider.class, mock(Divider.class));
+
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         WindowManager windowManager = mock(WindowManager.class);
         Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
@@ -102,15 +166,111 @@
         navigationBarFragment.onHomeLongClick(navigationBarFragment.getView());
     }
 
+    @Test
+    public void testSetImeWindowStatusWhenImeSwitchOnDisplay() {
+        // Create default & external NavBar fragment.
+        NavigationBarFragment defaultNavBar = (NavigationBarFragment) mFragment;
+        NavigationBarFragment externalNavBar = (NavigationBarFragment) mFragmentExternalDisplay;
+        mFragments.dispatchCreate();
+        processAllMessages();
+        mFragments.dispatchResume();
+        processAllMessages();
+        mControllerExternalDisplay.dispatchCreate();
+        processAllMessages();
+        mControllerExternalDisplay.dispatchResume();
+        processAllMessages();
+
+        // Set IME window status for default NavBar.
+        mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
+                BACK_DISPOSITION_DEFAULT, true);
+        Handler.getMain().runWithScissors(() -> { }, 500);
+
+        // Verify IME window state will be updated in default NavBar & external NavBar state reset.
+        assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
+                defaultNavBar.getNavigationIconHints());
+        assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
+        assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
+
+        // Set IME window status for external NavBar.
+        mCommandQueue.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null,
+                IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true);
+        Handler.getMain().runWithScissors(() -> { }, 500);
+
+        // Verify IME window state will be updated in external NavBar & default NavBar state reset.
+        assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
+                externalNavBar.getNavigationIconHints());
+        assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
+        assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
+    }
+
     @Override
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         DeviceProvisionedController deviceProvisionedController =
                 mock(DeviceProvisionedController.class);
         assertNotNull(mAccessibilityWrapper);
-        return new NavigationBarFragment(mAccessibilityWrapper,
+        return new NavigationBarFragment(
+                context.getDisplayId() == DEFAULT_DISPLAY ? mAccessibilityWrapper
+                        : mock(AccessibilityManagerWrapper.class),
                 deviceProvisionedController,
                 new MetricsLogger(),
-                new AssistManager(deviceProvisionedController, mContext),
+                mock(AssistManager.class),
                 mOverviewProxyService);
     }
+
+    private class HostCallbacksForExternalDisplay extends
+            FragmentHostCallback<NavigationBarFragmentTest> {
+        private Context mDisplayContext;
+
+        HostCallbacksForExternalDisplay(Context context) {
+            super(context, mHandler, 0);
+            mDisplayContext = context;
+        }
+
+        @Override
+        public NavigationBarFragmentTest onGetHost() {
+            return NavigationBarFragmentTest.this;
+        }
+
+        @Override
+        public Fragment instantiate(Context context, String className, Bundle arguments) {
+            return NavigationBarFragmentTest.this.instantiate(context, className, arguments);
+        }
+
+        @Override
+        public View onFindViewById(int id) {
+            return mView.findViewById(id);
+        }
+
+        @Override
+        public LayoutInflater onGetLayoutInflater() {
+            return new LayoutInflaterWrapper(mDisplayContext);
+        }
+    }
+
+    private static class LayoutInflaterWrapper extends LayoutInflater {
+        protected LayoutInflaterWrapper(Context context) {
+            super(context);
+        }
+
+        @Override
+        public LayoutInflater cloneInContext(Context newContext) {
+            return null;
+        }
+
+        @Override
+        public View inflate(@LayoutRes int resource, @Nullable ViewGroup root,
+                boolean attachToRoot) {
+            NavigationBarView view = mock(NavigationBarView.class);
+            when(view.getDisplay()).thenReturn(mContext.getDisplay());
+            when(view.getBackButton()).thenReturn(mock(ButtonDispatcher.class));
+            when(view.getHomeButton()).thenReturn(mock(ButtonDispatcher.class));
+            when(view.getRecentsButton()).thenReturn(mock(ButtonDispatcher.class));
+            when(view.getAccessibilityButton()).thenReturn(mock(ButtonDispatcher.class));
+            when(view.getRotateSuggestionButton()).thenReturn(mock(RotationContextButton.class));
+            when(view.getBarTransitions()).thenReturn(mock(BarTransitions.class));
+            when(view.getLightTransitionsController()).thenReturn(
+                    mock(LightBarTransitionsController.class));
+            return view;
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 43bc21b..fb16465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -681,6 +681,25 @@
     }
 
     @Test
+    public void testPulseWhileDozingWithDockingReason_suppressWakeUpGesture() {
+        // Keep track of callback to be able to stop the pulse
+        final DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
+        doAnswer(invocation -> {
+            pulseCallback[0] = invocation.getArgument(0);
+            return null;
+        }).when(mDozeScrimController).pulse(any(), anyInt());
+
+        // Starting a pulse while docking should suppress wakeup gesture
+        mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class),
+                DozeLog.PULSE_REASON_DOCKING);
+        verify(mStatusBarWindowView).suppressWakeUpGesture(eq(true));
+
+        // Ending a pulse should restore wakeup gesture
+        pulseCallback[0].onPulseFinished();
+        verify(mStatusBarWindowView).suppressWakeUpGesture(eq(false));
+    }
+
+    @Test
     public void testSetState_changesIsFullScreenUserSwitcherState() {
         mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
         assertFalse(mStatusBar.isFullScreenUserSwitcherState());
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3c97c39..c2aade3 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -575,6 +575,12 @@
      */
     int mCurTokenDisplayId = INVALID_DISPLAY;
 
+    /**
+     * The display ID of the input method indicates the fallback display which returned by
+     * {@link #computeImeDisplayIdForTarget}.
+     */
+    private static final int FALLBACK_DISPLAY_ID = DEFAULT_DISPLAY;
+
     final ImeDisplayValidator mImeDisplayValidator;
 
     /**
@@ -625,7 +631,8 @@
      *    currently invisible.
      * </dd>
      * </dl>
-     * <em>Do not update this value outside of setImeWindowStatus.</em>
+     * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
+     * {@link #unbindCurrentMethodLocked()}.</em>
      */
     int mImeWindowVis;
 
@@ -2124,12 +2131,10 @@
      */
     static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) {
         if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) {
-            // We always assume that the default display id suitable to show the IME window.
-            return DEFAULT_DISPLAY;
+            return FALLBACK_DISPLAY_ID;
         }
-        // Show IME in default display when the display with IME target doesn't support system
-        // decorations.
-        return checker.displayCanShowIme(displayId) ? displayId : DEFAULT_DISPLAY;
+        // Show IME window on fallback display when the display is not allowed.
+        return checker.displayCanShowIme(displayId) ? displayId : FALLBACK_DISPLAY_ID;
     }
 
     @Override
@@ -2198,6 +2203,10 @@
                 mIWindowManager.removeWindowToken(mCurToken, mCurTokenDisplayId);
             } catch (RemoteException e) {
             }
+            // Set IME window status as invisible when unbind current method.
+            mImeWindowVis = 0;
+            mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
+            updateSystemUiLocked(mImeWindowVis, mBackDisposition);
             mCurToken = null;
             mCurTokenDisplayId = INVALID_DISPLAY;
         }
@@ -2399,10 +2408,20 @@
     @BinderThread
     @SuppressWarnings("deprecation")
     private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) {
+        final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId();
+
         synchronized (mMethodMap) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
+            // Skip update IME status when current token display is not same as focused display.
+            // Note that we still need to update IME status when focusing external display
+            // that does not support system decoration and fallback to show IME on default
+            // display since it is intentional behavior.
+            if (mCurTokenDisplayId != topFocusedDisplayId
+                    && mCurTokenDisplayId != FALLBACK_DISPLAY_ID) {
+                return;
+            }
             mImeWindowVis = vis;
             mBackDisposition = backDisposition;
             updateSystemUiLocked(vis, backDisposition);
@@ -2447,7 +2466,8 @@
         if (DEBUG) {
             Slog.d(TAG, "IME window vis: " + vis
                     + " active: " + (vis & InputMethodService.IME_ACTIVE)
-                    + " inv: " + (vis & InputMethodService.IME_INVISIBLE));
+                    + " inv: " + (vis & InputMethodService.IME_INVISIBLE)
+                    + " displayId: " + mCurTokenDisplayId);
         }
 
         // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
@@ -2461,7 +2481,7 @@
             // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked().
             final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
             if (mStatusBar != null) {
-                mStatusBar.setImeWindowStatus(mCurToken, vis, backDisposition,
+                mStatusBar.setImeWindowStatus(mCurTokenDisplayId, mCurToken, vis, backDisposition,
                         needsToShowImeSwitcher);
             }
             final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 8578bb7..a349d87 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -248,7 +248,8 @@
             scheduleOpTimeOutLocked();
             final Intent intent = new Intent().setComponent(job.getServiceComponent());
             boolean binding = mContext.bindServiceAsUser(intent, this,
-                    Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
+                    Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
+                    | Context.BIND_NOT_VISIBLE | Context.BIND_ADJUST_BELOW_PERCEPTIBLE,
                     new UserHandle(job.getUserId()));
             if (!binding) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index aaf3df3..9cbf00b 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -843,10 +843,9 @@
         }
     }
 
-    // TODO(b/117478341): support back button change when IME is showing on a external display.
     @Override
-    public void setImeWindowStatus(final IBinder token, final int vis, final int backDisposition,
-            final boolean showImeSwitcher) {
+    public void setImeWindowStatus(int displayId, final IBinder token, final int vis,
+            final int backDisposition, final boolean showImeSwitcher) {
         enforceStatusBar();
 
         if (SPEW) {
@@ -857,18 +856,13 @@
             // In case of IME change, we need to call up setImeWindowStatus() regardless of
             // mImeWindowVis because mImeWindowVis may not have been set to false when the
             // previous IME was destroyed.
-            // TODO(b/117478341): support back button change when IME is showing on a external
-            // display.
-            getUiState(DEFAULT_DISPLAY)
-                    .setImeWindowState(vis, backDisposition, showImeSwitcher, token);
+            getUiState(displayId).setImeWindowState(vis, backDisposition, showImeSwitcher, token);
 
             mHandler.post(() -> {
                 if (mBar == null) return;
                 try {
-                    // TODO(b/117478341): support back button change when IME is showing on a
-                    // external display.
                     mBar.setImeWindowStatus(
-                            DEFAULT_DISPLAY, token, vis, backDisposition, showImeSwitcher);
+                            displayId, token, vis, backDisposition, showImeSwitcher);
                 } catch (RemoteException ex) { }
             });
         }
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 76b0351..4ed07c3 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -4089,11 +4089,14 @@
         // The activity that we are finishing may be over the lock screen. In this case, we do not
         // want to consider activities that cannot be shown on the lock screen as running and should
         // proceed with finishing the activity if there is no valid next top running activity.
+        // Note that if this finishing activity is floating task, we don't need to wait the
+        // next activity resume and can destroy it directly.
         final ActivityDisplay display = getDisplay();
         final ActivityRecord next = display.topRunningActivity(true /* considerKeyguardState */);
+        final boolean isFloating = r.getConfiguration().windowConfiguration.tasksAreFloating();
 
         if (mode == FINISH_AFTER_VISIBLE && (r.visible || r.nowVisible)
-                && next != null && !next.nowVisible) {
+                && next != null && !next.nowVisible && !isFloating) {
             if (!mStackSupervisor.mStoppingActivities.contains(r)) {
                 addToStopping(r, false /* scheduleIdle */, false /* idleDelayed */);
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 9d80425..78c5dbd 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -476,6 +476,11 @@
     public abstract int getDisplayIdForWindow(IBinder windowToken);
 
     /**
+     * @return The top focused display ID.
+     */
+    public abstract int getTopFocusedDisplayId();
+
+    /**
      * Checks whether this display should support showing system decorations.
      */
     public abstract boolean shouldShowSystemDecorOnDisplay(int displayId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8ed2a15..d46aa7b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7340,6 +7340,13 @@
         }
 
         @Override
+        public int getTopFocusedDisplayId() {
+            synchronized (mGlobalLock) {
+                return mRoot.getTopFocusedDisplayContent().getDisplayId();
+            }
+        }
+
+        @Override
         public boolean shouldShowSystemDecorOnDisplay(int displayId) {
             synchronized (mGlobalLock) {
                 return WindowManagerService.this.shouldShowSystemDecors(displayId);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8e18683..4105487 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2861,6 +2861,13 @@
             }
             mDestroying = false;
             destroyedSomething = true;
+
+            // Since mDestroying will affect AppWindowToken#allDrawn, we need to perform another
+            // traversal in case we are waiting on this window to start the transition.
+            if (getDisplayContent().mAppTransition.isTransitionSet()
+                    && getDisplayContent().mOpeningApps.contains(mAppToken)) {
+                mWmService.mWindowPlacerLocked.requestTraversal();
+            }
         }
 
         return destroyedSomething;
diff --git a/tests/testables/src/android/testing/BaseFragmentTest.java b/tests/testables/src/android/testing/BaseFragmentTest.java
index 9f60cce..6cd88b5 100644
--- a/tests/testables/src/android/testing/BaseFragmentTest.java
+++ b/tests/testables/src/android/testing/BaseFragmentTest.java
@@ -52,7 +52,7 @@
 
     private static final int VIEW_ID = 42;
     private final Class<? extends Fragment> mCls;
-    private Handler mHandler;
+    protected Handler mHandler;
     protected FrameLayout mView;
     protected FragmentController mFragments;
     protected Fragment mFragment;