Merge "Move floating rotation button handling to Launcher" into sc-v2-dev am: 1aef5f62a1

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16101761

Change-Id: Idfcc8a6f075aecb1c54ecae4dbb26f4cb6b83039
This commit is contained in:
TreeHugger Robot
2021-11-01 12:50:25 +00:00
committed by Automerger Merge Worker
20 changed files with 460 additions and 218 deletions

View File

@@ -81,7 +81,6 @@
<dimen name="fab_margin">24dp</dimen> <dimen name="fab_margin">24dp</dimen>
<dimen name="navigation_key_width">128dp</dimen> <dimen name="navigation_key_width">128dp</dimen>
<dimen name="navigation_key_padding">25dp</dimen>
<!-- Keyboard shortcuts helper --> <!-- Keyboard shortcuts helper -->
<dimen name="ksh_layout_width">488dp</dimen> <dimen name="ksh_layout_width">488dp</dimen>

View File

@@ -21,11 +21,4 @@
<dimen name="navigation_side_padding">@dimen/button_size</dimen> <dimen name="navigation_side_padding">@dimen/button_size</dimen>
<dimen name="navigation_key_width">@dimen/button_size</dimen> <dimen name="navigation_key_width">@dimen/button_size</dimen>
<dimen name="navigation_extra_key_width">@dimen/button_size</dimen> <dimen name="navigation_extra_key_width">@dimen/button_size</dimen>
<!-- The maximum width of the navigation bar ripples. -->
<dimen name="key_button_ripple_max_width">76dp</dimen>
<!-- The padding around the navigation buttons -->
<dimen name="navigation_key_padding">0dp</dimen>
</resources> </resources>

View File

@@ -56,11 +56,6 @@
<!-- The amount by which the arrow is shifted to avoid the finger--> <!-- The amount by which the arrow is shifted to avoid the finger-->
<dimen name="navigation_edge_finger_offset">48dp</dimen> <dimen name="navigation_edge_finger_offset">48dp</dimen>
<dimen name="floating_rotation_button_diameter">40dp</dimen>
<dimen name="floating_rotation_button_min_margin">20dp</dimen>
<dimen name="floating_rotation_button_taskbar_left_margin">20dp</dimen>
<dimen name="floating_rotation_button_taskbar_bottom_margin">10dp</dimen>
<!-- Height of notification icons in the status bar --> <!-- Height of notification icons in the status bar -->
<dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen> <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
@@ -361,8 +356,6 @@
<!-- The width/height of the icon of a navigation button --> <!-- The width/height of the icon of a navigation button -->
<dimen name="navigation_icon_size">32dp</dimen> <dimen name="navigation_icon_size">32dp</dimen>
<dimen name="navigation_key_padding">0dp</dimen>
<!-- The width of the view containing the menu/ime navigation bar icons --> <!-- The width of the view containing the menu/ime navigation bar icons -->
<dimen name="navigation_extra_key_width">36dp</dimen> <dimen name="navigation_extra_key_width">36dp</dimen>
@@ -974,9 +967,6 @@
<dimen name="signal_indicator_to_icon_frame_spacing">3dp</dimen> <dimen name="signal_indicator_to_icon_frame_spacing">3dp</dimen>
<!-- The maximum width of the navigation bar ripples. -->
<dimen name="key_button_ripple_max_width">95dp</dimen>
<!-- Inset shadow for FakeShadowDrawable. It is used to avoid gaps between the card <!-- Inset shadow for FakeShadowDrawable. It is used to avoid gaps between the card
and the shadow. --> and the shadow. -->
<dimen name="fake_shadow_inset">1dp</dimen> <dimen name="fake_shadow_inset">1dp</dimen>
@@ -1166,7 +1156,6 @@
<!-- The absolute side margins of quick settings --> <!-- The absolute side margins of quick settings -->
<dimen name="quick_settings_bottom_margin_media">8dp</dimen> <dimen name="quick_settings_bottom_margin_media">8dp</dimen>
<dimen name="rounded_corner_content_padding">0dp</dimen>
<dimen name="nav_content_padding">0dp</dimen> <dimen name="nav_content_padding">0dp</dimen>
<dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen> <dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen>
<dimen name="nav_quick_scrub_track_thickness">10dp</dimen> <dimen name="nav_quick_scrub_track_thickness">10dp</dimen>

View File

