Merge "Redesign One-haned tutorial UX behavior" into sc-dev

This commit is contained in:
Bill Lin
2021-07-10 01:05:11 +00:00
committed by Android (Google) Code Review
3 changed files with 30 additions and 68 deletions

View File

@@ -223,7 +223,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor); OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
OneHandedState transitionState = new OneHandedState(); OneHandedState transitionState = new OneHandedState();
OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context, OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
displayLayout, windowManager, settingsUtil, mainExecutor); windowManager);
OneHandedAnimationController animationController = OneHandedAnimationController animationController =
new OneHandedAnimationController(context); new OneHandedAnimationController(context);
OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler, OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler,

View File

@@ -16,7 +16,7 @@
package com.android.wm.shell.onehanded; package com.android.wm.shell.onehanded;
import static android.os.UserHandle.myUserId; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE; 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_ENTERING;
@@ -24,7 +24,6 @@ import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE; import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.graphics.Rect; import android.graphics.Rect;
@@ -41,7 +40,6 @@ import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R; import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import java.io.PrintWriter; import java.io.PrintWriter;
@@ -56,57 +54,44 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
private static final String TAG = "OneHandedTutorialHandler"; private static final String TAG = "OneHandedTutorialHandler";
private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE = private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
"persist.debug.one_handed_offset_percentage"; "persist.debug.one_handed_offset_percentage";
private static final int MAX_TUTORIAL_SHOW_COUNT = 2;
private final float mTutorialHeightRatio; private final float mTutorialHeightRatio;
private final WindowManager mWindowManager; private final WindowManager mWindowManager;
private final OneHandedSettingsUtil mSettingsUtil;
private final ShellExecutor mShellExecutor;
private boolean mCanShow; private boolean mIsShowing;
private @OneHandedState.State int mCurrentState; private @OneHandedState.State int mCurrentState;
private int mShownCounts;
private int mTutorialAreaHeight; private int mTutorialAreaHeight;
private Context mContext; private Context mContext;
private ContentResolver mContentResolver;
private Rect mDisplayBounds; private Rect mDisplayBounds;
private @Nullable View mTutorialView; private @Nullable View mTutorialView;
private @Nullable ViewGroup mTargetViewContainer; private @Nullable ViewGroup mTargetViewContainer;
private final OneHandedAnimationCallback mAnimationCallback = new OneHandedAnimationCallback() { private final OneHandedAnimationCallback mAnimationCallback;
@Override
public void onAnimationUpdate(float xPos, float yPos) {
if (!canShowTutorial()) {
return;
}
mTargetViewContainer.setTransitionGroup(true);
mTargetViewContainer.setTranslationY(yPos - mTargetViewContainer.getHeight());
}
};
public OneHandedTutorialHandler(Context context, DisplayLayout displayLayout, public OneHandedTutorialHandler(Context context, WindowManager windowManager) {
WindowManager windowManager, OneHandedSettingsUtil settingsUtil,
ShellExecutor mainExecutor) {
mContext = context; mContext = context;
mContentResolver = context.getContentResolver();
mWindowManager = windowManager; mWindowManager = windowManager;
mSettingsUtil = settingsUtil;
mShellExecutor = mainExecutor;
final float offsetPercentageConfig = context.getResources().getFraction( final float offsetPercentageConfig = context.getResources().getFraction(
R.fraction.config_one_handed_offset, 1, 1); R.fraction.config_one_handed_offset, 1, 1);
final int sysPropPercentageConfig = SystemProperties.getInt( final int sysPropPercentageConfig = SystemProperties.getInt(
ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f)); ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f));
mTutorialHeightRatio = sysPropPercentageConfig / 100.0f; mTutorialHeightRatio = sysPropPercentageConfig / 100.0f;
mShownCounts = mSettingsUtil.getTutorialShownCounts(mContentResolver, myUserId()); mAnimationCallback = new OneHandedAnimationCallback() {
@Override
public void onAnimationUpdate(float xPos, float yPos) {
if (!isShowing()) {
return;
}
mTargetViewContainer.setTransitionGroup(true);
mTargetViewContainer.setTranslationY(yPos - mTargetViewContainer.getHeight());
}
};
} }
@Override @Override
public void onStateChanged(int newState) { public void onStateChanged(int newState) {
mCurrentState = newState; mCurrentState = newState;
if (!canShowTutorial()) {
return;
}
switch (newState) { switch (newState) {
case STATE_ENTERING: case STATE_ENTERING:
createViewAndAttachToWindow(mContext); createViewAndAttachToWindow(mContext);
@@ -139,7 +124,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
@VisibleForTesting @VisibleForTesting
void createViewAndAttachToWindow(Context context) { void createViewAndAttachToWindow(Context context) {
if (!canShowTutorial()) { if (isShowing()) {
return; return;
} }
mTutorialView = LayoutInflater.from(context).inflate(R.layout.one_handed_tutorial, null); mTutorialView = LayoutInflater.from(context).inflate(R.layout.one_handed_tutorial, null);
@@ -150,15 +135,6 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
attachTargetToWindow(); attachTargetToWindow();
} }
@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. * Adds the tutorial target view to the WindowManager and update its layout.
*/ */
@@ -166,6 +142,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
if (!mTargetViewContainer.isAttachedToWindow()) { if (!mTargetViewContainer.isAttachedToWindow()) {
try { try {
mWindowManager.addView(mTargetViewContainer, getTutorialTargetLayoutParams()); mWindowManager.addView(mTargetViewContainer, getTutorialTargetLayoutParams());
mIsShowing = true;
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
// This shouldn't happen, but if the target is already added, just update its // This shouldn't happen, but if the target is already added, just update its
// layout params. // layout params.
@@ -179,14 +156,12 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
void removeTutorialFromWindowManager(boolean increment) { void removeTutorialFromWindowManager(boolean increment) {
if (mTargetViewContainer != null && mTargetViewContainer.isAttachedToWindow()) { if (mTargetViewContainer != null && mTargetViewContainer.isAttachedToWindow()) {
mWindowManager.removeViewImmediate(mTargetViewContainer); mWindowManager.removeViewImmediate(mTargetViewContainer);
if (increment) { mIsShowing = false;
setTutorialShownCountIncrement();
}
} }
} }
@Nullable OneHandedAnimationCallback getAnimationCallback() { @Nullable OneHandedAnimationCallback getAnimationCallback() {
return canShowTutorial() ? mAnimationCallback : null /* Disabled */; return isShowing() ? mAnimationCallback : null /* Disabled */;
} }
/** /**
@@ -200,6 +175,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT); PixelFormat.TRANSLUCENT);
lp.gravity = Gravity.TOP | Gravity.LEFT; lp.gravity = Gravity.TOP | Gravity.LEFT;
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setFitInsetsTypes(0 /* types */); lp.setFitInsetsTypes(0 /* types */);
lp.setTitle("one-handed-tutorial-overlay"); lp.setTitle("one-handed-tutorial-overlay");
@@ -207,17 +183,14 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
} }
@VisibleForTesting @VisibleForTesting
boolean canShowTutorial() { boolean isShowing() {
return mCanShow = mShownCounts < MAX_TUTORIAL_SHOW_COUNT; return mIsShowing;
} }
/** /**
* onConfigurationChanged events for updating tutorial text. * onConfigurationChanged events for updating tutorial text.
*/ */
public void onConfigurationChanged() { public void onConfigurationChanged() {
if (!canShowTutorial()) {
return;
}
removeTutorialFromWindowManager(false /* increment */); removeTutorialFromWindowManager(false /* increment */);
if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) { if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) {
createViewAndAttachToWindow(mContext); createViewAndAttachToWindow(mContext);
@@ -227,14 +200,12 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
void dump(@NonNull PrintWriter pw) { void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " "; final String innerPrefix = " ";
pw.println(TAG); pw.println(TAG);
pw.print(innerPrefix + "mCanShow="); pw.print(innerPrefix + "mIsShowing=");
pw.println(mCanShow); pw.println(mIsShowing);
pw.print(innerPrefix + "mCurrentState="); pw.print(innerPrefix + "mCurrentState=");
pw.println(mCurrentState); pw.println(mCurrentState);
pw.print(innerPrefix + "mDisplayBounds="); pw.print(innerPrefix + "mDisplayBounds=");
pw.println(mDisplayBounds); pw.println(mDisplayBounds);
pw.print(innerPrefix + "mShownCounts=");
pw.println(mShownCounts);
pw.print(innerPrefix + "mTutorialAreaHeight="); pw.print(innerPrefix + "mTutorialAreaHeight=");
pw.println(mTutorialAreaHeight); pw.println(mTutorialAreaHeight);
} }

View File

@@ -19,8 +19,6 @@ 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_ENTERING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE; 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.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
@@ -68,17 +66,10 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase {
mDisplayLayout = new DisplayLayout(mContext, mDisplay); mDisplayLayout = new DisplayLayout(mContext, mDisplay);
mSpiedTransitionState = spy(new OneHandedState()); mSpiedTransitionState = spy(new OneHandedState());
mSpiedTutorialHandler = spy( mSpiedTutorialHandler = spy(
new OneHandedTutorialHandler(mContext, mDisplayLayout, mMockWindowManager, new OneHandedTutorialHandler(mContext, mMockWindowManager));
mMockSettingsUtil, mMockShellMainExecutor));
mTimeoutHandler = new OneHandedTimeoutHandler(mMockShellMainExecutor); mTimeoutHandler = new OneHandedTimeoutHandler(mMockShellMainExecutor);
} }
@Test
public void testDefaultZeroShownCounts_canShowTutorial() {
assertThat(mSpiedTutorialHandler.canShowTutorial()).isTrue();
verify(mMockShellMainExecutor, never()).execute(any());
}
@Test @Test
public void testDefaultZeroShownCounts_doNotAttachWindow() { public void testDefaultZeroShownCounts_doNotAttachWindow() {
verify(mMockShellMainExecutor, never()).execute(any()); verify(mMockShellMainExecutor, never()).execute(any());
@@ -86,7 +77,7 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase {
@Test @Test
public void testOnStateChangedEntering_createViewAndAttachToWindow() { public void testOnStateChangedEntering_createViewAndAttachToWindow() {
when(mSpiedTutorialHandler.canShowTutorial()).thenReturn(true); when(mSpiedTutorialHandler.isShowing()).thenReturn(true);
try { try {
mSpiedTutorialHandler.onStateChanged(STATE_ENTERING); mSpiedTutorialHandler.onStateChanged(STATE_ENTERING);
} catch (ClassCastException e) { } catch (ClassCastException e) {
@@ -98,7 +89,7 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase {
@Test @Test
public void testOnStateChangedNone_removeViewAndAttachToWindow() { public void testOnStateChangedNone_removeViewAndAttachToWindow() {
when(mSpiedTutorialHandler.canShowTutorial()).thenReturn(true); when(mSpiedTutorialHandler.isShowing()).thenReturn(true);
try { try {
mSpiedTutorialHandler.onStateChanged(STATE_NONE); mSpiedTutorialHandler.onStateChanged(STATE_NONE);
} catch (ClassCastException e) { } catch (ClassCastException e) {
@@ -110,19 +101,19 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase {
@Test @Test
public void testOnStateChangedNone_shouldNotAttachWindow() { public void testOnStateChangedNone_shouldNotAttachWindow() {
when(mSpiedTutorialHandler.canShowTutorial()).thenReturn(true); when(mSpiedTutorialHandler.isShowing()).thenReturn(true);
try { try {
mSpiedTutorialHandler.onStateChanged(STATE_NONE); mSpiedTutorialHandler.onStateChanged(STATE_NONE);
} catch (ClassCastException e) { } catch (ClassCastException e) {
// no-op, just assert setTutorialShownCountIncrement() never be called // no-op, just assert setTutorialShownCountIncrement() never be called
} }
verify(mSpiedTutorialHandler, never()).setTutorialShownCountIncrement(); verify(mSpiedTutorialHandler, never()).createViewAndAttachToWindow(any());
} }
@Test @Test
public void testOnConfigurationChanged_shouldUpdateViewContent() { public void testOnConfigurationChanged_shouldUpdateViewContent() {
when(mSpiedTutorialHandler.canShowTutorial()).thenReturn(true); when(mSpiedTutorialHandler.isShowing()).thenReturn(true);
try { try {
mSpiedTutorialHandler.onStateChanged(STATE_ENTERING); mSpiedTutorialHandler.onStateChanged(STATE_ENTERING);
} catch (ClassCastException e) { } catch (ClassCastException e) {