Integrate DisplayLayout for One handed mode

1) Handle rotation
2) Initialize DisplayLayout in dagger and update when
   onDisplayAdded() & onDisplayRemoved()
3) Refact OneHandedTouchHandler
4) Refact OneHandedBackgroundPanelOrganizer
5) Refact OneHandedDisplayAreaOrganizer

Test: atest WMShellUnitTests
Bug:182899148
Bug:182633327
Bug:183172404
Bug:183489178
Bug:183559944
Bug:183065937
Bug:183005910
Bug:183063280
Bug:183162084
Bug:181423645
Bug:177497107
Change-Id: Ie7a4fa97df5f6a3963a55bd507d36e026e971bab
This commit is contained in:
Bill Lin
2021-03-17 20:11:17 +08:00
parent 17ea6fcab5
commit 52a3bb48ca
10 changed files with 445 additions and 231 deletions

View File

@@ -23,7 +23,6 @@ import android.graphics.Rect;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
@@ -34,8 +33,9 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.annotations.GuardedBy;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.Executor;
@@ -52,12 +52,15 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final float[] mColor;
private final float mAlpha;
private final Rect mRect;
private final Executor mMainExecutor;
private final Rect mDisplaySize;
private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
/**
* The background to distinguish the boundary of translated windows and empty region when
* one handed mode triggered.
*/
private Rect mBkgBounds;
@VisibleForTesting
@GuardedBy("mLock")
boolean mIsShowing;
@@ -82,15 +85,19 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
mMainExecutor.execute(() -> removeBackgroundPanelLayer());
}
public OneHandedBackgroundPanelOrganizer(Context context, WindowManager windowManager,
DisplayController displayController, Executor executor) {
public OneHandedBackgroundPanelOrganizer(Context context, DisplayLayout displayLayout,
Executor executor) {
super(executor);
mDisplaySize = windowManager.getCurrentWindowMetrics().getBounds();
final Resources res = context.getResources();
final float defaultRGB = res.getFloat(R.dimen.config_one_handed_background_rgb);
mColor = new float[]{defaultRGB, defaultRGB, defaultRGB};
mAlpha = res.getFloat(R.dimen.config_one_handed_background_alpha);
mRect = new Rect(0, 0, mDisplaySize.width(), mDisplaySize.height());
// Ensure the mBkgBounds is portrait, due to OHM only support on portrait
if (displayLayout.height() > displayLayout.width()) {
mBkgBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height());
} else {
mBkgBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width());
}
mMainExecutor = executor;
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
}
@@ -144,6 +151,7 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
if (mBackgroundSurface == null) {
mBackgroundSurface = new SurfaceControl.Builder(mSurfaceSession)
.setParent(mParentLeash)
.setBufferSize(mBkgBounds.width(), mBkgBounds.height())
.setColorLayer()
.setFormat(PixelFormat.RGBA_8888)
.setOpaque(false)
@@ -188,11 +196,19 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
SurfaceControl.Transaction transaction =
mSurfaceControlTransactionFactory.getTransaction();
transaction.remove(mBackgroundSurface);
transaction.apply();
transaction.remove(mBackgroundSurface).apply();
transaction.close();
mBackgroundSurface = null;
mIsShowing = false;
}
}
void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
pw.println(TAG + "states: ");
pw.print(innerPrefix + "mIsShowing=");
pw.println(mIsShowing);
pw.print(innerPrefix + "mBkgBounds=");
pw.println(mBkgBounds);
}
}

View File

