diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java index 7e5fd927ea051..1ae2636241866 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java @@ -19,6 +19,7 @@ package com.android.wm.shell.onehanded; import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.IntDef; +import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; import android.view.SurfaceControl; @@ -203,8 +204,10 @@ public class OneHandedAnimationController { } OneHandedTransitionAnimator addOneHandedAnimationCallback( - OneHandedAnimationCallback callback) { - mOneHandedAnimationCallbacks.add(callback); + @Nullable OneHandedAnimationCallback callback) { + if (callback != null) { + mOneHandedAnimationCallbacks.add(callback); + } return this; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index e506542120fcf..49266ce1bb624 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -217,7 +217,7 @@ public class OneHandedController implements RemoteCallable OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor); OneHandedState transitionState = new OneHandedState(); OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context, - displayLayout, windowManager, mainExecutor); + displayLayout, windowManager, settingsUtil, mainExecutor); OneHandedAnimationController animationController = new OneHandedAnimationController(context); OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler, @@ -299,6 +299,8 @@ public class OneHandedController implements RemoteCallable mAccessibilityManager = AccessibilityManager.getInstance(context); mAccessibilityManager.addAccessibilityStateChangeListener( mAccessibilityStateChangeListener); + + mState.addSListeners(mTutorialHandler); } public OneHanded asOneHanded() { @@ -627,13 +629,13 @@ public class OneHandedController implements RemoteCallable } private void onConfigChanged(Configuration newConfig) { - if (mTutorialHandler != null) { - if (!mIsOneHandedEnabled - || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { - return; - } - mTutorialHandler.onConfigurationChanged(newConfig); + if (mTutorialHandler == null) { + return; } + if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { + return; + } + mTutorialHandler.onConfigurationChanged(); } private void onUserSwitch(int newUserId) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java index bb68224b0a872..90fc823fb574d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java @@ -29,7 +29,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * APIs for querying or updating one handed settings . + * APIs for querying or updating one handed settings. */ public final class OneHandedSettingsUtil { private static final String TAG = "OneHandedSettingsUtil"; @@ -62,7 +62,7 @@ public final class OneHandedSettingsUtil { public static final int ONE_HANDED_TIMEOUT_LONG_IN_SECONDS = 12; /** - * Register one handed preference settings observer + * Registers one handed preference settings observer * * @param key Setting key to monitor in observer * @param resolver ContentResolver of context @@ -82,7 +82,7 @@ public final class OneHandedSettingsUtil { } /** - * Unregister one handed preference settings observer + * Unregisters one handed preference settings observer. * * @param resolver ContentResolver of context * @param observer preference key change observer @@ -95,7 +95,7 @@ public final class OneHandedSettingsUtil { } /** - * Query one handed enable or disable flag from Settings provider. + * Queries one handed enable or disable flag from Settings provider. * * @return enable or disable one handed mode flag. */ @@ -105,7 +105,7 @@ public final class OneHandedSettingsUtil { } /** - * Query taps app to exit config from Settings provider. + * Queries taps app to exit config from Settings provider. * * @return enable or disable taps app exit. */ @@ -115,7 +115,7 @@ public final class OneHandedSettingsUtil { } /** - * Query timeout value from Settings provider. Default is + * Queries timeout value from Settings provider. Default is. * {@link OneHandedSettingsUtil#ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS} * * @return timeout value in seconds. @@ -135,10 +135,31 @@ public final class OneHandedSettingsUtil { Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 0, userId) == 1; } + /** - * Sets one handed activated or not to notify state for shortcut + * Queries tutorial shown counts from Settings provider. Default is 0. * - * @return activated or not + * @return counts tutorial shown counts. + */ + public int getTutorialShownCounts(ContentResolver resolver, int userId) { + return Settings.Secure.getIntForUser(resolver, + Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT, 0, userId); + } + + /** + * Sets tutorial shown counts. + * + * @return true if the value was set, false on database errors. + */ + public boolean setTutorialShownCounts(ContentResolver resolver, int shownCounts, int userId) { + return Settings.Secure.putIntForUser(resolver, + Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT, shownCounts, userId); + } + + /** + * Sets one handed activated or not to notify state for shortcut. + * + * @return true if one handed mode is activated. */ public boolean getOneHandedModeActivated(ContentResolver resolver, int userId) { return Settings.Secure.getIntForUser(resolver, @@ -146,9 +167,9 @@ public final class OneHandedSettingsUtil { } /** - * Sets one handed activated or not to notify state for shortcut + * Sets one handed activated or not to notify state for shortcut. * - * @return activated or not + * @return true if the value was set, false on database errors. */ public boolean setOneHandedModeActivated(ContentResolver resolver, int state, int userId) { return Settings.Secure.putIntForUser(resolver, @@ -167,6 +188,8 @@ public final class OneHandedSettingsUtil { pw.println(getSettingsTapsAppToExit(resolver, userId)); pw.print(innerPrefix + "shortcutActivated="); pw.println(getOneHandedModeActivated(resolver, userId)); + pw.print(innerPrefix + "tutorialShownCounts="); + pw.println(getTutorialShownCounts(resolver, userId)); } public OneHandedSettingsUtil() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java index facc4bd2b6e8b..47bb99da73243 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java @@ -21,6 +21,8 @@ import android.annotation.IntDef; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; /** Represents current OHM state by following steps, a generic CUJ is @@ -28,13 +30,13 @@ import java.lang.annotation.RetentionPolicy; */ public class OneHandedState { /** DEFAULT STATE after OHM feature initialized. */ - public static final int STATE_NONE = 0x00000000; + public static final int STATE_NONE = 0; /** The state flag set when user trigger OHM. */ - public static final int STATE_ENTERING = 0x00000001; + public static final int STATE_ENTERING = 1; /** The state flag set when transitioning */ - public static final int STATE_ACTIVE = 0x00000002; + public static final int STATE_ACTIVE = 2; /** The state flag set when user stop OHM feature. */ - public static final int STATE_EXITING = 0x00000004; + public static final int STATE_EXITING = 3; @IntDef(prefix = { "STATE_" }, value = { STATE_NONE, @@ -54,9 +56,18 @@ public class OneHandedState { private static final String TAG = OneHandedState.class.getSimpleName(); + private List mStateChangeListeners = new ArrayList<>(); + + /** + * Adds listener to be called back when one handed state changed. + * @param listener the listener to be called back + */ + public void addSListeners(OnStateChangedListener listener) { + mStateChangeListeners.add(listener); + } + /** * Gets current transition state of One handed mode. - * * @return The bitwise flags representing current states. */ public @State int getState() { @@ -85,6 +96,9 @@ public class OneHandedState { */ public void setState(@State int newState) { sCurrentState = newState; + if (!mStateChangeListeners.isEmpty()) { + mStateChangeListeners.forEach((listener) -> listener.onStateChanged(newState)); + } } /** Dumps internal state. */ @@ -93,4 +107,14 @@ public class OneHandedState { pw.println(TAG); pw.println(innerPrefix + "sCurrentState=" + sCurrentState); } + + /** + * Gets notified when one handed state changed + * + * @see OneHandedState + */ + public interface OnStateChangedListener { + /** Called when one handed state changed */ + default void onStateChanged(@State int newState) {} + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java index e8cee8a95c443..6cee404758ec3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java @@ -16,13 +16,19 @@ package com.android.wm.shell.onehanded; +import static android.os.UserHandle.myUserId; + +import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE; +import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING; +import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING; +import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE; + +import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; -import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.SystemProperties; -import android.provider.Settings; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -32,6 +38,7 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; +import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; @@ -39,42 +46,33 @@ import com.android.wm.shell.common.ShellExecutor; import java.io.PrintWriter; /** - * Manages the user tutorial handling for One Handed operations, including animations synchronized - * with one-handed translation. - * Refer {@link OneHandedGestureHandler} and {@link OneHandedTouchHandler} to see start and stop - * one handed gesture + * Handles tutorial visibility and synchronized transition for One Handed operations, + * TargetViewContainer only be created and attach to window when + * shown counts < {@link MAX_TUTORIAL_SHOW_COUNT}, and detach TargetViewContainer from window + * after exiting one handed mode. */ -public class OneHandedTutorialHandler implements OneHandedTransitionCallback { +public class OneHandedTutorialHandler implements OneHandedTransitionCallback, + OneHandedState.OnStateChangedListener { private static final String TAG = "OneHandedTutorialHandler"; private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE = "persist.debug.one_handed_offset_percentage"; private static final int MAX_TUTORIAL_SHOW_COUNT = 2; - private final WindowManager mWindowManager; - private final String mPackageName; + private final float mTutorialHeightRatio; + private final WindowManager mWindowManager; + private final OneHandedSettingsUtil mSettingsUtil; + private final ShellExecutor mShellExecutor; + + private boolean mCanShow; + private @OneHandedState.State int mCurrentState; + private int mShownCounts; + private int mTutorialAreaHeight; private Context mContext; - private Rect mDisplayBounds; - private View mTutorialView; private ContentResolver mContentResolver; - private boolean mCanShowTutorial; - private boolean mIsOneHandedMode; - - private enum ONE_HANDED_TRIGGER_STATE { - UNSET, ENTERING, EXITING - } - /** - * Current One-Handed trigger state. - * Note: This is a dynamic state, whenever last state has been confirmed - * (i.e. onStartFinished() or onStopFinished()), the state should be set "UNSET" at final. - */ - private ONE_HANDED_TRIGGER_STATE mTriggerState = ONE_HANDED_TRIGGER_STATE.UNSET; - - /** - * Container of the tutorial panel showing at outside region when one handed starting - */ - private ViewGroup mTargetViewContainer; - private int mTutorialAreaHeight; + private Rect mDisplayBounds; + private @Nullable View mTutorialView; + private @Nullable ViewGroup mTargetViewContainer; private final OneHandedAnimationCallback mAnimationCallback = new OneHandedAnimationCallback() { @Override @@ -82,63 +80,51 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback { if (!canShowTutorial()) { return; } - mTargetViewContainer.setVisibility(View.VISIBLE); mTargetViewContainer.setTransitionGroup(true); mTargetViewContainer.setTranslationY(yPos - mTargetViewContainer.getHeight()); } - - @Override - public void onOneHandedAnimationStart( - OneHandedAnimationController.OneHandedTransitionAnimator animator) { - final float startValue = (float) animator.getStartValue(); - if (mTriggerState == ONE_HANDED_TRIGGER_STATE.UNSET) { - mTriggerState = (startValue == 0f) - ? ONE_HANDED_TRIGGER_STATE.ENTERING : ONE_HANDED_TRIGGER_STATE.EXITING; - if (mCanShowTutorial && mTriggerState == ONE_HANDED_TRIGGER_STATE.ENTERING) { - attachTurtorialTarget(); - } - } - } }; public OneHandedTutorialHandler(Context context, DisplayLayout displayLayout, - WindowManager windowManager, ShellExecutor mainExecutor) { + WindowManager windowManager, OneHandedSettingsUtil settingsUtil, + ShellExecutor mainExecutor) { mContext = context; - mWindowManager = windowManager; - mPackageName = context.getPackageName(); mContentResolver = context.getContentResolver(); + mWindowManager = windowManager; + mSettingsUtil = settingsUtil; + mShellExecutor = mainExecutor; final float offsetPercentageConfig = context.getResources().getFraction( R.fraction.config_one_handed_offset, 1, 1); final int sysPropPercentageConfig = SystemProperties.getInt( ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f)); mTutorialHeightRatio = sysPropPercentageConfig / 100.0f; - onDisplayChanged(displayLayout); - mCanShowTutorial = (Settings.Secure.getInt(mContentResolver, - Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT, 0) >= MAX_TUTORIAL_SHOW_COUNT) - ? false : true; - mIsOneHandedMode = false; - - mainExecutor.execute(() -> { - recreateTutorialView(mContext); - }); + mShownCounts = mSettingsUtil.getTutorialShownCounts(mContentResolver, myUserId()); } @Override - public void onStartFinished(Rect bounds) { - updateFinished(View.VISIBLE, 0f); - updateTutorialCount(); - mTriggerState = ONE_HANDED_TRIGGER_STATE.UNSET; - } - - @Override - public void onStopFinished(Rect bounds) { - updateFinished(View.INVISIBLE, -mTargetViewContainer.getHeight()); - removeTutorialFromWindowManager(); - mTriggerState = ONE_HANDED_TRIGGER_STATE.UNSET; + public void onStateChanged(int newState) { + mCurrentState = newState; + if (!canShowTutorial()) { + return; + } + switch (newState) { + case STATE_ENTERING: + createViewAndAttachToWindow(mContext); + break; + case STATE_ACTIVE: + case STATE_EXITING: + // no - op + break; + case STATE_NONE: + removeTutorialFromWindowManager(true /* increment */); + break; + default: + break; + } } /** - * Called when onDisplayAdded() or onDisplayRemoved() callback + * Called when onDisplayAdded() or onDisplayRemoved() callback. * @param displayLayout The latest {@link DisplayLayout} representing current displayId */ public void onDisplayChanged(DisplayLayout displayLayout) { @@ -151,38 +137,32 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback { mTutorialAreaHeight = Math.round(mDisplayBounds.height() * mTutorialHeightRatio); } - private void recreateTutorialView(Context context) { - mTutorialView = LayoutInflater.from(context).inflate(R.layout.one_handed_tutorial, - null); - mTargetViewContainer = new FrameLayout(context); - mTargetViewContainer.setClipChildren(false); - mTargetViewContainer.addView(mTutorialView); - mTargetViewContainer.setVisibility(mIsOneHandedMode ? View.VISIBLE : View.GONE); - } - - private void updateFinished(int visible, float finalPosition) { + @VisibleForTesting + void createViewAndAttachToWindow(Context context) { if (!canShowTutorial()) { return; } - mIsOneHandedMode = (finalPosition == 0f) ? true : false; - mTargetViewContainer.setVisibility(visible); - mTargetViewContainer.setTranslationY(finalPosition); + mTutorialView = LayoutInflater.from(context).inflate(R.layout.one_handed_tutorial, null); + mTargetViewContainer = new FrameLayout(context); + mTargetViewContainer.setClipChildren(false); + mTargetViewContainer.addView(mTutorialView); + + attachTargetToWindow(); } - private void updateTutorialCount() { - int showCount = Settings.Secure.getInt(mContentResolver, - Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT, 0); - showCount = Math.min(MAX_TUTORIAL_SHOW_COUNT, showCount + 1); - mCanShowTutorial = showCount < MAX_TUTORIAL_SHOW_COUNT; - Settings.Secure.putInt(mContentResolver, - Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT, showCount); + @VisibleForTesting + boolean setTutorialShownCountIncrement() { + if (!canShowTutorial()) { + return false; + } + mShownCounts += 1; + return mSettingsUtil.setTutorialShownCounts(mContentResolver, mShownCounts, myUserId()); } /** - * Adds the tutorial target view to the WindowManager and update its layout, so it's ready - * to be animated in. + * Adds the tutorial target view to the WindowManager and update its layout. */ - private void attachTurtorialTarget() { + private void attachTargetToWindow() { if (!mTargetViewContainer.isAttachedToWindow()) { try { mWindowManager.addView(mTargetViewContainer, getTutorialTargetLayoutParams()); @@ -195,14 +175,18 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback { } } - private void removeTutorialFromWindowManager() { - if (mTargetViewContainer.isAttachedToWindow()) { + @VisibleForTesting + void removeTutorialFromWindowManager(boolean increment) { + if (mTargetViewContainer != null && mTargetViewContainer.isAttachedToWindow()) { mWindowManager.removeViewImmediate(mTargetViewContainer); + if (increment) { + setTutorialShownCountIncrement(); + } } } - OneHandedAnimationCallback getAnimationCallback() { - return mAnimationCallback; + @Nullable OneHandedAnimationCallback getAnimationCallback() { + return canShowTutorial() ? mAnimationCallback : null /* Disabled */; } /** @@ -222,38 +206,36 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback { return lp; } - void dump(@NonNull PrintWriter pw) { - final String innerPrefix = " "; - pw.println(TAG); - pw.print(innerPrefix + "mTriggerState="); - pw.println(mTriggerState); - pw.print(innerPrefix + "mDisplayBounds="); - pw.println(mDisplayBounds); - pw.print(innerPrefix + "mTutorialAreaHeight="); - pw.println(mTutorialAreaHeight); - } - - private boolean canShowTutorial() { - if (!mCanShowTutorial) { - // Since canSHowTutorial() will be called in onAnimationUpdate() and we still need to - // hide Tutorial text in the period of continuously onAnimationUpdate() API call, - // so we have to hide mTargetViewContainer here. - mTargetViewContainer.setVisibility(View.GONE); - return false; - } - return true; + @VisibleForTesting + boolean canShowTutorial() { + return mCanShow = mShownCounts < MAX_TUTORIAL_SHOW_COUNT; } /** * onConfigurationChanged events for updating tutorial text. - * @param newConfig */ - public void onConfigurationChanged(Configuration newConfig) { - if (!mCanShowTutorial) { + public void onConfigurationChanged() { + if (!canShowTutorial()) { return; } - removeTutorialFromWindowManager(); - recreateTutorialView(mContext.createConfigurationContext(newConfig)); - attachTurtorialTarget(); + removeTutorialFromWindowManager(false /* increment */); + if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) { + createViewAndAttachToWindow(mContext); + } + } + + void dump(@NonNull PrintWriter pw) { + final String innerPrefix = " "; + pw.println(TAG); + pw.print(innerPrefix + "mCanShow="); + pw.println(mCanShow); + pw.print(innerPrefix + "mCurrentState="); + pw.println(mCurrentState); + pw.print(innerPrefix + "mDisplayBounds="); + pw.println(mDisplayBounds); + pw.print(innerPrefix + "mShownCounts="); + pw.println(mShownCounts); + pw.print(innerPrefix + "mTutorialAreaHeight="); + pw.println(mTutorialAreaHeight); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index 25d90b30162b1..1852279ee96c8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -398,4 +398,14 @@ public class OneHandedControllerTest extends OneHandedTestCase { verify(mSpiedOneHandedController, never()).startOneHanded(); verify(mSpiedOneHandedController, never()).stopOneHanded(); } + + @Test + public void testControllerInit_tutorialAddStateChangeListener() { + when(mSpiedOneHandedController.isOneHandedEnabled()).thenReturn(true); + when(mSpiedTransitionState.getState()).thenReturn(STATE_NONE); + when(mSpiedTransitionState.isTransitioning()).thenReturn(false); + when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(false); + + verify(mSpiedTransitionState).addSListeners(mMockTutorialHandler); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java index 5f2bfad6bb041..1bc2a0823ff73 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java @@ -16,19 +16,26 @@ package com.android.wm.shell.onehanded; +import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING; +import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.never; +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.os.Handler; import android.testing.AndroidTestingRunner; -import android.util.ArrayMap; +import android.view.Display; +import android.view.WindowManager; 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; import org.junit.Before; import org.junit.Test; @@ -39,62 +46,94 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) public class OneHandedTutorialHandlerTest extends OneHandedTestCase { + Display mDisplay; + DisplayLayout mDisplayLayout; OneHandedTimeoutHandler mTimeoutHandler; - OneHandedController mOneHandedController; OneHandedState mSpiedTransitionState; + OneHandedTutorialHandler mSpiedTutorialHandler; - @Mock - OneHandedTouchHandler mMockTouchHandler; - @Mock - OneHandedTutorialHandler mMockTutorialHandler; - @Mock - DisplayController mMockDisplayController; - @Mock - OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer; - @Mock - OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer; - @Mock - IOverlayManager mMockOverlayManager; - @Mock - TaskStackListenerImpl mMockTaskStackListener; @Mock ShellExecutor mMockShellMainExecutor; @Mock - Handler mMockShellMainHandler; - @Mock - OneHandedUiEventLogger mMockUiEventLogger; - @Mock OneHandedSettingsUtil mMockSettingsUtil; @Mock - OneHandedAccessibilityUtil mMockAccessibilityUtil; + WindowManager mMockWindowManager; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mTimeoutHandler = new OneHandedTimeoutHandler(mMockShellMainExecutor); - mSpiedTransitionState = new OneHandedState(); + when(mMockSettingsUtil.getTutorialShownCounts(any(), anyInt())).thenReturn(0); - when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>()); - mOneHandedController = new OneHandedController( - mContext, - mMockDisplayController, - mMockBackgroundOrganizer, - mMockDisplayAreaOrganizer, - mMockTouchHandler, - mMockTutorialHandler, - mMockSettingsUtil, - mMockAccessibilityUtil, - mTimeoutHandler, - mSpiedTransitionState, - mMockUiEventLogger, - mMockOverlayManager, - mMockTaskStackListener, - mMockShellMainExecutor, - mMockShellMainHandler); + mDisplay = mContext.getDisplay(); + mDisplayLayout = new DisplayLayout(mContext, mDisplay); + mSpiedTransitionState = spy(new OneHandedState()); + mSpiedTutorialHandler = spy( + new OneHandedTutorialHandler(mContext, mDisplayLayout, mMockWindowManager, + mMockSettingsUtil, mMockShellMainExecutor)); + mTimeoutHandler = new OneHandedTimeoutHandler(mMockShellMainExecutor); } @Test - public void testRegisterForDisplayAreaOrganizer() { - verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mMockTutorialHandler); + public void testDefaultZeroShownCounts_canShowTutorial() { + assertThat(mSpiedTutorialHandler.canShowTutorial()).isTrue(); + verify(mMockShellMainExecutor, never()).execute(any()); + } + + @Test + public void testDefaultZeroShownCounts_doNotAttachWindow() { + verify(mMockShellMainExecutor, never()).execute(any()); + } + + @Test + public void testOnStateChangedEntering_createViewAndAttachToWindow() { + when(mSpiedTutorialHandler.canShowTutorial()).thenReturn(true); + try { + mSpiedTutorialHandler.onStateChanged(STATE_ENTERING); + } catch (ClassCastException e) { + // no-op, just assert createViewAndAttachToWindow() to be called + } + + verify(mSpiedTutorialHandler).createViewAndAttachToWindow(any()); + } + + @Test + public void testOnStateChangedNone_removeViewAndAttachToWindow() { + when(mSpiedTutorialHandler.canShowTutorial()).thenReturn(true); + try { + mSpiedTutorialHandler.onStateChanged(STATE_NONE); + } catch (ClassCastException e) { + // no-op, just assert removeTutorialFromWindowManager() to be called + } + + verify(mSpiedTutorialHandler).removeTutorialFromWindowManager(true); + } + + @Test + public void testOnStateChangedNone_shouldNotAttachWindow() { + when(mSpiedTutorialHandler.canShowTutorial()).thenReturn(true); + try { + mSpiedTutorialHandler.onStateChanged(STATE_NONE); + } catch (ClassCastException e) { + // no-op, just assert setTutorialShownCountIncrement() never be called + } + + verify(mSpiedTutorialHandler, never()).setTutorialShownCountIncrement(); + } + + @Test + public void testOnConfigurationChanged_shouldUpdateViewContent() { + when(mSpiedTutorialHandler.canShowTutorial()).thenReturn(true); + try { + mSpiedTutorialHandler.onStateChanged(STATE_ENTERING); + } catch (ClassCastException e) { + // no-op, set current state for test onConfigurationChanged() + } + try { + mSpiedTutorialHandler.onConfigurationChanged(); + } catch (ClassCastException e) { + // no-op, just assert removeTutorialFromWindowManager() be called + } + + verify(mSpiedTutorialHandler).removeTutorialFromWindowManager(false); } }