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
This commit is contained in:
Jason Chang
2021-07-08 22:19:38 +08:00
parent e8ad8b7ff4
commit 966382b0c9
7 changed files with 165 additions and 19 deletions

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<item android:color="?androidprv:attr/colorSurfaceVariant"/>
</selector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="60dp"
android:viewportWidth="32"
android:viewportHeight="60">
<path
android:pathData="M1.9703,30.5041C1.9703,28.295 3.7612,26.5042 5.9703,26.5042H25.5551C27.7642,26.5042 29.5551,28.295 29.5551,30.5042V54.0296C29.5551,56.2387 27.7642,58.0296 25.5551,58.0296H5.9703C3.7612,58.0296 1.9703,56.2387 1.9703,54.0296V30.5041Z"
android:fillColor="#000000"
android:fillAlpha="0.16"/>
<path
android:pathData="M25.5254,2H6C3.7909,2 2,3.7909 2,6V54C2,56.2091 3.7909,58 6,58H25.5254C27.7346,58 29.5254,56.2091 29.5254,54V6C29.5254,3.7909 27.7346,2 25.5254,2ZM6,0C2.6863,0 0,2.6863 0,6V54C0,57.3137 2.6863,60 6,60H25.5254C28.8391,60 31.5254,57.3137 31.5254,54V6C31.5254,2.6863 28.8391,0 25.5254,0H6ZM12.2034,47.2336L12.8307,47.861L15.3178,45.3783V52.1277H16.2076V45.3783L18.6903,47.8654L19.322,47.2336L15.7627,43.6743L12.2034,47.2336ZM19.7034,55.0742H11.822V56.552H19.7034V55.0742Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</vector>

View File

@@ -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" />
<TextView
@@ -45,7 +45,6 @@
android:fontFamily="google-sans-medium"
android:text="@string/one_handed_tutorial_title"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@android:color/white"/>
<TextView
@@ -54,8 +53,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginBottom="0dp"
android:layout_marginStart="46dp"
android:layout_marginEnd="46dp"
android:layout_marginStart="60dp"
android:layout_marginEnd="60dp"
android:gravity="center_horizontal"
android:fontFamily="roboto-regular"
android:text="@string/one_handed_tutorial_description"

View File

@@ -20,7 +20,6 @@ import android.content.Context;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.DisplayAreaAppearedInfo;
@@ -30,7 +29,6 @@ import android.window.DisplayAreaOrganizer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.content.ContextCompat;
import com.android.internal.annotations.GuardedBy;
import com.android.wm.shell.R;
@@ -48,14 +46,17 @@ import java.util.concurrent.Executor;
public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
implements OneHandedTransitionCallback {
private static final String TAG = "OneHandedBackgroundPanelOrganizer";
private static final int THEME_COLOR_OFFSET = 10;
private final Context mContext;
private final Object mLock = new Object();
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final float[] mDefaultColor;
private final Executor mMainExecutor;
private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
private float[] mDefaultColor;
/**
* The background to distinguish the boundary of translated windows and empty region when
* one handed mode triggered.
@@ -88,15 +89,14 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
public OneHandedBackgroundPanelOrganizer(Context context, DisplayLayout displayLayout,
Executor executor) {
super(executor);
mContext = context;
// Ensure the mBkgBounds is portrait, due to OHM only support on portrait
if (displayLayout.height() > displayLayout.width()) {
mBkgBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height());
} else {
mBkgBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width());
}
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);

View File

@@ -658,12 +658,13 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
}
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();
}

View File

@@ -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);
}
}