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:
@@ -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 |
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user