Loading libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java +5 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -203,8 +204,10 @@ public class OneHandedAnimationController { } OneHandedTransitionAnimator addOneHandedAnimationCallback( OneHandedAnimationCallback callback) { @Nullable OneHandedAnimationCallback callback) { if (callback != null) { mOneHandedAnimationCallbacks.add(callback); } return this; } Loading libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +9 −7 Original line number Diff line number Diff line Loading @@ -217,7 +217,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> 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, Loading Loading @@ -299,6 +299,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController> mAccessibilityManager = AccessibilityManager.getInstance(context); mAccessibilityManager.addAccessibilityStateChangeListener( mAccessibilityStateChangeListener); mState.addSListeners(mTutorialHandler); } public OneHanded asOneHanded() { Loading Loading @@ -627,13 +629,13 @@ public class OneHandedController implements RemoteCallable<OneHandedController> } private void onConfigChanged(Configuration newConfig) { if (mTutorialHandler != null) { if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { if (mTutorialHandler == null) { return; } mTutorialHandler.onConfigurationChanged(newConfig); if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { return; } mTutorialHandler.onConfigurationChanged(); } private void onUserSwitch(int newUserId) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java +33 −10 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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. */ Loading @@ -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. */ Loading @@ -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. Loading @@ -135,10 +135,31 @@ public final class OneHandedSettingsUtil { Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 0, userId) == 1; } /** * Queries tutorial shown counts from Settings provider. Default is 0. * * @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 * Sets one handed activated or not to notify state for shortcut. * * @return activated or not * @return true if one handed mode is activated. */ public boolean getOneHandedModeActivated(ContentResolver resolver, int userId) { return Settings.Secure.getIntForUser(resolver, Loading @@ -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, Loading @@ -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() { Loading libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java +29 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading @@ -54,9 +56,18 @@ public class OneHandedState { private static final String TAG = OneHandedState.class.getSimpleName(); private List<OnStateChangedListener> 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() { Loading Loading @@ -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. */ Loading @@ -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) {} } } libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java +100 −118 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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; 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; } @Override public void onStopFinished(Rect bounds) { updateFinished(View.INVISIBLE, -mTargetViewContainer.getHeight()); removeTutorialFromWindowManager(); mTriggerState = ONE_HANDED_TRIGGER_STATE.UNSET; } /** * 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) { Loading @@ -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); @VisibleForTesting void createViewAndAttachToWindow(Context context) { if (!canShowTutorial()) { return; } 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); attachTargetToWindow(); } private void updateFinished(int visible, float finalPosition) { @VisibleForTesting boolean setTutorialShownCountIncrement() { if (!canShowTutorial()) { return; } mIsOneHandedMode = (finalPosition == 0f) ? true : false; mTargetViewContainer.setVisibility(visible); mTargetViewContainer.setTranslationY(finalPosition); return false; } 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); 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()); Loading @@ -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 */; } /** Loading @@ -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); } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java +5 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -203,8 +204,10 @@ public class OneHandedAnimationController { } OneHandedTransitionAnimator addOneHandedAnimationCallback( OneHandedAnimationCallback callback) { @Nullable OneHandedAnimationCallback callback) { if (callback != null) { mOneHandedAnimationCallbacks.add(callback); } return this; } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +9 −7 Original line number Diff line number Diff line Loading @@ -217,7 +217,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> 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, Loading Loading @@ -299,6 +299,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController> mAccessibilityManager = AccessibilityManager.getInstance(context); mAccessibilityManager.addAccessibilityStateChangeListener( mAccessibilityStateChangeListener); mState.addSListeners(mTutorialHandler); } public OneHanded asOneHanded() { Loading Loading @@ -627,13 +629,13 @@ public class OneHandedController implements RemoteCallable<OneHandedController> } private void onConfigChanged(Configuration newConfig) { if (mTutorialHandler != null) { if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { if (mTutorialHandler == null) { return; } mTutorialHandler.onConfigurationChanged(newConfig); if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { return; } mTutorialHandler.onConfigurationChanged(); } private void onUserSwitch(int newUserId) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java +33 −10 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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. */ Loading @@ -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. */ Loading @@ -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. Loading @@ -135,10 +135,31 @@ public final class OneHandedSettingsUtil { Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 0, userId) == 1; } /** * Queries tutorial shown counts from Settings provider. Default is 0. * * @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 * Sets one handed activated or not to notify state for shortcut. * * @return activated or not * @return true if one handed mode is activated. */ public boolean getOneHandedModeActivated(ContentResolver resolver, int userId) { return Settings.Secure.getIntForUser(resolver, Loading @@ -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, Loading @@ -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() { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java +29 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading @@ -54,9 +56,18 @@ public class OneHandedState { private static final String TAG = OneHandedState.class.getSimpleName(); private List<OnStateChangedListener> 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() { Loading Loading @@ -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. */ Loading @@ -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) {} } }
libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java +100 −118 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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; 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; } @Override public void onStopFinished(Rect bounds) { updateFinished(View.INVISIBLE, -mTargetViewContainer.getHeight()); removeTutorialFromWindowManager(); mTriggerState = ONE_HANDED_TRIGGER_STATE.UNSET; } /** * 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) { Loading @@ -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); @VisibleForTesting void createViewAndAttachToWindow(Context context) { if (!canShowTutorial()) { return; } 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); attachTargetToWindow(); } private void updateFinished(int visible, float finalPosition) { @VisibleForTesting boolean setTutorialShownCountIncrement() { if (!canShowTutorial()) { return; } mIsOneHandedMode = (finalPosition == 0f) ? true : false; mTargetViewContainer.setVisibility(visible); mTargetViewContainer.setTranslationY(finalPosition); return false; } 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); 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()); Loading @@ -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 */; } /** Loading @@ -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); } }