From 966382b0c9b9ff39f3d254b24bd53542810f9e0f Mon Sep 17 00:00:00 2001 From: Jason Chang Date: Thu, 8 Jul 2021 22:19:38 +0800 Subject: [PATCH] Apply theme color and fade-in transition effect at the top of One-handed mode 1. Implement the Dynamic color theme at the top Tutorial area but the color should not be the same as the recents UI. 2. Do not translate Tutorial icon & text vertically, then provide quick subtle fade-in effect for Tutorial window. 3. Apply new icon for tutorial. Bug: 193126258 Bug: 193589897 Test: Local verify when changing dark theme and wallpaper theme. Test: Local verify Tutorial icon & text fade-in effect. Test: atest SystemUITests Test: atest WMShellTests Test: Perfetto check enter & exit performance Change-Id: I60f4e7a709f3a27fe6c7f480f1012caccdbbe5ec --- .../one_handed_tutorial_background_color.xml | 21 ++++ .../res/drawable-hdpi/one_handed_tutorial.png | Bin 1766 -> 0 bytes .../res/drawable/one_handed_tutorial_icon.xml | 14 +++ .../Shell/res/layout/one_handed_tutorial.xml | 7 +- .../OneHandedBackgroundPanelOrganizer.java | 37 +++++-- .../shell/onehanded/OneHandedController.java | 3 +- .../onehanded/OneHandedTutorialHandler.java | 102 ++++++++++++++++-- 7 files changed, 165 insertions(+), 19 deletions(-) create mode 100644 libs/WindowManager/Shell/res/color/one_handed_tutorial_background_color.xml delete mode 100644 libs/WindowManager/Shell/res/drawable-hdpi/one_handed_tutorial.png create mode 100644 libs/WindowManager/Shell/res/drawable/one_handed_tutorial_icon.xml diff --git a/libs/WindowManager/Shell/res/color/one_handed_tutorial_background_color.xml b/libs/WindowManager/Shell/res/color/one_handed_tutorial_background_color.xml new file mode 100644 index 0000000000000..4f56e0f023a42 --- /dev/null +++ b/libs/WindowManager/Shell/res/color/one_handed_tutorial_background_color.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/libs/WindowManager/Shell/res/drawable-hdpi/one_handed_tutorial.png b/libs/WindowManager/Shell/res/drawable-hdpi/one_handed_tutorial.png deleted file mode 100644 index 6c1f1cfdea7cc9367167f2843f88c2b75af87898..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1766 zcmVPx*ph-kQRCodHn`?+wRTRhPHq&Dyn;x2v%(Q}t3bJgHz_b(!`q0xJ(nHLGBzq3| zAqt5?5Tz)jqKFujFs(jBnL&kQ(F1#6sWdr84N>;c%o?R}rr-b0*~7Z$o_p^(=bn9= z;4Jvxz4qE`t-aQM-Luc!DKr@x7#J8sbs?AyP6Fe=A;1Ui$LSAX2j~Rr3WdTyl3o^iP7R3h8tUA;Wy20-OPmwtthR5;12K_blDT!u+;G4l$z|pHP za@-K2uj$}>*E!{)L>*%=!x(Rc*T^)K=<{L|!)J*)$8ec3zBiF^hRVdyZQ{^j5{q=g zoMkjO8NSi%r>4Wx4PTX20w)`dF2gsP)uG{Nl4EL>N?>H5)W`1MQ_H|(`)=cVgyE~Q zO5lh<LV;|*8U^r6o2_a5k z!!?08u@`mn1Y!@BC&hdQJPmAYxA_dj9x6|Y`3!g(*w}9K8Hhbpo)q&L@HDWo-R3h8 zd#F4q<}=`FU}L-e-_JlxrBr!KrsVX>r4_iBzLnII$Oll?YxKRm#yOD0FiJNv0fJZH zlR@mub(Hg3GWz)iogNag7y4ReCGBMi;B*uklS;JwRYv1_Bz(g^Oib%6 zrJY`4O-N~5_U9x0U7#eXw2l+nX(q~6LmVIBV2_GpqOgsIDtcLV}torCtWy! z2FF~|UF^4U)@nEz}qBoOtHp? zuNgSSbwIhq@dVTZuO!^>;xwAe!E6vo*sP>Mf$MjsBH4dH5))PNm4HqVk&e!TMK1Ut zpkHAx0`CVR-NPRP?Lob)z~(iCv%qgbqkfV$U%V1lqT9Gp!1hkjx zC18CZRvgy`d}M*mhcsw8m5`l*EED+H^l~H9?FMN`OhCOM?mslMkLV>Zlhs^XwevBdU*!k{R;9-_*DdCm#k4` zvlI#)2p%%Ro8i}D(_&<0tQ`IvNHtQeWg6)Pe_;9m&@vI}DAc)Mkx1fE8t(#bSzif% zbwvXjl)y(u;T-tCO}$?Ba~v!+P8KqvQ%${$B?%=t4`?+g`JJF5DSm%^TgRnbm=L$X)^MLEBhL!3Sw?WJX#-nz9~>XBRZpUWC}9AxsPS#JW? zK78B`cnD)l7bowK3q-$hFLP_WKlTJCJ+=Z;{q&pFqFu5fVFM}U?Hc~wzjgOE2p_sUt#cLio;h2{B^5M0zQ3&rP~h=(UG7rfOXPxDG0} z0Q-H=#w@smtZo+kgYs=VtIH|A_TwP(;$D~I>c$7MOu&t_jzah9uzL6DTlGMAWP0-m zOHF4$n + + + diff --git a/libs/WindowManager/Shell/res/layout/one_handed_tutorial.xml b/libs/WindowManager/Shell/res/layout/one_handed_tutorial.xml index 0190aad2d0ef2..d29ed8b5a9eca 100644 --- a/libs/WindowManager/Shell/res/layout/one_handed_tutorial.xml +++ b/libs/WindowManager/Shell/res/layout/one_handed_tutorial.xml @@ -31,7 +31,7 @@ android:layout_marginTop="6dp" android:layout_marginBottom="0dp" android:gravity="center_horizontal" - android:src="@drawable/one_handed_tutorial" + android:src="@drawable/one_handed_tutorial_icon" android:scaleType="centerInside" /> displayLayout.width()) { mBkgBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height()); } else { mBkgBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width()); } - final int defaultColor = ContextCompat.getColor(context, R.color.GM2_grey_800); - mDefaultColor = new float[]{Color.red(defaultColor) / 255.0f, - Color.green(defaultColor) / 255.0f, Color.blue(defaultColor) / 255.0f}; + updateThemeColors(); mMainExecutor = executor; mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; } @@ -170,7 +170,6 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer } if (getBackgroundSurface() == null) { - Log.w(TAG, "mBackgroundSurface is null !"); return; } @@ -201,6 +200,30 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer } } + /** + * onConfigurationChanged events for updating tutorial text. + */ + public void onConfigurationChanged() { + synchronized (mLock) { + if (mBackgroundSurface == null) { + getBackgroundSurface(); + } else { + removeBackgroundPanelLayer(); + } + updateThemeColors(); + showBackgroundPanelLayer(); + } + } + + private void updateThemeColors() { + synchronized (mLock) { + final int themeColor = mContext.getColor(R.color.one_handed_tutorial_background_color); + mDefaultColor = new float[]{(Color.red(themeColor) - THEME_COLOR_OFFSET) / 255.0f, + (Color.green(themeColor) - THEME_COLOR_OFFSET) / 255.0f, + (Color.blue(themeColor) - THEME_COLOR_OFFSET) / 255.0f}; + } + } + void dump(@NonNull PrintWriter pw) { final String innerPrefix = " "; pw.println(TAG); 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 2038cff4a9660..09cde38a0cfc9 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 @@ -658,12 +658,13 @@ public class OneHandedController implements RemoteCallable } private void onConfigChanged(Configuration newConfig) { - if (mTutorialHandler == null) { + if (mTutorialHandler == null || mBackgroundPanelOrganizer == null) { return; } if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { return; } + mBackgroundPanelOrganizer.onConfigurationChanged(); mTutorialHandler.onConfigurationChanged(); } 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 d0206a4e3dbfd..97e04b5a7abda 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 @@ -25,8 +25,11 @@ 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.animation.ValueAnimator; import android.annotation.Nullable; import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.SystemProperties; @@ -35,9 +38,13 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import android.view.animation.LinearInterpolator; import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.appcompat.view.ContextThemeWrapper; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.R; @@ -53,11 +60,14 @@ import java.io.PrintWriter; 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 String OFFSET_PERCENTAGE = "persist.debug.one_handed_offset_percentage"; + private static final String TRANSLATE_ANIMATION_DURATION = + "persist.debug.one_handed_translate_animation_duration"; + private static final float START_TRANSITION_FRACTION = 0.7f; private final float mTutorialHeightRatio; private final WindowManager mWindowManager; + private final OneHandedAnimationCallback mAnimationCallback; private @OneHandedState.State int mCurrentState; private int mTutorialAreaHeight; @@ -67,7 +77,9 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, private @Nullable View mTutorialView; private @Nullable ViewGroup mTargetViewContainer; - private final OneHandedAnimationCallback mAnimationCallback; + private float mAlphaTransitionStart; + private ValueAnimator mAlphaAnimator; + private int mAlphaAnimationDurationMs; public OneHandedTutorialHandler(Context context, WindowManager windowManager) { mContext = context; @@ -75,15 +87,35 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, 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)); + OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f)); mTutorialHeightRatio = sysPropPercentageConfig / 100.0f; + final int animationDuration = context.getResources().getInteger( + R.integer.config_one_handed_translate_animation_duration); + mAlphaAnimationDurationMs = SystemProperties.getInt(TRANSLATE_ANIMATION_DURATION, + animationDuration); mAnimationCallback = new OneHandedAnimationCallback() { + @Override + public void onOneHandedAnimationCancel( + OneHandedAnimationController.OneHandedTransitionAnimator animator) { + if (mAlphaAnimator != null) { + mAlphaAnimator.cancel(); + } + } + @Override public void onAnimationUpdate(float xPos, float yPos) { if (!isAttached()) { return; } - mTargetViewContainer.setTranslationY(yPos - mTutorialAreaHeight); + if (yPos < mAlphaTransitionStart) { + checkTransitionEnd(); + return; + } + if (mAlphaAnimator == null || mAlphaAnimator.isStarted() + || mAlphaAnimator.isRunning()) { + return; + } + mAlphaAnimator.start(); } }; } @@ -94,12 +126,16 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, switch (newState) { case STATE_ENTERING: createViewAndAttachToWindow(mContext); + updateThemeColor(); + setupAlphaTransition(true /* isEntering */); break; case STATE_ACTIVE: - case STATE_EXITING: - // no - op + checkTransitionEnd(); + setupAlphaTransition(false /* isEntering */); break; + case STATE_EXITING: case STATE_NONE: + checkTransitionEnd(); removeTutorialFromWindowManager(); break; default: @@ -119,6 +155,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, mDisplayBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width()); } mTutorialAreaHeight = Math.round(mDisplayBounds.height() * mTutorialHeightRatio); + mAlphaTransitionStart = mTutorialAreaHeight * START_TRANSITION_FRACTION; } @VisibleForTesting @@ -129,6 +166,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, mTutorialView = LayoutInflater.from(context).inflate(R.layout.one_handed_tutorial, null); mTargetViewContainer = new FrameLayout(context); mTargetViewContainer.setClipChildren(false); + mTargetViewContainer.setAlpha(mCurrentState == STATE_ACTIVE ? 1.0f : 0.0f); mTargetViewContainer.addView(mTutorialView); mTargetViewContainer.setLayerType(LAYER_TYPE_HARDWARE, null); @@ -192,6 +230,52 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, removeTutorialFromWindowManager(); if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) { createViewAndAttachToWindow(mContext); + updateThemeColor(); + checkTransitionEnd(); + } + } + + private void updateThemeColor() { + if (mTutorialView == null) { + return; + } + + final Context themedContext = new ContextThemeWrapper(mTutorialView.getContext(), + com.android.internal.R.style.Theme_DeviceDefault_DayNight); + final int textColorPrimary; + final int themedTextColorSecondary; + TypedArray ta = themedContext.obtainStyledAttributes(new int[]{ + com.android.internal.R.attr.textColorPrimary, + com.android.internal.R.attr.textColorSecondary}); + textColorPrimary = ta.getColor(0, 0); + themedTextColorSecondary = ta.getColor(1, 0); + ta.recycle(); + + final ImageView iconView = mTutorialView.findViewById(R.id.one_handed_tutorial_image); + iconView.setImageTintList(ColorStateList.valueOf(textColorPrimary)); + + final TextView tutorialTitle = mTutorialView.findViewById(R.id.one_handed_tutorial_title); + final TextView tutorialDesc = mTutorialView.findViewById( + R.id.one_handed_tutorial_description); + tutorialTitle.setTextColor(textColorPrimary); + tutorialDesc.setTextColor(themedTextColorSecondary); + } + + private void setupAlphaTransition(boolean isEntering) { + final float start = isEntering ? 0.0f : 1.0f; + final float end = isEntering ? 1.0f : 0.0f; + mAlphaAnimator = ValueAnimator.ofFloat(start, end); + mAlphaAnimator.setInterpolator(new LinearInterpolator()); + mAlphaAnimator.setDuration(mAlphaAnimationDurationMs); + mAlphaAnimator.addUpdateListener( + animator -> mTargetViewContainer.setAlpha((float) animator.getAnimatedValue())); + } + + private void checkTransitionEnd() { + if (mAlphaAnimator != null && mAlphaAnimator.isRunning()) { + mAlphaAnimator.end(); + mAlphaAnimator.removeAllUpdateListeners(); + mAlphaAnimator = null; } } @@ -206,5 +290,9 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, pw.println(mDisplayBounds); pw.print(innerPrefix + "mTutorialAreaHeight="); pw.println(mTutorialAreaHeight); + pw.print(innerPrefix + "mAlphaTransitionStart="); + pw.println(mAlphaTransitionStart); + pw.print(innerPrefix + "mAlphaAnimationDurationMs="); + pw.println(mAlphaAnimationDurationMs); } }