Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +3 −1 Original line number Diff line number Diff line Loading @@ -1811,6 +1811,7 @@ public abstract class WMShellModule { Optional<DesktopUserRepositories> desktopUserRepositories, FocusTransitionObserver focusTransitionObserver, DisplayImeController displayImeController, Optional<DesktopModeWindowDecorViewModel> desktopModeWindowDecorViewModel, DisplayController displayController, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions, Loading @@ -1825,7 +1826,8 @@ public abstract class WMShellModule { return Optional.of( new DesktopImeHandler(desktopTasksController.get(), desktopUserRepositories.get(), focusTransitionObserver, shellTaskOrganizer, displayImeController, displayController, transitions, mainExecutor, displayImeController, desktopModeWindowDecorViewModel, displayController, transitions, mainExecutor, animExecutor, context, shellInit)); } Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImeHandler.kt +28 −3 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ import com.android.wm.shell.shared.animation.WindowAnimator import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel import java.util.Optional /** Handles the interactions between IME and desktop tasks */ class DesktopImeHandler( Loading @@ -48,13 +50,17 @@ class DesktopImeHandler( private val focusTransitionObserver: FocusTransitionObserver, private val shellTaskOrganizer: ShellTaskOrganizer, private val displayImeController: DisplayImeController, private val desktopModeWindowDecorViewModel: Optional<DesktopModeWindowDecorViewModel>, private val displayController: DisplayController, private val transitions: Transitions, private val mainExecutor: ShellExecutor, private val animExecutor: ShellExecutor, private val context: Context, shellInit: ShellInit, ) : DisplayImeController.ImePositionProcessor, Transitions.TransitionHandler { ) : DisplayImeController.ImePositionProcessor, Transitions.TransitionHandler, DesktopModeWindowDecorViewModel.CaptionTouchStatusListener { init { shellInit.addInitCallback(::onInit, this) Loading @@ -63,11 +69,16 @@ class DesktopImeHandler( private fun onInit() { if (DesktopExperienceFlags.ENABLE_DESKTOP_IME_BUGFIX.isTrue()) { displayImeController.addPositionProcessor(this) desktopModeWindowDecorViewModel.ifPresent { it -> it.registerCaptionTouchStatusListener(this) } } } private val taskToImeTarget = mutableMapOf<Int, ImeTarget>() private var imeTriggeredTransition: IBinder? = null /** Responsible for tracking whether app header (not app handle) is pressed. */ private var isCaptionPressed: Boolean = false data class ImeTarget( var topTask: ActivityManager.RunningTaskInfo, Loading @@ -87,9 +98,15 @@ class DesktopImeHandler( return IME_ANIMATION_DEFAULT } if (showing) { // Only get the top task when the IME will be showing. Otherwise just restore // previously manipulated task. if (showing) { // If user is still pressing the caption, we shouldn't move the task. if (isCaptionPressed) { logD("Caption is pressed, do not move task with IME") return IME_ANIMATION_DEFAULT } val currentTopTask = getFocusedTask(displayId) ?: return IME_ANIMATION_DEFAULT if (!userRepositories.current.isActiveTask(currentTopTask.taskId)) return IME_ANIMATION_DEFAULT Loading Loading @@ -265,6 +282,14 @@ class DesktopImeHandler( ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } override fun onCaptionPressed() { isCaptionPressed = true } override fun onCaptionReleased() { isCaptionPressed = false } private companion object { private const val TAG = "DesktopImeHandler" // NOTE: All these constants came from InsetsController. Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +30 −0 Original line number Diff line number Diff line Loading @@ -267,6 +267,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private final LatencyTracker mLatencyTracker; private final CompatUIHandler mCompatUI; private final UserProfileContexts mUserProfileContexts; private CaptionTouchStatusListener mCaptionTouchStatusListener; public DesktopModeWindowDecorViewModel( Context context, Loading Loading @@ -1055,6 +1056,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mDesktopTilingDecorViewModel.onDeskRemoved(deskId); } /** Registers a {@link CaptionTouchStatusListener}. */ public void registerCaptionTouchStatusListener(CaptionTouchStatusListener l) { mCaptionTouchStatusListener = l; } /** Listener for caption touch events. */ public interface CaptionTouchStatusListener { /** Called when the caption is pressed. */ void onCaptionPressed(); /** Called when the caption is released. */ void onCaptionReleased(); } @VisibleForTesting public class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener, Loading Loading @@ -1395,6 +1408,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private boolean handleFreeformMotionEvent(WindowDecorationWrapper decoration, RunningTaskInfo taskInfo, View v, MotionEvent e) { updateTouchStatus(e); final int id = v.getId(); if (mGestureDetector.onTouchEvent(e)) { return true; Loading Loading @@ -1543,6 +1557,22 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } } private void updateTouchStatus(MotionEvent e) { if (mCaptionTouchStatusListener == null) { return; } switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: mCaptionTouchStatusListener.onCaptionPressed(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { mCaptionTouchStatusListener.onCaptionReleased(); break; } } } /** * Perform a task size toggle on release of the double-tap, assuming no drag event * was handled during the double-tap. Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImeHandlerTest.kt +67 −0 Original line number Diff line number Diff line Loading @@ -40,7 +40,9 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlin.test.Test import org.junit.Before import org.mockito.ArgumentCaptor Loading Loading @@ -69,6 +71,7 @@ class DesktopImeHandlerTest : ShellTestCase() { private val focusTransitionObserver = mock<FocusTransitionObserver>() private val desktopUserRepositories = mock<DesktopUserRepositories>() private val tasksRepository = mock<DesktopRepository>() private val desktopModeWindowDecorViewModel = mock<DesktopModeWindowDecorViewModel>() private lateinit var imeHandler: DesktopImeHandler private lateinit var shellInit: ShellInit Loading @@ -89,6 +92,7 @@ class DesktopImeHandlerTest : ShellTestCase() { focusTransitionObserver, shellTaskOrganizer, displayImeController, Optional.of(desktopModeWindowDecorViewModel), displayController, transitions, mainExecutor = mock(), Loading Loading @@ -290,6 +294,69 @@ class DesktopImeHandlerTest : ShellTestCase() { verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), wct.capture(), anyOrNull()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_IME_BUGFIX) @DisableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS) fun onImeStartPositioning_captionPressed_noOp() { setUpLandscapeDisplay() val wct = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) val taskBounds = Rect(0, 400, 500, 1600) var freeformTask = createFreeformTask(DEFAULT_DISPLAY, taskBounds) freeformTask.isFocused = true whenever(shellTaskOrganizer.getRunningTasks(any())).thenReturn(arrayListOf(freeformTask)) whenever(shellTaskOrganizer.getRunningTaskInfo(freeformTask.taskId)) .thenReturn(freeformTask) imeHandler.onCaptionPressed() imeHandler.onImeStartPositioning( DEFAULT_DISPLAY, hiddenTop = DISPLAY_DIMENSION_SHORT, shownTop = IME_HEIGHT, showing = true, isFloating = false, t = mock(), ) // No transition is started because the IME is floating verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), wct.capture(), anyOrNull()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_IME_BUGFIX) @DisableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS) fun onImeStartPositioning_captionPressedAndReleased_movesTask() { setUpLandscapeDisplay() val wct = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) val taskBounds = Rect(0, 400, 500, 1600) val expectedBounds = Rect( taskBounds.left, STATUS_BAR_HEIGHT, taskBounds.right, STATUS_BAR_HEIGHT + taskBounds.height(), ) var freeformTask = createFreeformTask(DEFAULT_DISPLAY, taskBounds) freeformTask.isFocused = true whenever(shellTaskOrganizer.getRunningTasks(any())).thenReturn(arrayListOf(freeformTask)) whenever(shellTaskOrganizer.getRunningTaskInfo(freeformTask.taskId)) .thenReturn(freeformTask) imeHandler.onCaptionPressed() imeHandler.onCaptionReleased() imeHandler.onImeStartPositioning( DEFAULT_DISPLAY, hiddenTop = DISPLAY_DIMENSION_SHORT, shownTop = IME_HEIGHT, showing = true, isFloating = false, t = mock(), ) // Moves the task up to the top of stable bounds verify(transitions).startTransition(eq(TRANSIT_CHANGE), wct.capture(), anyOrNull()) assertThat(findBoundsChange(wct.value, freeformTask)).isEqualTo(expectedBounds) } @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_IME_BUGFIX, Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +3 −1 Original line number Diff line number Diff line Loading @@ -1811,6 +1811,7 @@ public abstract class WMShellModule { Optional<DesktopUserRepositories> desktopUserRepositories, FocusTransitionObserver focusTransitionObserver, DisplayImeController displayImeController, Optional<DesktopModeWindowDecorViewModel> desktopModeWindowDecorViewModel, DisplayController displayController, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions, Loading @@ -1825,7 +1826,8 @@ public abstract class WMShellModule { return Optional.of( new DesktopImeHandler(desktopTasksController.get(), desktopUserRepositories.get(), focusTransitionObserver, shellTaskOrganizer, displayImeController, displayController, transitions, mainExecutor, displayImeController, desktopModeWindowDecorViewModel, displayController, transitions, mainExecutor, animExecutor, context, shellInit)); } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImeHandler.kt +28 −3 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ import com.android.wm.shell.shared.animation.WindowAnimator import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel import java.util.Optional /** Handles the interactions between IME and desktop tasks */ class DesktopImeHandler( Loading @@ -48,13 +50,17 @@ class DesktopImeHandler( private val focusTransitionObserver: FocusTransitionObserver, private val shellTaskOrganizer: ShellTaskOrganizer, private val displayImeController: DisplayImeController, private val desktopModeWindowDecorViewModel: Optional<DesktopModeWindowDecorViewModel>, private val displayController: DisplayController, private val transitions: Transitions, private val mainExecutor: ShellExecutor, private val animExecutor: ShellExecutor, private val context: Context, shellInit: ShellInit, ) : DisplayImeController.ImePositionProcessor, Transitions.TransitionHandler { ) : DisplayImeController.ImePositionProcessor, Transitions.TransitionHandler, DesktopModeWindowDecorViewModel.CaptionTouchStatusListener { init { shellInit.addInitCallback(::onInit, this) Loading @@ -63,11 +69,16 @@ class DesktopImeHandler( private fun onInit() { if (DesktopExperienceFlags.ENABLE_DESKTOP_IME_BUGFIX.isTrue()) { displayImeController.addPositionProcessor(this) desktopModeWindowDecorViewModel.ifPresent { it -> it.registerCaptionTouchStatusListener(this) } } } private val taskToImeTarget = mutableMapOf<Int, ImeTarget>() private var imeTriggeredTransition: IBinder? = null /** Responsible for tracking whether app header (not app handle) is pressed. */ private var isCaptionPressed: Boolean = false data class ImeTarget( var topTask: ActivityManager.RunningTaskInfo, Loading @@ -87,9 +98,15 @@ class DesktopImeHandler( return IME_ANIMATION_DEFAULT } if (showing) { // Only get the top task when the IME will be showing. Otherwise just restore // previously manipulated task. if (showing) { // If user is still pressing the caption, we shouldn't move the task. if (isCaptionPressed) { logD("Caption is pressed, do not move task with IME") return IME_ANIMATION_DEFAULT } val currentTopTask = getFocusedTask(displayId) ?: return IME_ANIMATION_DEFAULT if (!userRepositories.current.isActiveTask(currentTopTask.taskId)) return IME_ANIMATION_DEFAULT Loading Loading @@ -265,6 +282,14 @@ class DesktopImeHandler( ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } override fun onCaptionPressed() { isCaptionPressed = true } override fun onCaptionReleased() { isCaptionPressed = false } private companion object { private const val TAG = "DesktopImeHandler" // NOTE: All these constants came from InsetsController. Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +30 −0 Original line number Diff line number Diff line Loading @@ -267,6 +267,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private final LatencyTracker mLatencyTracker; private final CompatUIHandler mCompatUI; private final UserProfileContexts mUserProfileContexts; private CaptionTouchStatusListener mCaptionTouchStatusListener; public DesktopModeWindowDecorViewModel( Context context, Loading Loading @@ -1055,6 +1056,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mDesktopTilingDecorViewModel.onDeskRemoved(deskId); } /** Registers a {@link CaptionTouchStatusListener}. */ public void registerCaptionTouchStatusListener(CaptionTouchStatusListener l) { mCaptionTouchStatusListener = l; } /** Listener for caption touch events. */ public interface CaptionTouchStatusListener { /** Called when the caption is pressed. */ void onCaptionPressed(); /** Called when the caption is released. */ void onCaptionReleased(); } @VisibleForTesting public class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener, Loading Loading @@ -1395,6 +1408,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private boolean handleFreeformMotionEvent(WindowDecorationWrapper decoration, RunningTaskInfo taskInfo, View v, MotionEvent e) { updateTouchStatus(e); final int id = v.getId(); if (mGestureDetector.onTouchEvent(e)) { return true; Loading Loading @@ -1543,6 +1557,22 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } } private void updateTouchStatus(MotionEvent e) { if (mCaptionTouchStatusListener == null) { return; } switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: mCaptionTouchStatusListener.onCaptionPressed(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { mCaptionTouchStatusListener.onCaptionReleased(); break; } } } /** * Perform a task size toggle on release of the double-tap, assuming no drag event * was handled during the double-tap. Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImeHandlerTest.kt +67 −0 Original line number Diff line number Diff line Loading @@ -40,7 +40,9 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlin.test.Test import org.junit.Before import org.mockito.ArgumentCaptor Loading Loading @@ -69,6 +71,7 @@ class DesktopImeHandlerTest : ShellTestCase() { private val focusTransitionObserver = mock<FocusTransitionObserver>() private val desktopUserRepositories = mock<DesktopUserRepositories>() private val tasksRepository = mock<DesktopRepository>() private val desktopModeWindowDecorViewModel = mock<DesktopModeWindowDecorViewModel>() private lateinit var imeHandler: DesktopImeHandler private lateinit var shellInit: ShellInit Loading @@ -89,6 +92,7 @@ class DesktopImeHandlerTest : ShellTestCase() { focusTransitionObserver, shellTaskOrganizer, displayImeController, Optional.of(desktopModeWindowDecorViewModel), displayController, transitions, mainExecutor = mock(), Loading Loading @@ -290,6 +294,69 @@ class DesktopImeHandlerTest : ShellTestCase() { verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), wct.capture(), anyOrNull()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_IME_BUGFIX) @DisableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS) fun onImeStartPositioning_captionPressed_noOp() { setUpLandscapeDisplay() val wct = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) val taskBounds = Rect(0, 400, 500, 1600) var freeformTask = createFreeformTask(DEFAULT_DISPLAY, taskBounds) freeformTask.isFocused = true whenever(shellTaskOrganizer.getRunningTasks(any())).thenReturn(arrayListOf(freeformTask)) whenever(shellTaskOrganizer.getRunningTaskInfo(freeformTask.taskId)) .thenReturn(freeformTask) imeHandler.onCaptionPressed() imeHandler.onImeStartPositioning( DEFAULT_DISPLAY, hiddenTop = DISPLAY_DIMENSION_SHORT, shownTop = IME_HEIGHT, showing = true, isFloating = false, t = mock(), ) // No transition is started because the IME is floating verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), wct.capture(), anyOrNull()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_IME_BUGFIX) @DisableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS) fun onImeStartPositioning_captionPressedAndReleased_movesTask() { setUpLandscapeDisplay() val wct = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) val taskBounds = Rect(0, 400, 500, 1600) val expectedBounds = Rect( taskBounds.left, STATUS_BAR_HEIGHT, taskBounds.right, STATUS_BAR_HEIGHT + taskBounds.height(), ) var freeformTask = createFreeformTask(DEFAULT_DISPLAY, taskBounds) freeformTask.isFocused = true whenever(shellTaskOrganizer.getRunningTasks(any())).thenReturn(arrayListOf(freeformTask)) whenever(shellTaskOrganizer.getRunningTaskInfo(freeformTask.taskId)) .thenReturn(freeformTask) imeHandler.onCaptionPressed() imeHandler.onCaptionReleased() imeHandler.onImeStartPositioning( DEFAULT_DISPLAY, hiddenTop = DISPLAY_DIMENSION_SHORT, shownTop = IME_HEIGHT, showing = true, isFloating = false, t = mock(), ) // Moves the task up to the top of stable bounds verify(transitions).startTransition(eq(TRANSIT_CHANGE), wct.capture(), anyOrNull()) assertThat(findBoundsChange(wct.value, freeformTask)).isEqualTo(expectedBounds) } @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_IME_BUGFIX, Loading