@@ -17,10 +17,10 @@
package com.android.wm.shell.onehanded;
import static android.os.UserHandle.USER_CURRENT;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import android.Manifest;
import android.annotation.BinderThread;
import android.content.ComponentName;
import android.content.Context;
@@ -28,13 +28,13 @@ import android.content.om.IOverlayManager;
import android.content.om.OverlayInfo;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Slog;
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -47,7 +47,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ExecutorUtils;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
@@ -76,9 +76,10 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
private boolean mLockedDisabled;
private float mOffSetFraction;
private final Context mContext;
private Context mContext;
private final AccessibilityManager mAccessibilityManager;
private final DisplayController mDisplayController;
private final OneHandedGestureHandler mGestureHandler;
private final OneHandedSettingsUtil mOneHandedSettingsUtil;
private final OneHandedTimeoutHandler mTimeoutHandler;
private final OneHandedTouchHandler mTouchHandler;
@@ -89,10 +90,9 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
private final OneHandedImpl mImpl = new OneHandedImpl();
private final WindowManager mWindowManager;
private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
private final AccessibilityManager mAccessibilityManager;
private OneHandedGestureHandler mGestureHandler;
private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
/**
@@ -100,8 +100,29 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
*/
private final DisplayChangeController.OnDisplayChangingListener mRotationController =
(display, fromRotation, toRotation, wct) -> {
if (mDisplayAreaOrganizer != null) {
mDisplayAreaOrganizer.onRotateDisplay(fromRotation, toRotation, wct);
if (!isInitialized()) {
return;
}
mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation, wct);
mGestureHandler.onRotateDisplay(mDisplayAreaOrganizer.getDisplayLayout());
};
private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
new DisplayController.OnDisplaysChangedListener() {
@Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
if (displayId != DEFAULT_DISPLAY || !isInitialized()) {
return;
}
updateDisplayLayout(displayId);
}
@Override
public void onDisplayAdded(int displayId) {
if (displayId != DEFAULT_DISPLAY || !isInitialized()) {
return;
}
updateDisplayLayout(displayId);
}
};
@@ -115,8 +136,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
new AccessibilityManager.AccessibilityStateChangeListener() {
@Override
public void onAccessibilityStateChanged(boolean enabled) {
if (mOneHandedSettingsUtil == null) {
Slog.w(TAG, "mOneHandedSettingsUtil may not instantiate yet");
if (!isInitialized()) {
return;
}
if (enabled) {
@@ -147,6 +167,14 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
}
};
private boolean isInitialized() {
if (mDisplayAreaOrganizer == null || mDisplayController == null
|| mGestureHandler == null || mOneHandedSettingsUtil == null) {
Slog.w(TAG, "Components may not initialized yet!");
return false;
}
return true;
}
/**
* Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
@@ -154,8 +182,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
@Nullable
public static OneHandedController create(
Context context, WindowManager windowManager, DisplayController displayController,
TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger,
ShellExecutor mainExecutor, Handler mainHandler) {
DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
UiEventLogger uiEventLogger, ShellExecutor mainExecutor, Handler mainHandler) {
if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
Slog.w(TAG, "Device doesn't support OneHanded feature");
return null;
@@ -169,19 +197,17 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler,
mainExecutor);
OneHandedGestureHandler gestureHandler = new OneHandedGestureHandler(
context, windowManager, displayController, ViewConfiguration.get(context),
mainExecutor);
context, displayLayout, ViewConfiguration.get(context), mainExecutor);
OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer =
new OneHandedBackgroundPanelOrganizer(context, windowManager, displayController,
mainExecutor);
new OneHandedBackgroundPanelOrganizer(context, displayLayout, mainExecutor);
OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
context, windowManager, animationController, tutorialHandler,
context, displayLayout, animationController, tutorialHandler,
oneHandedBackgroundPanelOrganizer, mainExecutor);
OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
IOverlayManager overlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
return new OneHandedController(context, windowManager, displayController,
return new OneHandedController(context, displayController,
oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
gestureHandler, settingsUtil, timeoutHandler, oneHandedUiEventsLogger,
overlayManager, taskStackListener, mainExecutor, mainHandler);
@@ -189,7 +215,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
@VisibleForTesting
OneHandedController(Context context,
WindowManager windowManager,
DisplayController displayController,
OneHandedBackgroundPanelOrganizer backgroundPanelOrganizer,
OneHandedDisplayAreaOrganizer displayAreaOrganizer,
@@ -205,7 +230,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
Handler mainHandler) {
mContext = context;
mOneHandedSettingsUtil = settingsUtil;
mWindowManager = windowManager;
mBackgroundPanelOrganizer = backgroundPanelOrganizer;
mDisplayAreaOrganizer = displayAreaOrganizer;
mDisplayController = displayController;
@@ -218,6 +242,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
mOneHandedUiEventLogger = uiEventsLogger;
mTaskStackListener = taskStackListener;
mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
final float offsetPercentageConfig = context.getResources().getFraction(
R.fraction.config_one_handed_offset, 1, 1);
final int sysPropPercentageConfig = SystemProperties.getInt(
@@ -297,8 +322,14 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
Slog.d(TAG, "Temporary lock disabled");
return;
}
final int currentRotation = mDisplayAreaOrganizer.getDisplayLayout().rotation();
if (currentRotation != Surface.ROTATION_0 && currentRotation != Surface.ROTATION_180) {
Slog.w(TAG, "One handed mode only support portrait mode");
return;
}
if (!mDisplayAreaOrganizer.isInOneHanded()) {
final int yOffSet = Math.round(getDisplaySize().height() * mOffSetFraction);
final int yOffSet = Math.round(
mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction);
mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
mTimeoutHandler.resetTimer();
@@ -371,6 +402,12 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
.getSettingsSwipeToNotificationEnabled(mContext.getContentResolver()));
}
private void updateDisplayLayout(int displayId) {
mDisplayAreaOrganizer.setDisplayLayout(
mDisplayController.getDisplayLayout(displayId));
mGestureHandler.onDisplayChanged(mDisplayAreaOrganizer.getDisplayLayout());
}
private ContentObserver getObserver(Runnable onChangeRunnable) {
return new ContentObserver(mMainHandler) {
@Override
@@ -454,24 +491,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_TIMEOUT_OUT));
}
/**
* Query the current display real size from {@link WindowManager}
*
* @return {@link WindowManager#getCurrentWindowMetrics()#getBounds()}
*/
private Rect getDisplaySize() {
if (mWindowManager == null) {
Slog.e(TAG, "WindowManager instance is null! Can not get display size!");
return new Rect();
}
final Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds();
if (displaySize.width() == 0 || displaySize.height() == 0) {
Slog.e(TAG, "Display size error! width = " + displaySize.width()
+ ", height = " + displaySize.height());
}
return displaySize;
}
@VisibleForTesting
boolean isLockedDisabled() {
return mLockedDisabled;
@@ -483,7 +502,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
}
mTouchHandler.onOneHandedEnabled(mIsOneHandedEnabled);
mGestureHandler.onOneHandedEnabled(mIsOneHandedEnabled || mIsSwipeToNotificationEnabled);
mGestureHandler.onGestureEnabled(mIsOneHandedEnabled || mIsSwipeToNotificationEnabled);
if (!mIsOneHandedEnabled) {
mDisplayAreaOrganizer.unregisterOrganizer();
@@ -532,10 +551,15 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
@VisibleForTesting
void setLockedDisabled(boolean locked, boolean enabled) {
if (enabled == mIsOneHandedEnabled) {
final boolean isFeatureEnabled = mIsOneHandedEnabled || mIsSwipeToNotificationEnabled;
if (enabled == isFeatureEnabled) {
return;
}
mLockedDisabled = locked && !enabled;
// Disabled gesture when keyguard ON
mGestureHandler.onGestureEnabled(!mLockedDisabled && isFeatureEnabled);
}
private void onConfigChanged(Configuration newConfig) {
@@ -556,6 +580,10 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
pw.print(innerPrefix + "mLockedDisabled=");
pw.println(mLockedDisabled);
if (mBackgroundPanelOrganizer != null) {
mBackgroundPanelOrganizer.dump(pw);
}
if (mDisplayAreaOrganizer != null) {
mDisplayAreaOrganizer.dump(pw);
}

View File

@@ -23,9 +23,7 @@ import android.content.Context;
import android.graphics.Rect;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
@@ -37,6 +35,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import java.io.PrintWriter;
@@ -58,7 +57,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
private static final String ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION =
"persist.debug.one_handed_translate_animation_duration";
private final WindowManager mWindowManager;
private DisplayLayout mDisplayLayout = new DisplayLayout();
private final Rect mLastVisualDisplayBounds = new Rect();
private final Rect mDefaultDisplayBounds = new Rect();
@@ -108,15 +108,15 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
* Constructor of OneHandedDisplayAreaOrganizer
*/
public OneHandedDisplayAreaOrganizer(Context context,
WindowManager windowManager,
DisplayLayout displayLayout,
OneHandedAnimationController animationController,
OneHandedTutorialHandler tutorialHandler,
OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer,
ShellExecutor mainExecutor) {
super(mainExecutor);
mWindowManager = windowManager;
mDisplayLayout.set(displayLayout);
updateDisplayBounds();
mAnimationController = animationController;
mLastVisualDisplayBounds.set(getDisplayBounds());
final int animationDurationConfig = context.getResources().getInteger(
R.integer.config_one_handed_translate_animation_duration);
mEnterExitAnimationDurationMs =
@@ -146,7 +146,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
}
mDefaultDisplayBounds.set(getDisplayBounds());
updateDisplayBounds();
return displayAreaInfos;
}
@@ -157,29 +157,21 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
}
/**
* Handler for display rotation changes by below policy which
* handles 90 degree display rotation changes {@link Surface.Rotation}.
* Handler for display rotation changes by {@link DisplayLayout}
*
* @param fromRotation starting rotation of the display.
* @param toRotation target rotation of the display (after rotating).
* @param wct A task transaction {@link WindowContainerTransaction} from
* {@link DisplayChangeController} to populate.
* @param context Any context
* @param toRotation target rotation of the display (after rotating).
* @param wct A task transaction {@link WindowContainerTransaction} from
* {@link DisplayChangeController} to populate.
*/
public void onRotateDisplay(int fromRotation, int toRotation, WindowContainerTransaction wct) {
// Stop one handed without animation and reset cropped size immediately
final Rect newBounds = new Rect(getDisplayBounds());
// This diff rule will only filter the cases portrait <-> landscape
final boolean isOrientationDiff = Math.abs(fromRotation - toRotation) % 2 == 1;
if (isOrientationDiff) {
// getDisplayBounds() will return window metrics bounds which dose not update to
// corresponding display orientation yet, we have to manual rotate bounds
newBounds.set(0, 0, newBounds.bottom, newBounds.right);
resetWindowsOffset(wct);
mDefaultDisplayBounds.set(newBounds);
mLastVisualDisplayBounds.set(newBounds);
finishOffset(0, TRANSITION_DIRECTION_EXIT);
public void onRotateDisplay(Context context, int toRotation, WindowContainerTransaction wct) {
if (mDisplayLayout.rotation() == toRotation) {
return;
}
mDisplayLayout.rotateTo(context.getResources(), toRotation);
resetWindowsOffset(wct);
updateDisplayBounds();
finishOffset(0, TRANSITION_DIRECTION_EXIT);
}
/**
@@ -191,9 +183,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
mDefaultDisplayBounds.top + yOffset,
mDefaultDisplayBounds.right,
mDefaultDisplayBounds.bottom + yOffset);
final Rect fromBounds = getLastVisualDisplayBounds() != null
? getLastVisualDisplayBounds()
: mDefaultDisplayBounds;
final Rect fromBounds = getLastVisualDisplayBounds();
final int direction = yOffset > 0
? TRANSITION_DIRECTION_TRIGGER
: TRANSITION_DIRECTION_EXIT;
@@ -219,7 +209,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
applyTransaction(wct);
}
private void resetWindowsOffset(WindowContainerTransaction wct) {
@VisibleForTesting
void resetWindowsOffset(WindowContainerTransaction wct) {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
mDisplayAreaTokenMap.forEach(
@@ -292,19 +283,19 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
return mLastVisualDisplayBounds;
}
@Nullable
@VisibleForTesting
Rect getDisplayBounds() {
if (mWindowManager == null) {
Slog.e(TAG, "WindowManager instance is null! Can not get display size!");
return new Rect();
}
final Rect displayBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
if (displayBounds.width() == 0 || displayBounds.height() == 0) {
Slog.e(TAG, "Display size error! width = " + displayBounds.width()
+ ", height = " + displayBounds.height());
}
return displayBounds;
@Nullable
Rect getLastDisplayBounds() {
return mLastVisualDisplayBounds;
}
public DisplayLayout getDisplayLayout() {
return mDisplayLayout;
}
@VisibleForTesting
void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
mDisplayLayout.set(displayLayout);
}
@VisibleForTesting
@@ -312,6 +303,11 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
return mDisplayAreaTokenMap;
}
void updateDisplayBounds() {
mDefaultDisplayBounds.set(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
mLastVisualDisplayBounds.set(mDefaultDisplayBounds);
}
/**
* Register transition callback
*/
@@ -324,13 +320,13 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
pw.println(TAG + "states: ");
pw.print(innerPrefix + "mIsInOneHanded=");
pw.println(mIsInOneHanded);
pw.print(innerPrefix + "mDisplayLayout.rotation()=");
pw.println(mDisplayLayout.rotation());
pw.print(innerPrefix + "mDisplayAreaTokenMap=");
pw.println(mDisplayAreaTokenMap);
pw.print(innerPrefix + "mDefaultDisplayBounds=");
pw.println(mDefaultDisplayBounds);
pw.print(innerPrefix + "mLastVisualDisplayBounds=");
pw.println(mLastVisualDisplayBounds);
pw.print(innerPrefix + "getDisplayBounds()=");
pw.println(getDisplayBounds());
}
}

View File

@@ -24,7 +24,6 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Looper;
import android.util.Log;
import android.view.Display;
import android.view.InputChannel;
import android.view.InputEvent;
@@ -33,29 +32,23 @@ import android.view.InputMonitor;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import java.io.PrintWriter;
/**
* The class manage swipe up and down gesture for 3-Button mode navigation,
* others(e.g, 2-button, full gesture mode) are handled by Launcher quick steps.
* TODO(b/160934654) Migrate to Launcher quick steps
* The class manage swipe up and down gesture for 3-Button mode navigation, others(e.g, 2-button,
* full gesture mode) are handled by Launcher quick steps. TODO(b/160934654) Migrate to Launcher
* quick steps
*/
public class OneHandedGestureHandler implements OneHandedTransitionCallback,
DisplayChangeController.OnDisplayChangingListener {
public class OneHandedGestureHandler implements OneHandedTransitionCallback {
private static final String TAG = "OneHandedGestureHandler";
private static final boolean DEBUG_GESTURE = false;
private static final int ANGLE_MAX = 150;
private static final int ANGLE_MIN = 30;
@@ -64,14 +57,13 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
private final PointF mStartDragPos = new PointF();
private final WindowManager mWindowManager;
private boolean mPassedSlop;
private boolean mAllowGesture;
private boolean mIsEnabled;
private int mNavGestureHeight;
private boolean mIsThreeButtonModeEnabled;
private int mRotation = Surface.ROTATION_0;
private int mRotation;
@VisibleForTesting
InputMonitor mInputMonitor;
@@ -85,37 +77,35 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
private boolean mIsStopGesture;
/**
* Constructor of OneHandedGestureHandler, we only handle the gesture of
* {@link Display#DEFAULT_DISPLAY}
* Constructor of OneHandedGestureHandler, we only handle the gesture of {@link
* Display#DEFAULT_DISPLAY}
*
* @param context {@link Context}
* @param displayController {@link DisplayController}
* @param context Any context
* @param displayLayout Current {@link DisplayLayout} from controller
* @param viewConfig {@link ViewConfiguration} to obtain touch slop
* @param mainExecutor The wm-shell main executor
*/
public OneHandedGestureHandler(Context context, WindowManager windowManager,
DisplayController displayController, ViewConfiguration viewConfig,
public OneHandedGestureHandler(Context context,
DisplayLayout displayLayout,
ViewConfiguration viewConfig,
ShellExecutor mainExecutor) {
mWindowManager = windowManager;
mMainExecutor = mainExecutor;
displayController.addDisplayChangingController(this);
mNavGestureHeight = getNavBarSize(context,
displayController.getDisplayLayout(DEFAULT_DISPLAY));
mDragDistThreshold = context.getResources().getDimensionPixelSize(
R.dimen.gestures_onehanded_drag_threshold);
final float slop = viewConfig.getScaledTouchSlop();
mSquaredSlop = slop * slop;
onDisplayChanged(displayLayout);
updateIsEnabled();
}
/**
* Notified by {@link OneHandedController}, when user update settings of Enabled or Disabled
* Notifies by {@link OneHandedController}, when swipe down gesture is enabled on 3 button
* navigation bar mode.
*
* @param isEnabled is one handed settings enabled or not
* @param isEnabled Either one handed mode or swipe for notification function enabled or not
*/
public void onOneHandedEnabled(boolean isEnabled) {
if (DEBUG_GESTURE) {
Log.d(TAG, "onOneHandedEnabled, isEnabled = " + isEnabled);
}
public void onGestureEnabled(boolean isEnabled) {
mIsEnabled = isEnabled;
updateIsEnabled();
}
@@ -126,25 +116,31 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
}
/**
* Register {@link OneHandedGestureEventCallback} to receive onStart(), onStop() callback
* Registers {@link OneHandedGestureEventCallback} to receive onStart(), onStop() callback
*/
public void setGestureEventListener(OneHandedGestureEventCallback callback) {
mGestureEventCallback = callback;
}
/**
* Called when onDisplayAdded() or onDisplayRemoved() callback
* @param displayLayout The latest {@link DisplayLayout} representing current displayId
*/
public void onDisplayChanged(DisplayLayout displayLayout) {
mNavGestureHeight = getNavBarSize(displayLayout);
mGestureRegion.set(0, displayLayout.height() - mNavGestureHeight, displayLayout.width(),
displayLayout.height());
mRotation = displayLayout.rotation();
}
private void onMotionEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
mAllowGesture = isWithinTouchRegion(ev.getX(), ev.getY())
&& mRotation == Surface.ROTATION_0;
mAllowGesture = isWithinTouchRegion(ev.getX(), ev.getY()) && isGestureAvailable();
if (mAllowGesture) {
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
}
if (DEBUG_GESTURE) {
Log.d(TAG, "ACTION_DOWN, mDownPos=" + mDownPos + ", mAllowGesture="
+ mAllowGesture);
}
} else if (mAllowGesture) {
switch (action) {
case MotionEvent.ACTION_MOVE:
@@ -204,34 +200,17 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
}
private boolean isWithinTouchRegion(float x, float y) {
if (DEBUG_GESTURE) {
Log.d(TAG, "isWithinTouchRegion(), mGestureRegion=" + mGestureRegion + ", downX=" + x
+ ", downY=" + y);
}
return mGestureRegion.contains(Math.round(x), Math.round(y));
}
private int getNavBarSize(Context context, @Nullable DisplayLayout displayLayout) {
if (displayLayout != null) {
return displayLayout.navBarFrameHeight();
} else {
return isRotated()
? context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height_landscape)
: context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height);
}
private int getNavBarSize(@NonNull DisplayLayout displayLayout) {
return isGestureAvailable() ? displayLayout.navBarFrameHeight() : 0 /* In landscape */;
}
private void updateIsEnabled() {
disposeInputChannel();
// Either OHM or swipe notification shade can activate in portrait mode only
if (mIsEnabled && mIsThreeButtonModeEnabled && !isRotated()) {
final Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds();
// Register input event receiver to monitor the touch region of NavBar gesture height
mGestureRegion.set(0, displaySize.height() - mNavGestureHeight, displaySize.width(),
displaySize.height());
if (mIsEnabled && mIsThreeButtonModeEnabled && isGestureAvailable()) {
mInputMonitor = InputManager.getInstance().monitorGestureInput(
"onehanded-gesture-offset", DEFAULT_DISPLAY);
try {
@@ -251,10 +230,16 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
}
}
@Override
public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
WindowContainerTransaction t) {
mRotation = toRotation;
/**
* Handler for display rotation changes by {@link DisplayLayout}
*
* @param displayLayout The rotated displayLayout
*/
public void onRotateDisplay(DisplayLayout displayLayout) {
mRotation = displayLayout.rotation();
mNavGestureHeight = getNavBarSize(displayLayout);
mGestureRegion.set(0, displayLayout.height() - mNavGestureHeight, displayLayout.width(),
displayLayout.height());
updateIsEnabled();
}
@@ -270,8 +255,9 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
}
}
private boolean isRotated() {
return mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270;
private boolean isGestureAvailable() {
// Either OHM or swipe notification shade can activate in portrait mode only
return mRotation == Surface.ROTATION_0 || mRotation == Surface.ROTATION_180;
}
private boolean isValidStartAngle(float deltaX, float deltaY) {
@@ -291,14 +277,18 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
pw.println(TAG + "States: ");
pw.print(innerPrefix + "mAllowGesture=");
pw.println(mAllowGesture);
pw.print(innerPrefix + "mIsEnabled=");
pw.println(mIsEnabled);
pw.print(innerPrefix + "mGestureRegion=");
pw.println(mGestureRegion);
pw.print(innerPrefix + "mNavGestureHeight=");
pw.println(mNavGestureHeight);
pw.print(innerPrefix + "mIsThreeButtonModeEnabled=");
pw.println(mIsThreeButtonModeEnabled);
pw.print(innerPrefix + "isLandscape=");
pw.println(isRotated());
pw.print(innerPrefix + "mRotation=");
pw.println(mRotation);
}
/**

View File

@@ -22,6 +22,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -35,6 +36,7 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import org.junit.Before;
import org.junit.Test;
@@ -48,7 +50,8 @@ import org.mockito.MockitoAnnotations;
public class OneHandedBackgroundPanelOrganizerTest extends OneHandedTestCase {
private DisplayAreaInfo mDisplayAreaInfo;
private Display mDisplay;
private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
private DisplayLayout mDisplayLayout;
private OneHandedBackgroundPanelOrganizer mSpiedBackgroundPanelOrganizer;
private WindowContainerToken mToken;
private SurfaceControl mLeash;
private TestableLooper mTestableLooper;
@@ -65,37 +68,38 @@ public class OneHandedBackgroundPanelOrganizerTest extends OneHandedTestCase {
mToken = new WindowContainerToken(mMockRealToken);
mLeash = new SurfaceControl();
mDisplay = mContext.getDisplay();
mDisplayLayout = new DisplayLayout(mContext, mDisplay);
when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY,
FEATURE_ONE_HANDED_BACKGROUND_PANEL);
mBackgroundPanelOrganizer = new OneHandedBackgroundPanelOrganizer(mContext, mWindowManager,
mMockDisplayController, Runnable::run);
mSpiedBackgroundPanelOrganizer = spy(
new OneHandedBackgroundPanelOrganizer(mContext, mDisplayLayout, Runnable::run));
}
@Test
public void testOnDisplayAreaAppeared() {
mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
mTestableLooper.processAllMessages();
assertThat(mBackgroundPanelOrganizer.getBackgroundSurface()).isNotNull();
assertThat(mSpiedBackgroundPanelOrganizer.getBackgroundSurface()).isNotNull();
}
@Test
public void testShowBackgroundLayer() {
mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
mBackgroundPanelOrganizer.showBackgroundPanelLayer();
mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
mSpiedBackgroundPanelOrganizer.showBackgroundPanelLayer();
mTestableLooper.processAllMessages();
assertThat(mBackgroundPanelOrganizer.mIsShowing).isTrue();
assertThat(mSpiedBackgroundPanelOrganizer.mIsShowing).isTrue();
}
@Test
public void testRemoveBackgroundLayer() {
mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
mBackgroundPanelOrganizer.removeBackgroundPanelLayer();
mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
mSpiedBackgroundPanelOrganizer.removeBackgroundPanelLayer();
mTestableLooper.processAllMessages();
assertThat(mBackgroundPanelOrganizer.mIsShowing).isFalse();
assertThat(mSpiedBackgroundPanelOrganizer.mIsShowing).isFalse();
}
}

View File

@@ -23,20 +23,24 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.om.IOverlayManager;
import android.graphics.Rect;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.util.ArrayMap;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -50,6 +54,7 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
public class OneHandedControllerTest extends OneHandedTestCase {
Display mDisplay;
DisplayLayout mDisplayLayout;
OneHandedController mSpiedOneHandedController;
OneHandedTimeoutHandler mSpiedTimeoutHandler;
@@ -88,6 +93,7 @@ public class OneHandedControllerTest extends OneHandedTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
mDisplay = mContext.getDisplay();
mDisplayLayout = new DisplayLayout(mContext, mDisplay);
mSpiedTimeoutHandler = spy(new OneHandedTimeoutHandler(mMockShellMainExecutor));
when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
@@ -103,9 +109,12 @@ public class OneHandedControllerTest extends OneHandedTestCase {
when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any())).thenReturn(
mDefaultSwipeToNotificationEnabled);
when(mMockDisplayAreaOrganizer.getLastDisplayBounds()).thenReturn(
new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height()));
when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(mDisplayLayout);
mSpiedOneHandedController = spy(new OneHandedController(
mContext,
mWindowManager,
mMockDisplayController,
mMockBackgroundOrganizer,
mMockDisplayAreaOrganizer,
@@ -127,7 +136,7 @@ public class OneHandedControllerTest extends OneHandedTestCase {
final OneHandedAnimationController animationController = new OneHandedAnimationController(
mContext);
OneHandedDisplayAreaOrganizer displayAreaOrganizer = new OneHandedDisplayAreaOrganizer(
mContext, mWindowManager, animationController, mMockTutorialHandler,
mContext, mDisplayLayout, animationController, mMockTutorialHandler,
mMockBackgroundOrganizer, mMockShellMainExecutor);
assertThat(displayAreaOrganizer.isInOneHanded()).isFalse();
@@ -188,7 +197,7 @@ public class OneHandedControllerTest extends OneHandedTestCase {
mSpiedOneHandedController.setOneHandedEnabled(true);
verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
verify(mMockGestureHandler, atLeastOnce()).onGestureEnabled(anyBoolean());
}
@Test
@@ -196,7 +205,7 @@ public class OneHandedControllerTest extends OneHandedTestCase {
mSpiedOneHandedController.setSwipeToNotificationEnabled(mDefaultSwipeToNotificationEnabled);
verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
verify(mMockGestureHandler, atLeastOnce()).onGestureEnabled(anyBoolean());
}
@Test
@@ -282,4 +291,67 @@ public class OneHandedControllerTest extends OneHandedTestCase {
verify(mMockDisplayAreaOrganizer).scheduleOffset(anyInt(), anyInt());
}
@Test
public void testRotation90CanNotStartOneHanded() {
final DisplayLayout landscapeDisplayLayout = new DisplayLayout(mDisplayLayout);
landscapeDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(landscapeDisplayLayout);
mSpiedOneHandedController.setOneHandedEnabled(true);
mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
mSpiedOneHandedController.startOneHanded();
verify(mMockDisplayAreaOrganizer, never()).scheduleOffset(anyInt(), anyInt());
}
@Test
public void testRotation180CanStartOneHanded() {
final DisplayLayout testDisplayLayout = new DisplayLayout(mDisplayLayout);
testDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(testDisplayLayout);
mSpiedOneHandedController.setOneHandedEnabled(true);
mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
mSpiedOneHandedController.startOneHanded();
verify(mMockDisplayAreaOrganizer).scheduleOffset(anyInt(), anyInt());
}
@Test
public void testRotation270CanNotStartOneHanded() {
final DisplayLayout testDisplayLayout = new DisplayLayout(mDisplayLayout);
testDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(testDisplayLayout);
mSpiedOneHandedController.setOneHandedEnabled(true);
mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
mSpiedOneHandedController.startOneHanded();
verify(mMockDisplayAreaOrganizer, never()).scheduleOffset(anyInt(), anyInt());
}
@Test
public void testDisabled3ButtonGestureWhenKeyguardOn() {
final boolean isOneHandedEnabled = true;
final boolean isLockWhenKeyguardOn = true;
final boolean isEnabledWhenKeyguardOn = false;
mSpiedOneHandedController.setOneHandedEnabled(isOneHandedEnabled);
mSpiedOneHandedController.setLockedDisabled(isLockWhenKeyguardOn, isEnabledWhenKeyguardOn);
verify(mMockGestureHandler).onGestureEnabled(isEnabledWhenKeyguardOn);
}
@Test
public void testEnabled3ButtonGestureWhenKeyguardGoingAway() {
final boolean isOneHandedEnabled = true;
final boolean isLockWhenKeyguardOn = false;
final boolean isEnabledWhenKeyguardOn = false;
mSpiedOneHandedController.setOneHandedEnabled(isOneHandedEnabled);
reset(mMockGestureHandler);
mSpiedOneHandedController.setLockedDisabled(isLockWhenKeyguardOn, isEnabledWhenKeyguardOn);
verify(mMockGestureHandler).onGestureEnabled(isOneHandedEnabled);
}
}

View File

@@ -19,6 +19,9 @@ package com.android.wm.shell.onehanded;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_EXIT;
import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_TRIGGER;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -32,6 +35,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -47,6 +51,7 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import org.junit.Before;
@@ -67,6 +72,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
DisplayAreaInfo mDisplayAreaInfo;
Display mDisplay;
DisplayLayout mDisplayLayout;
OneHandedDisplayAreaOrganizer mSpiedDisplayAreaOrganizer;
OneHandedTutorialHandler mTutorialHandler;
OneHandedAnimationController.OneHandedTransitionAnimator mFakeAnimator;
@@ -103,6 +109,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
mToken = new WindowContainerToken(mMockRealToken);
mLeash = new SurfaceControl();
mDisplay = mContext.getDisplay();
mDisplayLayout = new DisplayLayout(mContext, mDisplay);
mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY, FEATURE_ONE_HANDED);
mDisplayAreaInfo.configuration.orientation = Configuration.ORIENTATION_PORTRAIT;
when(mMockAnimationController.getAnimator(any(), any(), any(), any())).thenReturn(null);
@@ -121,7 +128,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
when(mMockLeash.getHeight()).thenReturn(DISPLAY_HEIGHT);
mSpiedDisplayAreaOrganizer = spy(new OneHandedDisplayAreaOrganizer(mContext,
mWindowManager,
mDisplayLayout,
mMockAnimationController,
mTutorialHandler,
mMockBackgroundOrganizer,
@@ -168,21 +175,14 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
any());
}
@Test
public void testRotation_getNewDisplayBounds() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 90
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_90,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).getDisplayBounds();
}
@Test
public void testRotation_portrait_0_to_landscape_90() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 90
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_90,
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@@ -190,8 +190,10 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_portrait_0_to_seascape_270() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 270
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_270,
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@@ -199,8 +201,12 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_portrait_180_to_landscape_90() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 180 -> 90
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_90,
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@@ -208,8 +214,12 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_portrait_180_to_seascape_270() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 180 -> 270
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_270,
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@@ -217,8 +227,12 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_landscape_90_to_portrait_0() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 90 -> 0
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_0,
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@@ -226,26 +240,38 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_landscape_90_to_portrait_180() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 90 -> 180
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_180,
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
public void testRotation_Seascape_270_to_portrait_0() {
public void testRotation_seascape_270_to_portrait_0() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 270 -> 0
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_0,
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
public void testRotation_seascape_90_to_portrait_180() {
public void testRotation_seascape_270_to_portrait_180() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 270 -> 180
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_180,
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@@ -253,7 +279,10 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_portrait_0_to_portrait_0() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 0
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_0,
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@@ -262,16 +291,23 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_portrait_0_to_portrait_180() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 180
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_180,
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
public void testRotation_portrait_180_to_portrait_180() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 180 -> 180
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_180,
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@@ -280,16 +316,25 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_portrait_180_to_portrait_0() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 180 -> 0
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_0,
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
public void testRotation_landscape_90_to_landscape_90() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 90 -> 90
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_90,
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@@ -298,26 +343,60 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_landscape_90_to_seascape_270() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 90 -> 270
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_270,
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
public void testRotation_seascape_270_to_seascape_270() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 270 -> 270
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_270,
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@Test
public void testRotation_seascape_90_to_landscape_90() {
public void testRotation_seascape_270_to_landscape_90() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 270 -> 90
mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_90,
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90,
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
public void testTriggerOffset() {
final Rect testBounds = mSpiedDisplayAreaOrganizer.getLastDisplayBounds();
final int offset = 100;
testBounds.offsetTo(0, offset);
mSpiedDisplayAreaOrganizer.finishOffset(offset, TRANSITION_DIRECTION_TRIGGER);
assertThat(mSpiedDisplayAreaOrganizer.getLastDisplayBounds()).isEqualTo(testBounds);
}
@Test
public void testExitOffsetToZero() {
final Rect testBounds = mSpiedDisplayAreaOrganizer.getLastDisplayBounds();
final int offset = 100;
mSpiedDisplayAreaOrganizer.finishOffset(offset, TRANSITION_DIRECTION_TRIGGER);
mSpiedDisplayAreaOrganizer.finishOffset(0, TRANSITION_DIRECTION_EXIT);
assertThat(mSpiedDisplayAreaOrganizer.getLastDisplayBounds()).isEqualTo(testBounds);
}
}

View File

@@ -16,21 +16,19 @@
package com.android.wm.shell.onehanded;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.view.Surface;
import android.view.ViewConfiguration;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -44,24 +42,20 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
public class OneHandedGestureHandlerTest extends OneHandedTestCase {
OneHandedGestureHandler mGestureHandler;
@Mock
DisplayController mMockDisplayController;
DisplayLayout mDisplayLayout;
@Mock
DisplayLayout mMockDisplayLayout;
@Mock
ShellExecutor mMockShellMainExecutor;
@Mock
WindowContainerTransaction mMockWct;
@Before
public void setUp() {
final int mockNavBarHeight = 100;
MockitoAnnotations.initMocks(this);
mGestureHandler = new OneHandedGestureHandler(mContext, mWindowManager,
mMockDisplayController, ViewConfiguration.get(mTestContext),
mMockShellMainExecutor);
mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay());
mGestureHandler = new OneHandedGestureHandler(mContext, mDisplayLayout,
ViewConfiguration.get(mTestContext), mMockShellMainExecutor);
when(mMockDisplayLayout.navBarFrameHeight()).thenReturn(mockNavBarHeight);
when(mMockDisplayController.getDisplayLayout(anyInt())).thenReturn(mMockDisplayLayout);
}
@Test
@@ -81,7 +75,7 @@ public class OneHandedGestureHandlerTest extends OneHandedTestCase {
@Test
public void testOneHandedDisabled_shouldDisposeInputChannel() {
mGestureHandler.onOneHandedEnabled(false);
mGestureHandler.onGestureEnabled(false);
assertThat(mGestureHandler.mInputMonitor).isNull();
assertThat(mGestureHandler.mInputEventReceiver).isNull();
@@ -89,7 +83,7 @@ public class OneHandedGestureHandlerTest extends OneHandedTestCase {
@Test
public void testChangeNavBarToNon3Button_shouldDisposeInputChannel() {
mGestureHandler.onOneHandedEnabled(true);
mGestureHandler.onGestureEnabled(true);
mGestureHandler.onThreeButtonModeEnabled(false);
assertThat(mGestureHandler.mInputMonitor).isNull();
@@ -98,11 +92,38 @@ public class OneHandedGestureHandlerTest extends OneHandedTestCase {
@Test
public void testOnlyHandleGestureInPortraitMode() {
mGestureHandler.onOneHandedEnabled(true);
mGestureHandler.onRotateDisplay(DEFAULT_DISPLAY, Surface.ROTATION_0, Surface.ROTATION_90,
mMockWct);
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
mGestureHandler.onGestureEnabled(true);
mGestureHandler.onRotateDisplay(mDisplayLayout);
assertThat(mGestureHandler.mInputMonitor).isNull();
assertThat(mGestureHandler.mInputEventReceiver).isNull();
}
@Test
public void testRotation90ShouldNotRegisterEventReceiver() throws InterruptedException {
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
mGestureHandler.onGestureEnabled(true);
mGestureHandler.onRotateDisplay(mDisplayLayout);
verify(mMockShellMainExecutor, never()).executeBlocking(any());
}
@Test
public void testRotation180ShouldNotRegisterEventReceiver() throws InterruptedException {
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
mGestureHandler.onGestureEnabled(true);
mGestureHandler.onRotateDisplay(mDisplayLayout);
verify(mMockShellMainExecutor, never()).executeBlocking(any());
}
@Test
public void testRotation270ShouldNotRegisterEventReceiver() throws InterruptedException {
mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
mGestureHandler.onGestureEnabled(true);
mGestureHandler.onRotateDisplay(mDisplayLayout);
verify(mMockShellMainExecutor, never()).executeBlocking(any());
}
}

View File

@@ -75,7 +75,6 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase {
when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>());
mOneHandedController = new OneHandedController(
mContext,
mWindowManager,
mMockDisplayController,
mMockBackgroundOrganizer,
mMockDisplayAreaOrganizer,

View File

@@ -52,6 +52,7 @@ import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
@@ -214,6 +215,12 @@ public abstract class WMShellBaseModule {
return new DisplayController(context, wmService, mainExecutor);
}
@WMSingleton
@Provides
static DisplayLayout provideDisplayLayout() {
return new DisplayLayout();
}
@WMSingleton
@Provides
static DragAndDropController provideDragAndDropController(Context context,
@@ -344,11 +351,13 @@ public abstract class WMShellBaseModule {
@Provides
static Optional<OneHandedController> provideOneHandedController(Context context,
WindowManager windowManager, DisplayController displayController,
TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger,
DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
UiEventLogger uiEventLogger,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return Optional.ofNullable(OneHandedController.create(context, windowManager,
displayController, taskStackListener, uiEventLogger, mainExecutor, mainHandler));
displayController, displayLayout, taskStackListener, uiEventLogger, mainExecutor,
mainHandler));
}
//