@@ -45,7 +45,9 @@ android_library {
":wm_shell-aidls", ":wm_shell-aidls",
":wm_shell_util-sources", ":wm_shell_util-sources",
], ],
resource_dirs: [
"res",
],
static_libs: [ static_libs: [
"PluginCoreLib", "PluginCoreLib",
"androidx.dynamicanimation_dynamicanimation", "androidx.dynamicanimation_dynamicanimation",

View File

@@ -18,12 +18,10 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
> >
<com.android.systemui.shared.rotation.FloatingRotationButtonView
<com.android.systemui.navigationbar.buttons.KeyButtonView
android:id="@+id/rotate_suggestion" android:id="@+id/rotate_suggestion"
android:layout_width="@dimen/floating_rotation_button_diameter" android:layout_width="@dimen/floating_rotation_button_diameter"
android:layout_height="@dimen/floating_rotation_button_diameter" android:layout_height="@dimen/floating_rotation_button_diameter"
android:contentDescription="@string/accessibility_rotate_button"
android:paddingStart="@dimen/navigation_key_padding" android:paddingStart="@dimen/navigation_key_padding"
android:paddingEnd="@dimen/navigation_key_padding" android:paddingEnd="@dimen/navigation_key_padding"
android:layout_gravity="bottom|left" android:layout_gravity="bottom|left"

View File

@@ -0,0 +1,19 @@
<?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.
*/
-->
<resources>
<dimen name="navigation_key_padding">25dp</dimen>
</resources>

View File

@@ -0,0 +1,23 @@
<?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.
*/
-->
<resources>
<!-- The maximum width of the navigation bar ripples. -->
<dimen name="key_button_ripple_max_width">76dp</dimen>
<!-- The padding around the navigation buttons -->
<dimen name="navigation_key_padding">0dp</dimen>
</resources>

View File

@@ -0,0 +1,30 @@
<?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.
*/
-->
<resources>
<!-- The maximum width of the navigation bar ripples. -->
<dimen name="key_button_ripple_max_width">95dp</dimen>
<dimen name="rounded_corner_content_padding">0dp</dimen>
<dimen name="navigation_key_padding">0dp</dimen>
<!-- Floating rotation button -->
<dimen name="floating_rotation_button_diameter">40dp</dimen>
<dimen name="floating_rotation_button_min_margin">20dp</dimen>
<dimen name="floating_rotation_button_taskbar_left_margin">20dp</dimen>
<dimen name="floating_rotation_button_taskbar_bottom_margin">10dp</dimen>
</resources>

View File

@@ -33,12 +33,12 @@ import android.view.RenderNodeAnimator;
import android.view.View; import android.view.View;
import android.view.ViewConfiguration; import android.view.ViewConfiguration;
import android.view.animation.Interpolator; import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import androidx.annotation.Keep; import androidx.annotation.Keep;
import com.android.systemui.shared.R;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
@@ -49,6 +49,8 @@ public class KeyButtonRipple extends Drawable {
private static final float GLOW_MAX_ALPHA_DARK = 0.1f; private static final float GLOW_MAX_ALPHA_DARK = 0.1f;
private static final int ANIMATION_DURATION_SCALE = 350; private static final int ANIMATION_DURATION_SCALE = 350;
private static final int ANIMATION_DURATION_FADE = 450; private static final int ANIMATION_DURATION_FADE = 450;
private static final Interpolator ALPHA_OUT_INTERPOLATOR =
new PathInterpolator(0f, 0f, 0.8f, 1f);
private Paint mRipplePaint; private Paint mRipplePaint;
private CanvasProperty<Float> mLeftProp; private CanvasProperty<Float> mLeftProp;
@@ -336,7 +338,7 @@ public class KeyButtonRipple extends Drawable {
private void exitSoftware() { private void exitSoftware() {
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f); ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f);
alphaAnimator.setInterpolator(Interpolators.ALPHA_OUT); alphaAnimator.setInterpolator(ALPHA_OUT_INTERPOLATOR);
alphaAnimator.setDuration(ANIMATION_DURATION_FADE); alphaAnimator.setDuration(ANIMATION_DURATION_FADE);
alphaAnimator.addListener(mAnimatorListener); alphaAnimator.addListener(mAnimatorListener);
alphaAnimator.start(); alphaAnimator.start();
@@ -459,7 +461,7 @@ public class KeyButtonRipple extends Drawable {
final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp, final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp,
RenderNodeAnimator.PAINT_ALPHA, 0); RenderNodeAnimator.PAINT_ALPHA, 0);
opacityAnim.setDuration(ANIMATION_DURATION_FADE); opacityAnim.setDuration(ANIMATION_DURATION_FADE);
opacityAnim.setInterpolator(Interpolators.ALPHA_OUT); opacityAnim.setInterpolator(ALPHA_OUT_INTERPOLATOR);
opacityAnim.addListener(mAnimatorListener); opacityAnim.addListener(mAnimatorListener);
opacityAnim.addListener(mExitHwTraceAnimator); opacityAnim.addListener(mExitHwTraceAnimator);
opacityAnim.setTarget(mTargetView); opacityAnim.setTarget(mTargetView);

View File

@@ -14,12 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.systemui.navigationbar.gestural; package com.android.systemui.shared.rotation;
import android.annotation.StringRes;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -27,28 +29,25 @@ import android.view.WindowManager;
import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import com.android.systemui.R; import androidx.core.view.OneShotPreDrawListener;
import com.android.systemui.navigationbar.RotationButton;
import com.android.systemui.navigationbar.RotationButtonController; import com.android.systemui.shared.R;
import com.android.systemui.navigationbar.buttons.KeyButtonDrawable; import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position;
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.navigationbar.gestural.FloatingRotationButtonPositionCalculator.Position;
/** /**
* Containing logic for the rotation button on the physical left bottom corner of the screen. * Containing logic for the rotation button on the physical left bottom corner of the screen.
*/ */
public class FloatingRotationButton implements RotationButton { public class FloatingRotationButton implements RotationButton {
private static final float BACKGROUND_ALPHA = 0.92f;
private static final int MARGIN_ANIMATION_DURATION_MILLIS = 300; private static final int MARGIN_ANIMATION_DURATION_MILLIS = 300;
private final WindowManager mWindowManager; private final WindowManager mWindowManager;
private final ViewGroup mKeyButtonContainer; private final ViewGroup mKeyButtonContainer;
private final KeyButtonView mKeyButtonView; private final FloatingRotationButtonView mKeyButtonView;
private final int mContainerSize; private final int mContainerSize;
private KeyButtonDrawable mKeyButtonDrawable; private AnimatedVectorDrawable mAnimatedDrawable;
private boolean mIsShowing; private boolean mIsShowing;
private boolean mCanShow = true; private boolean mCanShow = true;
private int mDisplayRotation; private int mDisplayRotation;
@@ -62,12 +61,13 @@ public class FloatingRotationButton implements RotationButton {
private RotationButtonUpdatesCallback mUpdatesCallback; private RotationButtonUpdatesCallback mUpdatesCallback;
private Position mPosition; private Position mPosition;
public FloatingRotationButton(Context context) { public FloatingRotationButton(Context context, @StringRes int contentDescription) {
mWindowManager = context.getSystemService(WindowManager.class); mWindowManager = context.getSystemService(WindowManager.class);
mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate( mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate(
R.layout.rotate_suggestion, null); R.layout.rotate_suggestion, null);
mKeyButtonView = mKeyButtonContainer.findViewById(R.id.rotate_suggestion); mKeyButtonView = mKeyButtonContainer.findViewById(R.id.rotate_suggestion);
mKeyButtonView.setVisibility(View.VISIBLE); mKeyButtonView.setVisibility(View.VISIBLE);
mKeyButtonView.setContentDescription(context.getString(contentDescription));
Resources res = context.getResources(); Resources res = context.getResources();
@@ -113,6 +113,10 @@ public class FloatingRotationButton implements RotationButton {
mIsShowing = true; mIsShowing = true;
int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// TODO(b/200103245): add new window type that has z-index above
// TYPE_NAVIGATION_BAR_PANEL as currently it could be below the taskbar which has
// the same window type
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
mContainerSize, mContainerSize,
mContainerSize, mContainerSize,
@@ -134,14 +138,18 @@ public class FloatingRotationButton implements RotationButton {
updateTranslation(mPosition, /* animate */ false); updateTranslation(mPosition, /* animate */ false);
mWindowManager.addView(mKeyButtonContainer, lp); mWindowManager.addView(mKeyButtonContainer, lp);
if (mKeyButtonDrawable != null && mKeyButtonDrawable.canAnimate()) { if (mAnimatedDrawable != null) {
mKeyButtonDrawable.resetAnimation(); mAnimatedDrawable.reset();
mKeyButtonDrawable.startAnimation(); mAnimatedDrawable.start();
} }
if (mUpdatesCallback != null) { // Notify about visibility only after first traversal so we can properly calculate
mUpdatesCallback.onVisibilityChanged(true); // the touch region for the button
} OneShotPreDrawListener.add(mKeyButtonView, () -> {
if (mIsShowing && mUpdatesCallback != null) {
mUpdatesCallback.onVisibilityChanged(true);
}
});
return true; return true;
} }
@@ -166,12 +174,10 @@ public class FloatingRotationButton implements RotationButton {
@Override @Override
public void updateIcon(int lightIconColor, int darkIconColor) { public void updateIcon(int lightIconColor, int darkIconColor) {
Color ovalBackgroundColor = Color.valueOf(Color.red(darkIconColor), mAnimatedDrawable = (AnimatedVectorDrawable) mKeyButtonView.getContext()
Color.green(darkIconColor), Color.blue(darkIconColor), BACKGROUND_ALPHA); .getDrawable(mRotationButtonController.getIconResId());
mKeyButtonDrawable = KeyButtonDrawable.create(mRotationButtonController.getContext(), mKeyButtonView.setImageDrawable(mAnimatedDrawable);
lightIconColor, darkIconColor, mRotationButtonController.getIconResId(), mKeyButtonView.setColors(lightIconColor, darkIconColor);
false /* shadow */, ovalBackgroundColor);
mKeyButtonView.setImageDrawable(mKeyButtonDrawable);
} }
@Override @Override
@@ -185,8 +191,8 @@ public class FloatingRotationButton implements RotationButton {
} }
@Override @Override
public KeyButtonDrawable getImageDrawable() { public Drawable getImageDrawable() {
return mKeyButtonDrawable; return mAnimatedDrawable;
} }
@Override @Override
@@ -202,6 +208,7 @@ public class FloatingRotationButton implements RotationButton {
} }
} }
@Override
public void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) { public void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) {
mIsTaskbarVisible = taskbarVisible; mIsTaskbarVisible = taskbarVisible;
mIsTaskbarStashed = taskbarStashed; mIsTaskbarStashed = taskbarStashed;

View File

@@ -1,4 +1,4 @@
package com.android.systemui.navigationbar.gestural package com.android.systemui.shared.rotation
import android.view.Gravity import android.view.Gravity
import android.view.Surface import android.view.Surface
@@ -7,7 +7,7 @@ import android.view.Surface
* Calculates gravity and translation that is necessary to display * Calculates gravity and translation that is necessary to display
* the button in the correct position based on the current state * the button in the correct position based on the current state
*/ */
internal class FloatingRotationButtonPositionCalculator( class FloatingRotationButtonPositionCalculator(
private val defaultMargin: Int, private val defaultMargin: Int,
private val taskbarMarginLeft: Int, private val taskbarMarginLeft: Int,
private val taskbarMarginBottom: Int private val taskbarMarginBottom: Int

View File

@@ -0,0 +1,81 @@
/*
* 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.
*/
package com.android.systemui.shared.rotation;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import com.android.systemui.navigationbar.buttons.KeyButtonRipple;
public class FloatingRotationButtonView extends ImageView {
private static final float BACKGROUND_ALPHA = 0.92f;
private final KeyButtonRipple mRipple;
private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
public FloatingRotationButtonView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FloatingRotationButtonView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setClickable(true);
mRipple = new KeyButtonRipple(context, this);
setBackground(mRipple);
setWillNotDraw(false);
forceHasOverlappingRendering(false);
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (visibility != View.VISIBLE) {
jumpDrawablesToCurrentState();
}
}
public void setColors(int lightColor, int darkColor) {
getDrawable().setColorFilter(new PorterDuffColorFilter(lightColor, PorterDuff.Mode.SRC_IN));
final int ovalBackgroundColor = Color.valueOf(Color.red(darkColor),
Color.green(darkColor), Color.blue(darkColor), BACKGROUND_ALPHA).toArgb();
mOvalBgPaint.setColor(ovalBackgroundColor);
mRipple.setType(KeyButtonRipple.Type.OVAL);
}
public void setDarkIntensity(float darkIntensity) {
mRipple.setDarkIntensity(darkIntensity);
}
@Override
public void draw(Canvas canvas) {
int d = Math.min(getWidth(), getHeight());
canvas.drawOval(0, 0, d, d, mOvalBgPaint);
super.draw(canvas);
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.
*/
package com.android.systemui.shared.rotation;
import android.graphics.drawable.Drawable;
import android.view.View;
/**
* Interface of a rotation button that interacts {@link RotationButtonController}.
* This interface exists because of the two different styles of rotation button in Sysui,
* one in contextual for 3 button nav and a floating rotation button for gestural.
*/
public interface RotationButton {
default void setRotationButtonController(RotationButtonController rotationButtonController) { }
default void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback) { }
default View getCurrentView() {
return null;
}
default boolean show() { return false; }
default boolean hide() { return false; }
default boolean isVisible() {
return false;
}
default void setCanShowRotationButton(boolean canShow) {}
default void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) {}
default void updateIcon(int lightIconColor, int darkIconColor) { }
default void setOnClickListener(View.OnClickListener onClickListener) { }
default void setOnHoverListener(View.OnHoverListener onHoverListener) { }
default Drawable getImageDrawable() {
return null;
}
default void setDarkIntensity(float darkIntensity) { }
default boolean acceptRotationProposal() {
return getCurrentView() != null;
}
/**
* Callback for updates provided by a rotation button
*/
interface RotationButtonUpdatesCallback {
default void onVisibilityChanged(boolean isVisible) {};
default void onPositionChanged() {};
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020 The Android Open Source Project * Copyright 2021 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,7 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.systemui.navigationbar; package com.android.systemui.shared.rotation;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION; import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
@@ -23,48 +25,51 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.annotation.ColorInt; import android.annotation.ColorInt;
import android.annotation.DrawableRes; import android.annotation.DrawableRes;
import android.annotation.SuppressLint;
import android.app.StatusBarManager; import android.app.StatusBarManager;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.RemoteException; import android.os.RemoteException;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; import android.util.Log;
import android.view.IRotationWatcher.Stub; import android.view.IRotationWatcher;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.Surface; import android.view.Surface;
import android.view.View; import android.view.View;
import android.view.WindowInsetsController; import android.view.WindowInsetsController;
import android.view.WindowInsetsController.Behavior;
import android.view.WindowManagerGlobal; import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.logging.UiEventLoggerImpl;
import com.android.systemui.Dependency; import com.android.internal.view.RotationPolicy;
import com.android.systemui.R; import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.navigationbar.RotationButton.RotationButtonUpdatesCallback;
import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.recents.utilities.ViewRippler; import com.android.systemui.shared.recents.utilities.ViewRippler;
import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.RotationLockController;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier;
/** Contains logic that deals with showing a rotate suggestion button with animation. */ /**
* Contains logic that deals with showing a rotate suggestion button with animation.
*/
public class RotationButtonController { public class RotationButtonController {
private static final String TAG = "StatusBar/RotationButtonController"; private static final String TAG = "StatusBar/RotationButtonController";
private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100; private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000; private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000;
private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3; private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
@@ -72,6 +77,7 @@ public class RotationButtonController {
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
private final ViewRippler mViewRippler = new ViewRippler(); private final ViewRippler mViewRippler = new ViewRippler();
private final Supplier<Integer> mWindowRotationProvider;
private RotationButton mRotationButton; private RotationButton mRotationButton;
private boolean mIsRecentsAnimationRunning; private boolean mIsRecentsAnimationRunning;
@@ -79,17 +85,30 @@ public class RotationButtonController {
private int mLastRotationSuggestion; private int mLastRotationSuggestion;
private boolean mPendingRotationSuggestion; private boolean mPendingRotationSuggestion;
private boolean mHoveringRotationSuggestion; private boolean mHoveringRotationSuggestion;
private RotationLockController mRotationLockController; private final AccessibilityManager mAccessibilityManager;
private AccessibilityManagerWrapper mAccessibilityManagerWrapper; private final TaskStackListenerImpl mTaskStackListener;
private TaskStackListenerImpl mTaskStackListener;
private Consumer<Integer> mRotWatcherListener; private Consumer<Integer> mRotWatcherListener;
private boolean mListenersRegistered = false; private boolean mListenersRegistered = false;
private boolean mIsNavigationBarShowing; private boolean mIsNavigationBarShowing;
private @Behavior int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT; @SuppressLint("InlinedApi")
private @WindowInsetsController.Behavior
int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT;
private boolean mSkipOverrideUserLockPrefsOnce; private boolean mSkipOverrideUserLockPrefsOnce;
private int mLightIconColor; private final int mLightIconColor;
private int mDarkIconColor; private final int mDarkIconColor;
private int mIconResId = R.drawable.ic_sysbar_rotate_button_ccw_start_90;
@DrawableRes
private final int mIconCcwStart0ResId;
@DrawableRes
private final int mIconCcwStart90ResId;
@DrawableRes
private final int mIconCwStart0ResId;
@DrawableRes
private final int mIconCwStart90ResId;
@DrawableRes
private int mIconResId;
private final Runnable mRemoveRotationProposal = private final Runnable mRemoveRotationProposal =
() -> setRotateSuggestionButtonState(false /* visible */); () -> setRotateSuggestionButtonState(false /* visible */);
@@ -97,19 +116,20 @@ public class RotationButtonController {
() -> mPendingRotationSuggestion = false; () -> mPendingRotationSuggestion = false;
private Animator mRotateHideAnimator; private Animator mRotateHideAnimator;
private final Stub mRotationWatcher = new Stub() {
private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() {
@Override @Override
public void onRotationChanged(final int rotation) throws RemoteException { public void onRotationChanged(final int rotation) {
// We need this to be scheduled as early as possible to beat the redrawing of // We need this to be scheduled as early as possible to beat the redrawing of
// window in response to the orientation change. // window in response to the orientation change.
mMainThreadHandler.postAtFrontOfQueue(() -> { mMainThreadHandler.postAtFrontOfQueue(() -> {
// If the screen rotation changes while locked, potentially update lock to flow with // If the screen rotation changes while locked, potentially update lock to flow with
// new screen rotation and hide any showing suggestions. // new screen rotation and hide any showing suggestions.
if (mRotationLockController.isRotationLocked()) { if (isRotationLocked()) {
if (shouldOverrideUserLockPrefs(rotation)) { if (shouldOverrideUserLockPrefs(rotation)) {
setRotationLockedAtAngle(rotation); setRotationLockedAtAngle(rotation);
} }
setRotateSuggestionButtonState(false /* visible */, true /* hideImmediately */); setRotateSuggestionButtonState(false /* visible */, true /* forced */);
} }
if (mRotWatcherListener != null) { if (mRotWatcherListener != null) {
@@ -121,27 +141,39 @@ public class RotationButtonController {
/** /**
* Determines if rotation suggestions disabled2 flag exists in flag * Determines if rotation suggestions disabled2 flag exists in flag
*
* @param disable2Flags see if rotation suggestion flag exists in this flag * @param disable2Flags see if rotation suggestion flag exists in this flag
* @return whether flag exists * @return whether flag exists
*/ */
static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) { public static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0; return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
} }
RotationButtonController(Context context, @ColorInt int lightIconColor, public RotationButtonController(Context context,
@ColorInt int darkIconColor) { @ColorInt int lightIconColor, @ColorInt int darkIconColor,
@DrawableRes int iconCcwStart0ResId,
@DrawableRes int iconCcwStart90ResId,
@DrawableRes int iconCwStart0ResId,
@DrawableRes int iconCwStart90ResId,
Supplier<Integer> windowRotationProvider) {
mContext = context; mContext = context;
mLightIconColor = lightIconColor; mLightIconColor = lightIconColor;
mDarkIconColor = darkIconColor; mDarkIconColor = darkIconColor;
mIsNavigationBarShowing = true; mIconCcwStart0ResId = iconCcwStart0ResId;
mRotationLockController = Dependency.get(RotationLockController.class); mIconCcwStart90ResId = iconCcwStart90ResId;
mAccessibilityManagerWrapper = Dependency.get(AccessibilityManagerWrapper.class); mIconCwStart0ResId = iconCwStart0ResId;
mIconCwStart90ResId = iconCwStart90ResId;
mIconResId = mIconCcwStart90ResId;
mAccessibilityManager = AccessibilityManager.getInstance(context);
mTaskStackListener = new TaskStackListenerImpl(); mTaskStackListener = new TaskStackListenerImpl();
mWindowRotationProvider = windowRotationProvider;
} }
void setRotationButton(RotationButton rotationButton, public void setRotationButton(RotationButton rotationButton,
RotationButtonUpdatesCallback updatesCallback) { RotationButtonUpdatesCallback updatesCallback) {
mRotationButton = rotationButton; mRotationButton = rotationButton;
mRotationButton.setRotationButtonController(this); mRotationButton.setRotationButtonController(this);
mRotationButton.setOnClickListener(this::onRotateSuggestionClick); mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
@@ -149,7 +181,24 @@ public class RotationButtonController {
mRotationButton.setUpdatesCallback(updatesCallback); mRotationButton.setUpdatesCallback(updatesCallback);
} }
void registerListeners() { public Context getContext() {
return mContext;
}
public void init() {
registerListeners();
if (mContext.getDisplay().getDisplayId() != DEFAULT_DISPLAY) {
// Currently there is no accelerometer sensor on non-default display, disable fixed
// rotation for non-default display
onDisable2FlagChanged(StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS);
}
}
public void onDestroy() {
unregisterListeners();
}
public void registerListeners() {
if (mListenersRegistered) { if (mListenersRegistered) {
return; return;
} }
@@ -157,18 +206,19 @@ public class RotationButtonController {
mListenersRegistered = true; mListenersRegistered = true;
try { try {
WindowManagerGlobal.getWindowManagerService() WindowManagerGlobal.getWindowManagerService()
.watchRotation(mRotationWatcher, mContext.getDisplay().getDisplayId()); .watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
mListenersRegistered = false; mListenersRegistered = false;
Log.w(TAG, "RegisterListeners for the display failed"); Log.w(TAG, "RegisterListeners for the display failed");
} catch (RemoteException e) { } catch (RemoteException e) {
throw e.rethrowFromSystemServer(); Log.e(TAG, "RegisterListeners caught a RemoteException", e);
return;
} }
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
} }
void unregisterListeners() { public void unregisterListeners() {
if (!mListenersRegistered) { if (!mListenersRegistered) {
return; return;
} }
@@ -177,34 +227,31 @@ public class RotationButtonController {
try { try {
WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher); WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
} catch (RemoteException e) { } catch (RemoteException e) {
throw e.rethrowFromSystemServer(); Log.e(TAG, "UnregisterListeners caught a RemoteException", e);
return;
} }
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
} }
void setRotationCallback(Consumer<Integer> watcher) { public void setRotationCallback(Consumer<Integer> watcher) {
mRotWatcherListener = watcher; mRotWatcherListener = watcher;
} }
void setRotationLockedAtAngle(int rotationSuggestion) { public void setRotationLockedAtAngle(int rotationSuggestion) {
mRotationLockController.setRotationLockedAtAngle(true /* locked */, rotationSuggestion); RotationPolicy.setRotationLockAtAngle(mContext, true, rotationSuggestion);
} }
public boolean isRotationLocked() { public boolean isRotationLocked() {
return mRotationLockController.isRotationLocked(); return RotationPolicy.isRotationLocked(mContext);
} }
void setRotateSuggestionButtonState(boolean visible) { public void setRotateSuggestionButtonState(boolean visible) {
setRotateSuggestionButtonState(visible, false /* hideImmediately */); setRotateSuggestionButtonState(visible, false /* force */);
} }
/** void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
* Change the visibility of rotate suggestion button. If {@code hideImmediately} is true, // At any point the button can become invisible because an a11y service became active.
* it doesn't wait until the completion of the running animation.
*/
void setRotateSuggestionButtonState(final boolean visible, final boolean hideImmediately) {
// At any point the the button can become invisible because an a11y service became active.
// Similarly, a call to make the button visible may be rejected because an a11y service is // Similarly, a call to make the button visible may be rejected because an a11y service is
// active. Must account for this. // active. Must account for this.
// Rerun a show animation to indicate change but don't rerun a hide animation // Rerun a show animation to indicate change but don't rerun a hide animation
@@ -213,7 +260,7 @@ public class RotationButtonController {
final View view = mRotationButton.getCurrentView(); final View view = mRotationButton.getCurrentView();
if (view == null) return; if (view == null) return;
final KeyButtonDrawable currentDrawable = mRotationButton.getImageDrawable(); final Drawable currentDrawable = mRotationButton.getImageDrawable();
if (currentDrawable == null) return; if (currentDrawable == null) return;
// Clear any pending suggestion flag as it has either been nullified or is being shown // Clear any pending suggestion flag as it has either been nullified or is being shown
@@ -232,11 +279,13 @@ public class RotationButtonController {
view.setAlpha(1f); view.setAlpha(1f);
// Run the rotate icon's animation if it has one // Run the rotate icon's animation if it has one
if (currentDrawable.canAnimate()) { if (currentDrawable instanceof AnimatedVectorDrawable) {
currentDrawable.resetAnimation(); ((AnimatedVectorDrawable) currentDrawable).reset();
currentDrawable.startAnimation(); ((AnimatedVectorDrawable) currentDrawable).start();
} }
// TODO(b/187754252): No idea why this doesn't work. If we remove the "false"
// we see the animation show the pressed state... but it only shows the first time.
if (!isRotateSuggestionIntroduced()) mViewRippler.start(view); if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
// Set visibility unless a11y service is active. // Set visibility unless a11y service is active.
@@ -244,7 +293,7 @@ public class RotationButtonController {
} else { // Hide } else { // Hide
mViewRippler.stop(); // Prevent any pending ripples, force hide or not mViewRippler.stop(); // Prevent any pending ripples, force hide or not
if (hideImmediately) { if (force) {
// If a hide animator is running stop it and make invisible // If a hide animator is running stop it and make invisible
if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) { if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
mRotateHideAnimator.pause(); mRotateHideAnimator.pause();
@@ -258,7 +307,7 @@ public class RotationButtonController {
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f); ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f);
fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS); fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
fadeOut.setInterpolator(Interpolators.LINEAR); fadeOut.setInterpolator(LINEAR_INTERPOLATOR);
fadeOut.addListener(new AnimatorListenerAdapter() { fadeOut.addListener(new AnimatorListenerAdapter() {
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
@@ -271,29 +320,34 @@ public class RotationButtonController {
} }
} }
void setRecentsAnimationRunning(boolean running) { public void setDarkIntensity(float darkIntensity) {
mRotationButton.setDarkIntensity(darkIntensity);
}
public void setRecentsAnimationRunning(boolean running) {
mIsRecentsAnimationRunning = running; mIsRecentsAnimationRunning = running;
updateRotationButtonStateInOverview(); updateRotationButtonStateInOverview();
} }
void setHomeRotationEnabled(boolean enabled) { public void setHomeRotationEnabled(boolean enabled) {
mHomeRotationEnabled = enabled; mHomeRotationEnabled = enabled;
updateRotationButtonStateInOverview(); updateRotationButtonStateInOverview();
} }
private void updateRotationButtonStateInOverview() { private void updateRotationButtonStateInOverview() {
if (mIsRecentsAnimationRunning && !mHomeRotationEnabled) { if (mIsRecentsAnimationRunning && !mHomeRotationEnabled) {
setRotateSuggestionButtonState(false, true /* hideImmediately */ ); setRotateSuggestionButtonState(false, true /* hideImmediately */);
} }
} }
void setDarkIntensity(float darkIntensity) { public void onRotationProposal(int rotation, boolean isValid) {
mRotationButton.setDarkIntensity(darkIntensity); int windowRotation = mWindowRotationProvider.get();
}
void onRotationProposal(int rotation, int windowRotation, boolean isValid) { if (!mRotationButton.acceptRotationProposal()) {
if (!mRotationButton.acceptRotationProposal() || (!mHomeRotationEnabled return;
&& mIsRecentsAnimationRunning)) { }
if (!mHomeRotationEnabled && mIsRecentsAnimationRunning) {
return; return;
} }
@@ -316,13 +370,9 @@ public class RotationButtonController {
mLastRotationSuggestion = rotation; // Remember rotation for click mLastRotationSuggestion = rotation; // Remember rotation for click
final boolean rotationCCW = Utilities.isRotationAnimationCCW(windowRotation, rotation); final boolean rotationCCW = Utilities.isRotationAnimationCCW(windowRotation, rotation);
if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) { if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
mIconResId = rotationCCW mIconResId = rotationCCW ? mIconCcwStart0ResId : mIconCwStart0ResId;
? R.drawable.ic_sysbar_rotate_button_ccw_start_90
: R.drawable.ic_sysbar_rotate_button_cw_start_90;
} else { // 90 or 270 } else { // 90 or 270
mIconResId = rotationCCW mIconResId = rotationCCW ? mIconCcwStart90ResId : mIconCwStart90ResId;
? R.drawable.ic_sysbar_rotate_button_ccw_start_0
: R.drawable.ic_sysbar_rotate_button_ccw_start_0;
} }
mRotationButton.updateIcon(mLightIconColor, mDarkIconColor); mRotationButton.updateIcon(mLightIconColor, mDarkIconColor);
@@ -339,23 +389,31 @@ public class RotationButtonController {
} }
} }
void onDisable2FlagChanged(int state2) { public void onDisable2FlagChanged(int state2) {
final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2); final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled(); if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
} }
void onNavigationBarWindowVisibilityChange(boolean showing) { public void onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior) {
if (DEFAULT_DISPLAY != displayId) {
return;
}
if (mBehavior != behavior) {
mBehavior = behavior;
showPendingRotationButtonIfNeeded();
}
}
public void onNavigationBarWindowVisibilityChange(boolean showing) {
if (mIsNavigationBarShowing != showing) { if (mIsNavigationBarShowing != showing) {
mIsNavigationBarShowing = showing; mIsNavigationBarShowing = showing;
showPendingRotationButtonIfNeeded(); showPendingRotationButtonIfNeeded();
} }
} }
void onBehaviorChanged(@Behavior int behavior) { public void onTaskbarStateChange(boolean visible, boolean stashed) {
if (mBehavior != behavior) { getRotationButton().onTaskbarStateChanged(visible, stashed);
mBehavior = behavior;
showPendingRotationButtonIfNeeded();
}
} }
private void showPendingRotationButtonIfNeeded() { private void showPendingRotationButtonIfNeeded() {
@@ -364,31 +422,33 @@ public class RotationButtonController {
} }
} }
/** Return true when either the nav bar is visible or it's in visual immersive mode. */ /**
* Return true when either the task bar is visible or it's in visual immersive mode.
*/
@SuppressLint("InlinedApi")
private boolean canShowRotationButton() { private boolean canShowRotationButton() {
return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT; return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT;
} }
public Context getContext() { @DrawableRes
return mContext; public int getIconResId() {
}
RotationButton getRotationButton() {
return mRotationButton;
}
public @DrawableRes int getIconResId() {
return mIconResId; return mIconResId;
} }
public @ColorInt int getLightIconColor() { @ColorInt
public int getLightIconColor() {
return mLightIconColor; return mLightIconColor;
} }
public @ColorInt int getDarkIconColor() { @ColorInt
public int getDarkIconColor() {
return mDarkIconColor; return mDarkIconColor;
} }
public RotationButton getRotationButton() {
return mRotationButton;
}
private void onRotateSuggestionClick(View v) { private void onRotateSuggestionClick(View v) {
mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED); mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
incrementNumAcceptedRotationSuggestionsIfNeeded(); incrementNumAcceptedRotationSuggestionsIfNeeded();
@@ -420,7 +480,7 @@ public class RotationButtonController {
* avoid losing original user rotation when display rotation is changed by entering the fixed * avoid losing original user rotation when display rotation is changed by entering the fixed
* orientation overview. * orientation overview.
*/ */
void setSkipOverrideUserLockPrefsOnce() { public void setSkipOverrideUserLockPrefsOnce() {
mSkipOverrideUserLockPrefsOnce = true; mSkipOverrideUserLockPrefsOnce = true;
} }
@@ -451,7 +511,7 @@ public class RotationButtonController {
} }
private int computeRotationProposalTimeout() { private int computeRotationProposalTimeout() {
return mAccessibilityManagerWrapper.getRecommendedTimeoutMillis( return mAccessibilityManager.getRecommendedTimeoutMillis(
mHoveringRotationSuggestion ? 16000 : 5000, mHoveringRotationSuggestion ? 16000 : 5000,
AccessibilityManager.FLAG_CONTENT_CONTROLS); AccessibilityManager.FLAG_CONTENT_CONTROLS);
} }
@@ -513,11 +573,15 @@ public class RotationButtonController {
ROTATION_SUGGESTION_ACCEPTED(207); ROTATION_SUGGESTION_ACCEPTED(207);
private final int mId; private final int mId;
RotationButtonEvent(int id) { RotationButtonEvent(int id) {
mId = id; mId = id;
} }
@Override public int getId() {
@Override
public int getId() {
return mId; return mId;
} }
} }
} }

View File

@@ -131,6 +131,8 @@ import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents; import com.android.systemui.recents.Recents;
import com.android.systemui.shared.rotation.RotationButton;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -944,7 +946,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
// not valid. Just ignore the rotation in this case. // not valid. Just ignore the rotation in this case.
if (!mNavigationBarView.isAttachedToWindow()) return; if (!mNavigationBarView.isAttachedToWindow()) return;
final int winRotation = mNavigationBarView.getDisplay().getRotation();
final boolean rotateSuggestionsDisabled = RotationButtonController final boolean rotateSuggestionsDisabled = RotationButtonController
.hasDisable2RotateSuggestionFlag(mDisabledFlags2); .hasDisable2RotateSuggestionFlag(mDisabledFlags2);
final RotationButtonController rotationButtonController = final RotationButtonController rotationButtonController =
@@ -953,7 +954,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
if (RotationContextButton.DEBUG_ROTATION) { if (RotationContextButton.DEBUG_ROTATION) {
Log.v(TAG, "onRotationProposal proposedRotation=" + Surface.rotationToString(rotation) Log.v(TAG, "onRotationProposal proposedRotation=" + Surface.rotationToString(rotation)
+ ", winRotation=" + Surface.rotationToString(winRotation)
+ ", isValid=" + isValid + ", mNavBarWindowState=" + ", isValid=" + isValid + ", mNavBarWindowState="
+ StatusBarManager.windowStateToString(mNavigationBarWindowState) + StatusBarManager.windowStateToString(mNavigationBarWindowState)
+ ", rotateSuggestionsDisabled=" + rotateSuggestionsDisabled + ", rotateSuggestionsDisabled=" + rotateSuggestionsDisabled
@@ -963,7 +963,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
// Respect the disabled flag, no need for action as flag change callback will handle hiding // Respect the disabled flag, no need for action as flag change callback will handle hiding
if (rotateSuggestionsDisabled) return; if (rotateSuggestionsDisabled) return;
rotationButtonController.onRotationProposal(rotation, winRotation, isValid); rotationButtonController.onRotationProposal(rotation, isValid);
} }
@Override @Override

View File

@@ -67,7 +67,6 @@ import com.android.systemui.Dependency;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.Interpolators;
import com.android.systemui.model.SysUiState; import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.RotationButton.RotationButtonUpdatesCallback;
import com.android.systemui.navigationbar.buttons.ButtonDispatcher; import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.buttons.ContextualButton; import com.android.systemui.navigationbar.buttons.ContextualButton;
import com.android.systemui.navigationbar.buttons.ContextualButtonGroup; import com.android.systemui.navigationbar.buttons.ContextualButtonGroup;
@@ -76,9 +75,11 @@ import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
import com.android.systemui.navigationbar.buttons.NearestTouchFrame; import com.android.systemui.navigationbar.buttons.NearestTouchFrame;
import com.android.systemui.navigationbar.buttons.RotationContextButton; import com.android.systemui.navigationbar.buttons.RotationContextButton;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.navigationbar.gestural.FloatingRotationButton; import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents; import com.android.systemui.recents.Recents;
import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.QuickStepContract;
@@ -322,9 +323,15 @@ public class NavigationBarView extends FrameLayout implements
mContextualButtonGroup.addButton(accessibilityButton); mContextualButtonGroup.addButton(accessibilityButton);
mRotationContextButton = new RotationContextButton(R.id.rotate_suggestion, mRotationContextButton = new RotationContextButton(R.id.rotate_suggestion,
mLightContext, R.drawable.ic_sysbar_rotate_button_ccw_start_0); mLightContext, R.drawable.ic_sysbar_rotate_button_ccw_start_0);
mFloatingRotationButton = new FloatingRotationButton(context); mFloatingRotationButton = new FloatingRotationButton(context,
mRotationButtonController = new RotationButtonController(mLightContext, R.string.accessibility_rotate_button);
mLightIconColor, mDarkIconColor); mRotationButtonController = new RotationButtonController(mLightContext, mLightIconColor,
mDarkIconColor, R.drawable.ic_sysbar_rotate_button_ccw_start_0,
R.drawable.ic_sysbar_rotate_button_ccw_start_90,
R.drawable.ic_sysbar_rotate_button_cw_start_0,
R.drawable.ic_sysbar_rotate_button_cw_start_90,
() -> getDisplay().getRotation());
updateRotationButton(); updateRotationButton();
mOverviewProxyService = Dependency.get(OverviewProxyService.class); mOverviewProxyService = Dependency.get(OverviewProxyService.class);
@@ -661,7 +668,7 @@ public class NavigationBarView extends FrameLayout implements
} }
public void setBehavior(@Behavior int behavior) { public void setBehavior(@Behavior int behavior) {
mRotationButtonController.onBehaviorChanged(behavior); mRotationButtonController.onBehaviorChanged(Display.DEFAULT_DISPLAY, behavior);
} }
@Override @Override
@@ -1277,6 +1284,7 @@ public class NavigationBarView extends FrameLayout implements
mButtonDispatchers.valueAt(i).onDestroy(); mButtonDispatchers.valueAt(i).onDestroy();
} }
if (mRotationButtonController != null) { if (mRotationButtonController != null) {
mFloatingRotationButton.hide();
mRotationButtonController.unregisterListeners(); mRotationButtonController.unregisterListeners();
} }

View File

@@ -1,48 +0,0 @@
/*
* Copyright (C) 2020 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.navigationbar;
import android.view.View;
import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
/** Interface of a rotation button that interacts {@link RotationButtonController}. */
public interface RotationButton {
void setRotationButtonController(RotationButtonController rotationButtonController);
void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback);
View getCurrentView();
boolean show();
boolean hide();
boolean isVisible();
void updateIcon(int lightIconColor, int darkIconColor);
void setOnClickListener(View.OnClickListener onClickListener);
void setOnHoverListener(View.OnHoverListener onHoverListener);
KeyButtonDrawable getImageDrawable();
void setDarkIntensity(float darkIntensity);
default void setCanShowRotationButton(boolean canShow) {}
default boolean acceptRotationProposal() {
return getCurrentView() != null;
}
/**
* Callback for updates provided by a rotation button
*/
interface RotationButtonUpdatesCallback {
void onVisibilityChanged(boolean isVisible);
void onPositionChanged();
}
}

View File

@@ -21,8 +21,8 @@ import android.annotation.IdRes;
import android.content.Context; import android.content.Context;
import android.view.View; import android.view.View;
import com.android.systemui.navigationbar.RotationButton; import com.android.systemui.shared.rotation.RotationButton;
import com.android.systemui.navigationbar.RotationButtonController; import com.android.systemui.shared.rotation.RotationButtonController;
/** Containing logic for the rotation button in nav bar. */ /** Containing logic for the rotation button in nav bar. */
public class RotationContextButton extends ContextualButton implements RotationButton { public class RotationContextButton extends ContextualButton implements RotationButton {

View File

@@ -22,6 +22,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import android.view.Display;
import android.view.View; import android.view.View;
import android.view.WindowInsetsController; import android.view.WindowInsetsController;
@@ -31,6 +32,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext; import com.android.systemui.SysuiTestableContext;
import com.android.systemui.shared.rotation.RotationButton;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.RotationLockController;
import org.junit.Before; import org.junit.Before;
@@ -39,6 +42,8 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.util.function.Supplier;
/** atest NavigationBarRotationContextTest */ /** atest NavigationBarRotationContextTest */
@SmallTest @SmallTest
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@@ -50,6 +55,8 @@ public class NavigationBarRotationContextTest extends SysuiTestCase {
InstrumentationRegistry.getContext(), getLeakCheck()); InstrumentationRegistry.getContext(), getLeakCheck());
private RotationButtonController mRotationButtonController; private RotationButtonController mRotationButtonController;
private RotationButton mRotationButton; private RotationButton mRotationButton;
private int mWindowRotation = DEFAULT_ROTATE;
private Supplier<Integer> mWindowRotationSupplier = () -> mWindowRotation;
@Before @Before
public void setup() { public void setup() {
@@ -58,7 +65,15 @@ public class NavigationBarRotationContextTest extends SysuiTestCase {
final View view = new View(mContext); final View view = new View(mContext);
mRotationButton = mock(RotationButton.class); mRotationButton = mock(RotationButton.class);
mRotationButtonController = new RotationButtonController(mContext, 0, 0); mRotationButtonController = new RotationButtonController(mContext,
/* lightIconColor */ 0,
/* darkIconColor */ 0,
/* iconCcwStart0 */ 0,
/* iconCcwStart90 */ 0,
/* iconCwStart0 */ 0,
/* iconCwStart90 */ 0,
mWindowRotationSupplier
);
mRotationButtonController.setRotationButton(mRotationButton, mRotationButtonController.setRotationButton(mRotationButton,
new RotationButton.RotationButtonUpdatesCallback() { new RotationButton.RotationButtonUpdatesCallback() {
@Override @Override
@@ -77,16 +92,16 @@ public class NavigationBarRotationContextTest extends SysuiTestCase {
@Test @Test
public void testOnInvalidRotationProposal() { public void testOnInvalidRotationProposal() {
mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, mWindowRotation = DEFAULT_ROTATE + 1;
false /* isValid */); mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, false /* isValid */);
verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState( verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
false /* visible */); false /* visible */);
} }
@Test @Test
public void testOnSameRotationProposal() { public void testOnSameRotationProposal() {
mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE, mWindowRotation = DEFAULT_ROTATE;
true /* isValid */); mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, true /* isValid */);
verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState( verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
false /* visible */); false /* visible */);
} }
@@ -94,17 +109,17 @@ public class NavigationBarRotationContextTest extends SysuiTestCase {
@Test @Test
public void testOnRotationProposalShowButtonShowNav() { public void testOnRotationProposalShowButtonShowNav() {
// No navigation bar should not call to set visibility state // No navigation bar should not call to set visibility state
mRotationButtonController.onBehaviorChanged( mRotationButtonController.onBehaviorChanged(Display.DEFAULT_DISPLAY,
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
mRotationButtonController.onNavigationBarWindowVisibilityChange(false /* showing */); mRotationButtonController.onNavigationBarWindowVisibilityChange(false /* showing */);
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState( verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
false /* visible */); false /* visible */);
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState( verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
true /* visible */); true /* visible */);
mWindowRotation = DEFAULT_ROTATE + 1;
// No navigation bar with rotation change should not call to set visibility state // No navigation bar with rotation change should not call to set visibility state
mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, true /* isValid */);
true /* isValid */);
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState( verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
false /* visible */); false /* visible */);
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState( verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
@@ -124,10 +139,10 @@ public class NavigationBarRotationContextTest extends SysuiTestCase {
false /* visible */); false /* visible */);
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState( verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
true /* visible */); true /* visible */);
mWindowRotation = DEFAULT_ROTATE + 1;
// Navigation bar is visible and rotation requested // Navigation bar is visible and rotation requested
mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, true /* isValid */);
true /* isValid */);
verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState( verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
true /* visible */); true /* visible */);
} }

View File

@@ -4,7 +4,8 @@ import android.view.Gravity
import android.view.Surface import android.view.Surface
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.navigationbar.gestural.FloatingRotationButtonPositionCalculator.Position import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator
import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith