diff --git a/packages/SystemUI/res/drawable/corner_gesture_hint.xml b/packages/SystemUI/res/drawable/corner_gesture_hint.xml deleted file mode 100644 index 3f4abb0e0dd4f..0000000000000 --- a/packages/SystemUI/res/drawable/corner_gesture_hint.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - diff --git a/packages/SystemUI/res/layout/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml index 02651a2128168..b409c8f2ba5aa 100644 --- a/packages/SystemUI/res/layout/rounded_corners.xml +++ b/packages/SystemUI/res/layout/rounded_corners.xml @@ -18,36 +18,30 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> + + + android:src="@drawable/rounded"/> - - + android:src="@drawable/rounded"/> diff --git a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java new file mode 100644 index 0000000000000..528db5acc86e8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2019 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. + */ + +package com.android.systemui; + +import android.animation.ArgbEvaluator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.SystemProperties; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.ContextThemeWrapper; +import android.view.View; + +import com.android.settingslib.Utils; + +/** + * CornerHandleView draws an inset arc intended to be displayed within the screen decoration + * corners. + */ +public class CornerHandleView extends View { + private static final boolean ALLOW_TUNING = false; + private static final int ANGLE_DEGREES = 50; + public static final int MARGIN_DP = 11; + public static final int RADIUS_DP = 37; + public static final float STROKE_DP = 2.5f; + + private Paint mPaint; + private int mLightColor; + private int mDarkColor; + private RectF mOval; + + + public CornerHandleView(Context context, AttributeSet attrs) { + super(context, attrs); + + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeCap(Paint.Cap.ROUND); + mPaint.setStrokeWidth(getStrokePx()); + + final int dualToneDarkTheme = Utils.getThemeAttr(mContext, + R.attr.darkIconTheme); + final int dualToneLightTheme = Utils.getThemeAttr(mContext, + R.attr.lightIconTheme); + Context lightContext = new ContextThemeWrapper(mContext, dualToneLightTheme); + Context darkContext = new ContextThemeWrapper(mContext, dualToneDarkTheme); + mLightColor = Utils.getColorAttrDefaultColor(lightContext, + R.attr.singleToneColor); + mDarkColor = Utils.getColorAttrDefaultColor(darkContext, + R.attr.singleToneColor); + + updateOval(); + } + + /** + * Receives an intensity from 0 (lightest) to 1 (darkest) and sets the handle color + * approriately. Intention is to match the home handle color. + */ + public void updateDarkness(float darkIntensity) { + mPaint.setColor((int) ArgbEvaluator.getInstance().evaluate(darkIntensity, + mLightColor, + mDarkColor)); + invalidate(); + } + + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (ALLOW_TUNING) { + mPaint.setStrokeWidth(getStrokePx()); + updateOval(); + } + + canvas.drawArc(mOval, 180 + ((90 - getAngle()) / 2), getAngle(), false, + mPaint); + } + + // TODO(b/133834204): Remove tweaking of corner handles + private static float convertDpToPixel(float dp, Context context) { + return dp * ((float) context.getResources().getDisplayMetrics().densityDpi + / DisplayMetrics.DENSITY_DEFAULT); + } + + private void updateOval() { + mOval = new RectF(getMarginPx() - (getStrokePx() / 2.f), + getMarginPx() - (getStrokePx() / 2.f), + getMarginPx() + (2 * (getRadiusPx()) + (getStrokePx() / 2.f)), + getMarginPx() + 2 * getRadiusPx() + (getStrokePx() / 2.f)); + } + + private int getAngle() { + if (ALLOW_TUNING) { + return SystemProperties.getInt("CORNER_HANDLE_ANGLE_DEGREES", ANGLE_DEGREES); + } else { + return ANGLE_DEGREES; + } + } + + private int getMarginPx() { + if (ALLOW_TUNING) { + return SystemProperties.getInt("CORNER_HANDLE_MARGIN_PX", + (int) convertDpToPixel(MARGIN_DP, getContext())); + } else { + return (int) convertDpToPixel(MARGIN_DP, getContext()); + } + } + + private int getRadiusPx() { + if (ALLOW_TUNING) { + return SystemProperties.getInt("CORNER_HANDLE_RADIUS_PX", + (int) convertDpToPixel(RADIUS_DP, getContext())); + } else { + return (int) convertDpToPixel(RADIUS_DP, getContext()); + } + } + + private int getStrokePx() { + if (ALLOW_TUNING) { + return SystemProperties.getInt("CORNER_HANDLE_STROKE_PX", + (int) convertDpToPixel(STROKE_DP, getContext())); + } else { + return (int) convertDpToPixel(STROKE_DP, getContext()); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 390a9e65c1b4a..6057e124b105f 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -60,6 +60,12 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; import android.view.WindowManager; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.OvershootInterpolator; +import android.view.animation.PathInterpolator; +import android.view.animation.ScaleAnimation; +import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; import android.widget.ImageView; @@ -72,6 +78,7 @@ import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.SecureSetting; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; +import com.android.systemui.statusbar.phone.NavigationBarTransitions; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.tuner.TunablePadding; import com.android.systemui.tuner.TunerService; @@ -85,7 +92,8 @@ import java.util.List; * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout) * for antialiasing and emulation purposes. */ -public class ScreenDecorations extends SystemUI implements Tunable { +public class ScreenDecorations extends SystemUI implements Tunable, + NavigationBarTransitions.DarkIntensityListener { private static final boolean DEBUG = false; private static final String TAG = "ScreenDecorations"; @@ -93,13 +101,17 @@ public class ScreenDecorations extends SystemUI implements Tunable { public static final String PADDING = "sysui_rounded_content_padding"; private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS = SystemProperties.getBoolean("debug.screenshot_rounded_corners", false); + private static final boolean VERBOSE = false; private DisplayManager mDisplayManager; private DisplayManager.DisplayListener mDisplayListener; - @VisibleForTesting protected int mRoundedDefault; - @VisibleForTesting protected int mRoundedDefaultTop; - @VisibleForTesting protected int mRoundedDefaultBottom; + @VisibleForTesting + protected int mRoundedDefault; + @VisibleForTesting + protected int mRoundedDefaultTop; + @VisibleForTesting + protected int mRoundedDefaultBottom; private View mOverlay; private View mBottomOverlay; private float mDensity; @@ -111,6 +123,8 @@ public class ScreenDecorations extends SystemUI implements Tunable { private SecureSetting mColorInversionSetting; private boolean mPendingRotationChange; private Handler mHandler; + private boolean mAssistHintBlocked = false; + private boolean mIsReceivingNavBarColor = false; /** * Converts a set of {@link Rect}s into a {@link Region} @@ -137,15 +151,32 @@ public class ScreenDecorations extends SystemUI implements Tunable { putComponent(ScreenDecorations.class, this); } - private void fade(View view, boolean fadeIn) { + private void fade(View view, boolean fadeIn, boolean isLeft) { if (fadeIn) { view.animate().cancel(); - view.setAlpha(0f); + view.setAlpha(1f); view.setVisibility(View.VISIBLE); - view.animate().alpha(1f); + + AnimationSet anim = new AnimationSet(true); + anim.setDuration(900); + + Animation scaleAnimation = new ScaleAnimation(2f, 1f, 2f, 1f, + ScaleAnimation.RELATIVE_TO_SELF, 0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f); + anim.addAnimation(scaleAnimation); + anim.setInterpolator(new PathInterpolator(0.02f, 0.44f, 0.67f, 1.00f)); + + Animation translateAnimation = new TranslateAnimation( + TranslateAnimation.RELATIVE_TO_SELF, isLeft ? -0.2f : 0.2f, + TranslateAnimation.RELATIVE_TO_SELF, + 0f, + TranslateAnimation.RELATIVE_TO_SELF, 0.2f, TranslateAnimation.RELATIVE_TO_SELF, + 0f); + anim.addAnimation(translateAnimation); + anim.setInterpolator(new OvershootInterpolator()); + view.startAnimation(anim); } else { view.animate().cancel(); - view.animate().alpha(0f).withEndAction(() -> view.setVisibility(View.INVISIBLE)); + view.animate().setDuration(400).alpha(0f); } } @@ -161,35 +192,59 @@ public class ScreenDecorations extends SystemUI implements Tunable { return; } + if (mAssistHintBlocked && visible) { + if (VERBOSE) { + Log.v(TAG, "Assist hint blocked, cannot make it visible"); + } + return; + } + if (mAssistHintVisible != visible) { mAssistHintVisible = visible; - View assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left); - View assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right); - View assistHintBottomLeft = mBottomOverlay.findViewById(R.id.assist_hint_left); - View assistHintBottomRight = mBottomOverlay.findViewById(R.id.assist_hint_right); + CornerHandleView assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left); + CornerHandleView assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right); + CornerHandleView assistHintBottomLeft = mBottomOverlay.findViewById( + R.id.assist_hint_left); + CornerHandleView assistHintBottomRight = mBottomOverlay.findViewById( + R.id.assist_hint_right); switch (mRotation) { case RotationUtils.ROTATION_NONE: - fade(assistHintBottomLeft, mAssistHintVisible); - fade(assistHintBottomRight, mAssistHintVisible); + fade(assistHintBottomLeft, mAssistHintVisible, /* isLeft = */ true); + fade(assistHintBottomRight, mAssistHintVisible, /* isLeft = */ false); break; case RotationUtils.ROTATION_LANDSCAPE: - fade(assistHintTopRight, mAssistHintVisible); - fade(assistHintBottomRight, mAssistHintVisible); + fade(assistHintTopRight, mAssistHintVisible, /* isLeft = */ true); + fade(assistHintBottomRight, mAssistHintVisible, /* isLeft = */ false); break; case RotationUtils.ROTATION_SEASCAPE: - fade(assistHintTopLeft, mAssistHintVisible); - fade(assistHintBottomLeft, mAssistHintVisible); + fade(assistHintTopLeft, mAssistHintVisible, /* isLeft = */ false); + fade(assistHintBottomLeft, mAssistHintVisible, /* isLeft = */ true); break; case RotationUtils.ROTATION_UPSIDE_DOWN: - fade(assistHintTopLeft, mAssistHintVisible); - fade(assistHintTopRight, mAssistHintVisible); + fade(assistHintTopLeft, mAssistHintVisible, /* isLeft = */ false); + fade(assistHintTopRight, mAssistHintVisible, /* isLeft = */ true); break; } } } + /** + * Prevents the assist hint from becoming visible even if `mAssistHintVisible` is true. + */ + public void setAssistHintBlocked(boolean blocked) { + if (!mHandler.getLooper().isCurrentThread()) { + mHandler.post(() -> setAssistHintBlocked(blocked)); + return; + } + + mAssistHintBlocked = blocked; + if (mAssistHintVisible && mAssistHintBlocked) { + setAssistHintVisible(false); + } + } + @VisibleForTesting Handler startHandlerThread() { HandlerThread thread = new HandlerThread("ScreenDecorations"); @@ -253,12 +308,12 @@ public class ScreenDecorations extends SystemUI implements Tunable { .inflate(R.layout.rounded_corners, null); mCutoutTop = new DisplayCutoutView(mContext, true, this::updateWindowVisibilities, this); - ((ViewGroup)mOverlay).addView(mCutoutTop); + ((ViewGroup) mOverlay).addView(mCutoutTop); mBottomOverlay = LayoutInflater.from(mContext) .inflate(R.layout.rounded_corners, null); mCutoutBottom = new DisplayCutoutView(mContext, false, this::updateWindowVisibilities, this); - ((ViewGroup)mBottomOverlay).addView(mCutoutBottom); + ((ViewGroup) mBottomOverlay).addView(mCutoutBottom); mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); mOverlay.setAlpha(0); @@ -416,7 +471,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { } else if (mRotation == RotationUtils.ROTATION_LANDSCAPE) { updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0); updateView(topRight, Gravity.BOTTOM | Gravity.LEFT, 270); - updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90);; + updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90); updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180); } else if (mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) { updateView(topLeft, Gravity.BOTTOM | Gravity.LEFT, 270); @@ -477,7 +532,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { } private void updateView(View v, int gravity, int rotation) { - ((FrameLayout.LayoutParams)v.getLayoutParams()).gravity = gravity; + ((FrameLayout.LayoutParams) v.getLayoutParams()).gravity = gravity; v.setRotation(rotation); } @@ -613,6 +668,10 @@ public class ScreenDecorations extends SystemUI implements Tunable { setSize(mOverlay.findViewById(R.id.right), sizeTop); setSize(mBottomOverlay.findViewById(R.id.left), sizeBottom); setSize(mBottomOverlay.findViewById(R.id.right), sizeBottom); + setSize(mOverlay.findViewById(R.id.assist_hint_left), sizeTop * 2); + setSize(mOverlay.findViewById(R.id.assist_hint_right), sizeTop * 2); + setSize(mBottomOverlay.findViewById(R.id.assist_hint_left), sizeBottom * 2); + setSize(mBottomOverlay.findViewById(R.id.assist_hint_right), sizeBottom * 2); } }); } @@ -624,6 +683,27 @@ public class ScreenDecorations extends SystemUI implements Tunable { view.setLayoutParams(params); } + @Override + public void onDarkIntensity(float darkIntensity) { + if (mOverlay != null) { + CornerHandleView assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left); + CornerHandleView assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right); + + assistHintTopLeft.updateDarkness(darkIntensity); + assistHintTopRight.updateDarkness(darkIntensity); + } + + if (mBottomOverlay != null) { + CornerHandleView assistHintBottomLeft = mBottomOverlay.findViewById( + R.id.assist_hint_left); + CornerHandleView assistHintBottomRight = mBottomOverlay.findViewById( + R.id.assist_hint_right); + + assistHintBottomLeft.updateDarkness(darkIntensity); + assistHintBottomRight.updateDarkness(darkIntensity); + } + } + @VisibleForTesting static class TunablePaddingTagListener implements FragmentListener { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 337c6b167cdf9..d94a33556a430 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -86,6 +86,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.LatencyTracker; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.ScreenDecorations; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.assist.AssistManager; import com.android.systemui.fragments.FragmentHostManager; @@ -170,6 +171,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback public int mDisplayId; private boolean mIsOnDefaultDisplay; public boolean mHomeBlockedThisTouch; + private ScreenDecorations mScreenDecorations; private Handler mHandler = Dependency.get(Dependency.MAIN_HANDLER); @@ -348,12 +350,17 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mDisabledFlags2 |= StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS; } setDisabled2Flags(mDisabledFlags2); + + mScreenDecorations = SysUiServiceProvider.getComponent(getContext(), + ScreenDecorations.class); + getBarTransitions().addDarkIntensityListener(mScreenDecorations); } @Override public void onDestroyView() { super.onDestroyView(); if (mNavigationBarView != null) { + mNavigationBarView.getBarTransitions().removeDarkIntensityListener(mScreenDecorations); mNavigationBarView.getBarTransitions().destroy(); mNavigationBarView.getLightTransitionsController().destroy(getContext()); } @@ -1020,7 +1027,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback getBarTransitions().transitionTo(barMode, animate); } - private BarTransitions getBarTransitions() { + public NavigationBarTransitions getBarTransitions() { return mNavigationBarView.getBarTransitions(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index 8a28c6fbff295..2b5a28e600828 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -36,9 +36,23 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dependency; import com.android.systemui.R; +import java.util.ArrayList; +import java.util.List; + public final class NavigationBarTransitions extends BarTransitions implements LightBarTransitionsController.DarkIntensityApplier { + /** + * Notified when the color of nav bar elements changes. + */ + public interface DarkIntensityListener { + /** + * Called when the color of nav bar elements changes. + * @param darkIntensity 0 is the lightest color, 1 is the darkest. + */ + void onDarkIntensity(float darkIntensity); + } + private final NavigationBarView mView; private final IStatusBarService mBarService; private final LightBarTransitionsController mLightTransitionsController; @@ -49,6 +63,7 @@ public final class NavigationBarTransitions extends BarTransitions implements private boolean mAutoDim; private View mNavButtons; private int mNavBarMode = NAV_BAR_MODE_3BUTTON; + private List mDarkIntensityListeners; private final Handler mHandler = Handler.getMain(); private final IWallpaperVisibilityListener mWallpaperVisibilityListener = @@ -69,6 +84,7 @@ public final class NavigationBarTransitions extends BarTransitions implements mLightTransitionsController = new LightBarTransitionsController(view.getContext(), this); mAllowAutoDimWallpaperNotVisible = view.getContext().getResources() .getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper); + mDarkIntensityListeners = new ArrayList(); IWindowManager windowManagerService = Dependency.get(IWindowManager.class); try { @@ -168,12 +184,16 @@ public final class NavigationBarTransitions extends BarTransitions implements applyDarkIntensity(mLightTransitionsController.getCurrentDarkIntensity()); } + @Override public void applyDarkIntensity(float darkIntensity) { SparseArray buttonDispatchers = mView.getButtonDispatchers(); for (int i = buttonDispatchers.size() - 1; i >= 0; i--) { buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity); } mView.getRotationButtonController().setDarkIntensity(darkIntensity); + for (DarkIntensityListener listener : mDarkIntensityListeners) { + listener.onDarkIntensity(darkIntensity); + } if (mAutoDim) { applyLightsOut(false, true); } @@ -190,4 +210,18 @@ public final class NavigationBarTransitions extends BarTransitions implements public void onNavigationModeChanged(int mode) { mNavBarMode = mode; } + + /** + * Register {@code listener} to be notified when the color of nav bar elements changes. + */ + public void addDarkIntensityListener(DarkIntensityListener listener) { + mDarkIntensityListeners.add(listener); + } + + /** + * Remove {@code listener} from being notified when the color of nav bar elements changes. + */ + public void removeDarkIntensityListener(DarkIntensityListener listener) { + mDarkIntensityListeners.remove(listener); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 7682e8a594cef..8a895e187b317 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -299,7 +299,7 @@ public class NavigationBarView extends FrameLayout implements return mTintController; } - public BarTransitions getBarTransitions() { + public NavigationBarTransitions getBarTransitions() { return mBarTransitions; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java index 4181d38ad2c57..faf5a97067352 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java @@ -268,7 +268,7 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { when(view.getRecentsButton()).thenReturn(mock(ButtonDispatcher.class)); when(view.getAccessibilityButton()).thenReturn(mock(ButtonDispatcher.class)); when(view.getRotateSuggestionButton()).thenReturn(mock(RotationContextButton.class)); - when(view.getBarTransitions()).thenReturn(mock(BarTransitions.class)); + when(view.getBarTransitions()).thenReturn(mock(NavigationBarTransitions.class)); when(view.getLightTransitionsController()).thenReturn( mock(LightBarTransitionsController.class)); when(view.getRotationButtonController()).thenReturn(