Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/NavigationBarsListener.kt 0 → 100644 +45 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.common import android.graphics.Insets import android.graphics.Rect import android.view.InsetsState import android.view.WindowInsets.Type import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener abstract class NavigationBarsListener( private val displayController: DisplayController, val displayId: Int ) : OnInsetsChangedListener { private var oldInsets = Insets.NONE override fun insetsChanged(insetsState: InsetsState) { getNavigationBarsInsets(insetsState).takeIf { it != oldInsets }?.let { newInsets -> oldInsets = newInsets onNavigationBarsVisibilityChanged(newInsets) } } private fun getNavigationBarsInsets(insetsState: InsetsState): Insets { val layout = displayController.getDisplayLayout(displayId) ?: return Insets.NONE return insetsState.calculateInsets(Rect(0, 0, layout.width(), layout.height()), Type.navigationBars(), /* ignoreVisibility= */true) } protected abstract fun onNavigationBarsVisibilityChanged(insets: Insets) } No newline at end of file libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java +20 −7 Original line number Original line Diff line number Diff line Loading @@ -18,8 +18,11 @@ package com.android.wm.shell.common.pip; import static com.android.wm.shell.common.pip.PipUtils.dpToPx; import static com.android.wm.shell.common.pip.PipUtils.dpToPx; import static java.lang.Math.max; import android.content.Context; import android.content.Context; import android.content.res.Resources; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Point; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Rect; import android.util.Size; import android.util.Size; Loading @@ -45,8 +48,8 @@ public class PipDisplayLayoutState { private Context mContext; private Context mContext; private int mDisplayId; private int mDisplayId; @NonNull private DisplayLayout mDisplayLayout; @NonNull private DisplayLayout mDisplayLayout; private Point mScreenEdgeInsets = null; private Point mScreenEdgeInsets = null; private Insets mNavigationBarsInsets = Insets.NONE; @Inject @Inject public PipDisplayLayoutState(Context context) { public PipDisplayLayoutState(Context context) { Loading Loading @@ -81,12 +84,16 @@ public class PipDisplayLayoutState { * Returns the inset bounds the PIP window can be visible in. * Returns the inset bounds the PIP window can be visible in. */ */ public Rect getInsetBounds() { public Rect getInsetBounds() { Rect insetBounds = new Rect(); final Rect insetBounds = new Rect(); Rect insets = getDisplayLayout().stableInsets(); final Rect stableInsets = getDisplayLayout().stableInsets(); insetBounds.set(insets.left + getScreenEdgeInsets().x, final Point screenEdgeInsets = getScreenEdgeInsets(); insets.top + getScreenEdgeInsets().y, final int left = max(stableInsets.left, mNavigationBarsInsets.left) + screenEdgeInsets.x; getDisplayLayout().width() - insets.right - getScreenEdgeInsets().x, final int top = max(stableInsets.top, mNavigationBarsInsets.top) + screenEdgeInsets.y; getDisplayLayout().height() - insets.bottom - getScreenEdgeInsets().y); final int right = getDisplayLayout().width() - max(stableInsets.right, mNavigationBarsInsets.right) - screenEdgeInsets.x; final int bottom = getDisplayLayout().height() - max(stableInsets.bottom, mNavigationBarsInsets.bottom) - screenEdgeInsets.y; insetBounds.set(left, top, right, bottom); return insetBounds; return insetBounds; } } Loading Loading @@ -131,6 +138,11 @@ public class PipDisplayLayoutState { mDisplayId = displayId; mDisplayId = displayId; } } /** Set the navigationBars side and widthOrHeight. */ public void setNavigationBarsInsets(Insets insets) { mNavigationBarsInsets = insets; } /** Dumps internal state. */ /** Dumps internal state. */ public void dump(PrintWriter pw, String prefix) { public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; final String innerPrefix = prefix + " "; Loading @@ -138,5 +150,6 @@ public class PipDisplayLayoutState { pw.println(innerPrefix + "mDisplayId=" + mDisplayId); pw.println(innerPrefix + "mDisplayId=" + mDisplayId); pw.println(innerPrefix + "getDisplayBounds=" + getDisplayBounds()); pw.println(innerPrefix + "getDisplayBounds=" + getDisplayBounds()); pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets); pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets); pw.println(innerPrefix + "mNavigationBarsInsets=" + mNavigationBarsInsets); } } } } libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +11 −0 Original line number Original line Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Rect; import android.os.Bundle; import android.os.Bundle; import android.util.Log; import android.util.Log; Loading @@ -51,6 +52,7 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.ImeListener; import com.android.wm.shell.common.ImeListener; import com.android.wm.shell.common.NavigationBarsListener; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.SingleInstanceRemoteListener; Loading Loading @@ -245,6 +247,15 @@ public class PipController implements ConfigurationChangeListener, mPipTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight); mPipTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight); } } }); }); mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(), new NavigationBarsListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) { @Override protected void onNavigationBarsVisibilityChanged( @NonNull Insets insets) { mPipDisplayLayoutState.setNavigationBarsInsets(insets); } }); // Allow other outside processes to bind to PiP controller using the key below. // Allow other outside processes to bind to PiP controller using the key below. mShellController.addExternalInterface(IPip.DESCRIPTOR, mShellController.addExternalInterface(IPip.DESCRIPTOR, Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/NavigationBarsListenerTest.kt 0 → 100644 +199 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.common import android.content.res.Configuration import android.content.res.Resources import android.graphics.Insets import android.graphics.Rect import android.testing.AndroidTestingRunner import android.view.DisplayCutout import android.view.DisplayInfo import android.view.InsetsSource import android.view.InsetsSource.ID_IME import android.view.InsetsState import android.view.Surface import android.view.WindowInsets.Type import androidx.test.filters.SmallTest import com.android.internal.R import com.android.wm.shell.ShellTestCase import junit.framework.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidTestingRunner::class) class NavigationBarsListenerTest : ShellTestCase() { private lateinit var navigationBarsListener: TestingNavigationBarsListener private lateinit var displayLayout: DisplayLayout @Mock private lateinit var displayController: DisplayController @Before fun setUp() { val resources = createResources(40, 50, false) val displayInfo = createDisplayInfo(1000, 1500, 0, Surface.ROTATION_0) displayLayout = DisplayLayout(displayInfo, resources, false, false) whenever(displayController.getDisplayLayout(DEFAULT_DISPLAY_ID)).thenReturn(displayLayout) navigationBarsListener = TestingNavigationBarsListener(displayController, DEFAULT_DISPLAY_ID) } @Test fun insetsChanged_bottomNavigationBars_callsOnBottomNavigationBarsAppears() { val insets = Insets.of(0, 0, 0, DEFAULT_NAVIGATION_BARS_SIZE) val insetsState = createBottomNavigationBars(insets) navigationBarsListener.insetsChanged(insetsState) assertEquals(insets, navigationBarsListener.testInsets) } @Test fun insetsChanged_rightNavigationBars_callsOnRightNavigationBarsAppears() { val insets = Insets.of(0, 0, DEFAULT_NAVIGATION_BARS_SIZE, 0) val insetsStateWithNavigationBars = createRightNavigationBars(insets) navigationBarsListener.insetsChanged(insetsStateWithNavigationBars) assertEquals(insets, navigationBarsListener.testInsets) } @Test fun insetsChanged_zeroNavigationBars_callsOnNavigationBarsDisappears() { val zeroInsets = Insets.of(0, 0, 0, 0) val insetsStateWithoutNavigationBars = createBottomNavigationBars(zeroInsets) navigationBarsListener.insetsChanged(insetsStateWithoutNavigationBars) assertEquals(Insets.NONE, navigationBarsListener.testInsets) } @Test fun insetsChanged_imeAppears_doesNotCallOnNavigationBarsInsetsChanged() { val insets = Insets.of(0, 0, 0, DEFAULT_IME_HEIGHT) // Send insetsState with an IME. val insetsState = createIme(insets) navigationBarsListener.insetsChanged(insetsState) assertEquals(Insets.NONE, navigationBarsListener.testInsets) } private fun createBottomNavigationBars(insets: Insets): InsetsState { val stableBounds = Rect() displayLayout.getStableBounds(stableBounds) val displayRect = Rect() displayRect.set(0, 0, displayLayout.width(), displayLayout.height()) val insetsState = InsetsState() val insetsSource = insetsState.getOrCreateSource( InsetsSource.createId( null, 0, Type.navigationBars()), Type.navigationBars() ) val frame = Rect(0, displayLayout.height() - insets.bottom, displayLayout.width(), displayLayout.height()) insetsSource.setFrame(frame) return insetsState } private fun createRightNavigationBars(insets: Insets): InsetsState { val stableBounds = Rect() displayLayout.getStableBounds(stableBounds) val displayRect = Rect() displayRect.set(0, 0, displayLayout.width(), displayLayout.height()) val insetsState = InsetsState() val insetsSource = insetsState.getOrCreateSource( InsetsSource.createId( null, 0, Type.navigationBars()), Type.navigationBars() ) val frame = Rect(displayLayout.width() - insets.right, 0, displayLayout.width(), displayLayout.height()) insetsSource.setFrame(frame) return insetsState } private fun createIme(insets: Insets): InsetsState { val stableBounds = Rect() displayLayout.getStableBounds(stableBounds) val displayRect = Rect() displayRect.set(0, 0, displayLayout.width(), displayLayout.height()) val insetsState = InsetsState() val insetsSource = insetsState.getOrCreateSource(ID_IME, Type.ime()) insetsSource.setVisible(true) insetsSource.setFrame(stableBounds.left, stableBounds.bottom - insets.bottom, stableBounds.right, stableBounds.bottom) return insetsState } private fun createDisplayInfo(width: Int, height: Int, cutoutHeight: Int, rotation: Int): DisplayInfo { val info = DisplayInfo() info.logicalWidth = width info.logicalHeight = height info.rotation = rotation if (cutoutHeight > 0) { info.displayCutout = DisplayCutout( Insets.of(0, cutoutHeight, 0, 0) /* safeInsets */, null /* boundLeft */, Rect(width / 2 - cutoutHeight, 0, width / 2 + cutoutHeight, cutoutHeight) /* boundTop */, null /* boundRight */, null /* boundBottom */) } else { info.displayCutout = DisplayCutout.NO_CUTOUT } info.logicalDensityDpi = 300 return info } private fun createResources(navLand: Int, navPort: Int, navMoves: Boolean): Resources { val cfg = Configuration() cfg.uiMode = Configuration.UI_MODE_TYPE_NORMAL val res = Mockito.mock(Resources::class.java) Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize( R.dimen.navigation_bar_height_landscape_car_mode) Mockito.doReturn(navPort).whenever(res).getDimensionPixelSize( R.dimen.navigation_bar_height_car_mode) Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize( R.dimen.navigation_bar_width_car_mode) Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize( R.dimen.navigation_bar_height_landscape) Mockito.doReturn(navPort).whenever(res).getDimensionPixelSize( R.dimen.navigation_bar_height) Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize( R.dimen.navigation_bar_width) Mockito.doReturn(navMoves).whenever(res).getBoolean(R.bool.config_navBarCanMove) Mockito.doReturn(cfg).whenever(res).configuration return res } private class TestingNavigationBarsListener( displayController: DisplayController, displayId: Int ) : NavigationBarsListener(displayController, displayId) { var testInsets = Insets.NONE override fun onNavigationBarsVisibilityChanged(insets: Insets) { testInsets = insets } } companion object { private const val DEFAULT_DISPLAY_ID = 0 private const val DEFAULT_NAVIGATION_BARS_SIZE = 100 private const val DEFAULT_IME_HEIGHT = 500 } } No newline at end of file Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/NavigationBarsListener.kt 0 → 100644 +45 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.common import android.graphics.Insets import android.graphics.Rect import android.view.InsetsState import android.view.WindowInsets.Type import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener abstract class NavigationBarsListener( private val displayController: DisplayController, val displayId: Int ) : OnInsetsChangedListener { private var oldInsets = Insets.NONE override fun insetsChanged(insetsState: InsetsState) { getNavigationBarsInsets(insetsState).takeIf { it != oldInsets }?.let { newInsets -> oldInsets = newInsets onNavigationBarsVisibilityChanged(newInsets) } } private fun getNavigationBarsInsets(insetsState: InsetsState): Insets { val layout = displayController.getDisplayLayout(displayId) ?: return Insets.NONE return insetsState.calculateInsets(Rect(0, 0, layout.width(), layout.height()), Type.navigationBars(), /* ignoreVisibility= */true) } protected abstract fun onNavigationBarsVisibilityChanged(insets: Insets) } No newline at end of file
libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java +20 −7 Original line number Original line Diff line number Diff line Loading @@ -18,8 +18,11 @@ package com.android.wm.shell.common.pip; import static com.android.wm.shell.common.pip.PipUtils.dpToPx; import static com.android.wm.shell.common.pip.PipUtils.dpToPx; import static java.lang.Math.max; import android.content.Context; import android.content.Context; import android.content.res.Resources; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Point; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Rect; import android.util.Size; import android.util.Size; Loading @@ -45,8 +48,8 @@ public class PipDisplayLayoutState { private Context mContext; private Context mContext; private int mDisplayId; private int mDisplayId; @NonNull private DisplayLayout mDisplayLayout; @NonNull private DisplayLayout mDisplayLayout; private Point mScreenEdgeInsets = null; private Point mScreenEdgeInsets = null; private Insets mNavigationBarsInsets = Insets.NONE; @Inject @Inject public PipDisplayLayoutState(Context context) { public PipDisplayLayoutState(Context context) { Loading Loading @@ -81,12 +84,16 @@ public class PipDisplayLayoutState { * Returns the inset bounds the PIP window can be visible in. * Returns the inset bounds the PIP window can be visible in. */ */ public Rect getInsetBounds() { public Rect getInsetBounds() { Rect insetBounds = new Rect(); final Rect insetBounds = new Rect(); Rect insets = getDisplayLayout().stableInsets(); final Rect stableInsets = getDisplayLayout().stableInsets(); insetBounds.set(insets.left + getScreenEdgeInsets().x, final Point screenEdgeInsets = getScreenEdgeInsets(); insets.top + getScreenEdgeInsets().y, final int left = max(stableInsets.left, mNavigationBarsInsets.left) + screenEdgeInsets.x; getDisplayLayout().width() - insets.right - getScreenEdgeInsets().x, final int top = max(stableInsets.top, mNavigationBarsInsets.top) + screenEdgeInsets.y; getDisplayLayout().height() - insets.bottom - getScreenEdgeInsets().y); final int right = getDisplayLayout().width() - max(stableInsets.right, mNavigationBarsInsets.right) - screenEdgeInsets.x; final int bottom = getDisplayLayout().height() - max(stableInsets.bottom, mNavigationBarsInsets.bottom) - screenEdgeInsets.y; insetBounds.set(left, top, right, bottom); return insetBounds; return insetBounds; } } Loading Loading @@ -131,6 +138,11 @@ public class PipDisplayLayoutState { mDisplayId = displayId; mDisplayId = displayId; } } /** Set the navigationBars side and widthOrHeight. */ public void setNavigationBarsInsets(Insets insets) { mNavigationBarsInsets = insets; } /** Dumps internal state. */ /** Dumps internal state. */ public void dump(PrintWriter pw, String prefix) { public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; final String innerPrefix = prefix + " "; Loading @@ -138,5 +150,6 @@ public class PipDisplayLayoutState { pw.println(innerPrefix + "mDisplayId=" + mDisplayId); pw.println(innerPrefix + "mDisplayId=" + mDisplayId); pw.println(innerPrefix + "getDisplayBounds=" + getDisplayBounds()); pw.println(innerPrefix + "getDisplayBounds=" + getDisplayBounds()); pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets); pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets); pw.println(innerPrefix + "mNavigationBarsInsets=" + mNavigationBarsInsets); } } } }
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +11 −0 Original line number Original line Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Rect; import android.os.Bundle; import android.os.Bundle; import android.util.Log; import android.util.Log; Loading @@ -51,6 +52,7 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.ImeListener; import com.android.wm.shell.common.ImeListener; import com.android.wm.shell.common.NavigationBarsListener; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.SingleInstanceRemoteListener; Loading Loading @@ -245,6 +247,15 @@ public class PipController implements ConfigurationChangeListener, mPipTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight); mPipTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight); } } }); }); mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(), new NavigationBarsListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) { @Override protected void onNavigationBarsVisibilityChanged( @NonNull Insets insets) { mPipDisplayLayoutState.setNavigationBarsInsets(insets); } }); // Allow other outside processes to bind to PiP controller using the key below. // Allow other outside processes to bind to PiP controller using the key below. mShellController.addExternalInterface(IPip.DESCRIPTOR, mShellController.addExternalInterface(IPip.DESCRIPTOR, Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/NavigationBarsListenerTest.kt 0 → 100644 +199 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.common import android.content.res.Configuration import android.content.res.Resources import android.graphics.Insets import android.graphics.Rect import android.testing.AndroidTestingRunner import android.view.DisplayCutout import android.view.DisplayInfo import android.view.InsetsSource import android.view.InsetsSource.ID_IME import android.view.InsetsState import android.view.Surface import android.view.WindowInsets.Type import androidx.test.filters.SmallTest import com.android.internal.R import com.android.wm.shell.ShellTestCase import junit.framework.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidTestingRunner::class) class NavigationBarsListenerTest : ShellTestCase() { private lateinit var navigationBarsListener: TestingNavigationBarsListener private lateinit var displayLayout: DisplayLayout @Mock private lateinit var displayController: DisplayController @Before fun setUp() { val resources = createResources(40, 50, false) val displayInfo = createDisplayInfo(1000, 1500, 0, Surface.ROTATION_0) displayLayout = DisplayLayout(displayInfo, resources, false, false) whenever(displayController.getDisplayLayout(DEFAULT_DISPLAY_ID)).thenReturn(displayLayout) navigationBarsListener = TestingNavigationBarsListener(displayController, DEFAULT_DISPLAY_ID) } @Test fun insetsChanged_bottomNavigationBars_callsOnBottomNavigationBarsAppears() { val insets = Insets.of(0, 0, 0, DEFAULT_NAVIGATION_BARS_SIZE) val insetsState = createBottomNavigationBars(insets) navigationBarsListener.insetsChanged(insetsState) assertEquals(insets, navigationBarsListener.testInsets) } @Test fun insetsChanged_rightNavigationBars_callsOnRightNavigationBarsAppears() { val insets = Insets.of(0, 0, DEFAULT_NAVIGATION_BARS_SIZE, 0) val insetsStateWithNavigationBars = createRightNavigationBars(insets) navigationBarsListener.insetsChanged(insetsStateWithNavigationBars) assertEquals(insets, navigationBarsListener.testInsets) } @Test fun insetsChanged_zeroNavigationBars_callsOnNavigationBarsDisappears() { val zeroInsets = Insets.of(0, 0, 0, 0) val insetsStateWithoutNavigationBars = createBottomNavigationBars(zeroInsets) navigationBarsListener.insetsChanged(insetsStateWithoutNavigationBars) assertEquals(Insets.NONE, navigationBarsListener.testInsets) } @Test fun insetsChanged_imeAppears_doesNotCallOnNavigationBarsInsetsChanged() { val insets = Insets.of(0, 0, 0, DEFAULT_IME_HEIGHT) // Send insetsState with an IME. val insetsState = createIme(insets) navigationBarsListener.insetsChanged(insetsState) assertEquals(Insets.NONE, navigationBarsListener.testInsets) } private fun createBottomNavigationBars(insets: Insets): InsetsState { val stableBounds = Rect() displayLayout.getStableBounds(stableBounds) val displayRect = Rect() displayRect.set(0, 0, displayLayout.width(), displayLayout.height()) val insetsState = InsetsState() val insetsSource = insetsState.getOrCreateSource( InsetsSource.createId( null, 0, Type.navigationBars()), Type.navigationBars() ) val frame = Rect(0, displayLayout.height() - insets.bottom, displayLayout.width(), displayLayout.height()) insetsSource.setFrame(frame) return insetsState } private fun createRightNavigationBars(insets: Insets): InsetsState { val stableBounds = Rect() displayLayout.getStableBounds(stableBounds) val displayRect = Rect() displayRect.set(0, 0, displayLayout.width(), displayLayout.height()) val insetsState = InsetsState() val insetsSource = insetsState.getOrCreateSource( InsetsSource.createId( null, 0, Type.navigationBars()), Type.navigationBars() ) val frame = Rect(displayLayout.width() - insets.right, 0, displayLayout.width(), displayLayout.height()) insetsSource.setFrame(frame) return insetsState } private fun createIme(insets: Insets): InsetsState { val stableBounds = Rect() displayLayout.getStableBounds(stableBounds) val displayRect = Rect() displayRect.set(0, 0, displayLayout.width(), displayLayout.height()) val insetsState = InsetsState() val insetsSource = insetsState.getOrCreateSource(ID_IME, Type.ime()) insetsSource.setVisible(true) insetsSource.setFrame(stableBounds.left, stableBounds.bottom - insets.bottom, stableBounds.right, stableBounds.bottom) return insetsState } private fun createDisplayInfo(width: Int, height: Int, cutoutHeight: Int, rotation: Int): DisplayInfo { val info = DisplayInfo() info.logicalWidth = width info.logicalHeight = height info.rotation = rotation if (cutoutHeight > 0) { info.displayCutout = DisplayCutout( Insets.of(0, cutoutHeight, 0, 0) /* safeInsets */, null /* boundLeft */, Rect(width / 2 - cutoutHeight, 0, width / 2 + cutoutHeight, cutoutHeight) /* boundTop */, null /* boundRight */, null /* boundBottom */) } else { info.displayCutout = DisplayCutout.NO_CUTOUT } info.logicalDensityDpi = 300 return info } private fun createResources(navLand: Int, navPort: Int, navMoves: Boolean): Resources { val cfg = Configuration() cfg.uiMode = Configuration.UI_MODE_TYPE_NORMAL val res = Mockito.mock(Resources::class.java) Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize( R.dimen.navigation_bar_height_landscape_car_mode) Mockito.doReturn(navPort).whenever(res).getDimensionPixelSize( R.dimen.navigation_bar_height_car_mode) Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize( R.dimen.navigation_bar_width_car_mode) Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize( R.dimen.navigation_bar_height_landscape) Mockito.doReturn(navPort).whenever(res).getDimensionPixelSize( R.dimen.navigation_bar_height) Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize( R.dimen.navigation_bar_width) Mockito.doReturn(navMoves).whenever(res).getBoolean(R.bool.config_navBarCanMove) Mockito.doReturn(cfg).whenever(res).configuration return res } private class TestingNavigationBarsListener( displayController: DisplayController, displayId: Int ) : NavigationBarsListener(displayController, displayId) { var testInsets = Insets.NONE override fun onNavigationBarsVisibilityChanged(insets: Insets) { testInsets = insets } } companion object { private const val DEFAULT_DISPLAY_ID = 0 private const val DEFAULT_NAVIGATION_BARS_SIZE = 100 private const val DEFAULT_IME_HEIGHT = 500 } } No newline at end of file