Update rotation suggestion button position to align it with taskbar

This CL moves the rotation suggestion button to the middle
of the taskbar when it is visible and not stashed.
When the user stashes or unstashes the taskbar the
button's position is animated accordingly.

Test: manual
Test: atest com.android.systemui.navigationbar.gestural.FloatingRotationButtonPositionCalculatorTest
Fixes: 187410455
Change-Id: Id918e10a44631362f7c3a8a8bb624ee80bdd3c8b
This commit is contained in:
Nick Chameyev
2021-06-24 18:27:22 +01:00
parent 8b74bdcad7
commit 733c39faaf
13 changed files with 475 additions and 272 deletions

View File

@@ -14,16 +14,19 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<com.android.systemui.navigationbar.buttons.KeyButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rotate_suggestion"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="0"
android:scaleType="center"
android:visibility="invisible"
android:contentDescription="@string/accessibility_rotate_button"
android:paddingStart="@dimen/navigation_key_padding"
android:paddingEnd="@dimen/navigation_key_padding"
/>
>
<com.android.systemui.navigationbar.buttons.KeyButtonView
android:id="@+id/rotate_suggestion"
android:layout_width="@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:paddingEnd="@dimen/navigation_key_padding"
android:layout_gravity="bottom|left"
android:scaleType="center"
android:visibility="invisible" />
</FrameLayout>

View File

@@ -62,7 +62,9 @@
<item name="navigation_luminance_change_threshold" type="dimen" format="float">0.05</item>
<dimen name="floating_rotation_button_diameter">40dp</dimen>
<dimen name="floating_rotation_button_min_margin">4dp</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 -->
<dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>

View File

@@ -140,5 +140,8 @@ interface ISystemUiProxy {
/** Notifies that a swipe-up gesture has started */
oneway void notifySwipeUpGestureStarted() = 46;
// Next id = 47
/** Notifies when taskbar status updated */
oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47;
// Next id = 48
}

View File

@@ -383,6 +383,13 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
mNavigationBarView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce();
}
@Override
public void onTaskbarStatusUpdated(boolean visible, boolean stashed) {
mNavigationBarView
.getFloatingRotationButton()
.onTaskbarStateChanged(visible, stashed);
}
@Override
public void onToggleRecentApps() {
// The same case as onOverviewShown but only for 3-button navigation.

View File

@@ -69,6 +69,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
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.ContextualButton;
import com.android.systemui.navigationbar.buttons.ContextualButtonGroup;
@@ -275,14 +276,23 @@ public class NavigationBarView extends FrameLayout implements
false /* inScreen */, false /* useNearestRegion */));
};
private final Consumer<Boolean> mRotationButtonListener = (visible) -> {
if (visible) {
// If the button will actually become visible and the navbar is about to hide,
// tell the statusbar to keep it around for longer
mAutoHideController.touchAutoHide();
}
notifyActiveTouchRegions();
};
private final RotationButtonUpdatesCallback mRotationButtonListener =
new RotationButtonUpdatesCallback() {
@Override
public void onVisibilityChanged(boolean visible) {
if (visible) {
// If the button will actually become visible and the navbar is about
// to hide, tell the statusbar to keep it around for longer
mAutoHideController.touchAutoHide();
}
notifyActiveTouchRegions();
}
@Override
public void onPositionChanged() {
notifyActiveTouchRegions();
}
};
private final Consumer<Boolean> mNavbarOverlayVisibilityChangeCallback = (visible) -> {
if (visible) {

View File

@@ -20,12 +20,10 @@ import android.view.View;
import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
import java.util.function.Consumer;
/** Interface of a rotation button that interacts {@link RotationButtonController}. */
public interface RotationButton {
void setRotationButtonController(RotationButtonController rotationButtonController);
void setVisibilityChangedCallback(Consumer<Boolean> visibilityChangedCallback);
void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback);
View getCurrentView();
boolean show();
boolean hide();
@@ -39,4 +37,12 @@ public interface RotationButton {
default boolean acceptRotationProposal() {
return getCurrentView() != null;
}
/**
* Callback for updates provided by a rotation button
*/
interface RotationButtonUpdatesCallback {
void onVisibilityChanged(boolean isVisible);
void onPositionChanged();
}
}

View File

@@ -46,6 +46,7 @@ import com.android.internal.logging.UiEventLoggerImpl;
import com.android.systemui.Dependency;
import com.android.systemui.R;
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.ViewRippler;
@@ -140,12 +141,12 @@ public class RotationButtonController {
}
void setRotationButton(RotationButton rotationButton,
Consumer<Boolean> visibilityChangedCallback) {
RotationButtonUpdatesCallback updatesCallback) {
mRotationButton = rotationButton;
mRotationButton.setRotationButtonController(this);
mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
mRotationButton.setVisibilityChangedCallback(visibilityChangedCallback);
mRotationButton.setUpdatesCallback(updatesCallback);
}
void registerListeners() {

View File

@@ -23,10 +23,6 @@ import android.view.View;
import com.android.systemui.navigationbar.RotationButton;
import com.android.systemui.navigationbar.RotationButtonController;
import com.android.systemui.navigationbar.buttons.ContextualButton;
import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
import java.util.function.Consumer;
/** Containing logic for the rotation button in nav bar. */
public class RotationContextButton extends ContextualButton implements RotationButton {
@@ -48,13 +44,10 @@ public class RotationContextButton extends ContextualButton implements RotationB
}
@Override
public void setVisibilityChangedCallback(Consumer<Boolean> visibilityChangedCallback) {
setListener(new ContextButtonListener() {
@Override
public void onVisibilityChanged(ContextualButton button, boolean visible) {
if (visibilityChangedCallback != null) {
visibilityChangedCallback.accept(visible);
}
public void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback) {
setListener((button, visible) -> {
if (updatesCallback != null) {
updatesCallback.onVisibilityChanged(visible);
}
});
}

View File

@@ -20,48 +20,72 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.navigationbar.RotationButton;
import com.android.systemui.navigationbar.RotationButtonController;
import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.navigationbar.gestural.FloatingRotationButtonPositionCalculator.Position;
import java.util.function.Consumer;
/** 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 {
private static final float BACKGROUND_ALPHA = 0.92f;
private static final int MARGIN_ANIMATION_DURATION_MILLIS = 300;
private final Context mContext;
private final WindowManager mWindowManager;
private final ViewGroup mKeyButtonContainer;
private final KeyButtonView mKeyButtonView;
private final int mDiameter;
private final int mMargin;
private final int mContainerSize;
private KeyButtonDrawable mKeyButtonDrawable;
private boolean mIsShowing;
private boolean mCanShow = true;
private int mDisplayRotation;
private boolean mIsTaskbarVisible = false;
private boolean mIsTaskbarStashed = false;
private final FloatingRotationButtonPositionCalculator mPositionCalculator;
private RotationButtonController mRotationButtonController;
private Consumer<Boolean> mVisibilityChangedCallback;
private RotationButtonUpdatesCallback mUpdatesCallback;
private Position mPosition;
public FloatingRotationButton(Context context) {
mContext = context;
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mKeyButtonView = (KeyButtonView) LayoutInflater.from(mContext).inflate(
mWindowManager = context.getSystemService(WindowManager.class);
mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate(
R.layout.rotate_suggestion, null);
mKeyButtonView = mKeyButtonContainer.findViewById(R.id.rotate_suggestion);
mKeyButtonView.setVisibility(View.VISIBLE);
Resources res = mContext.getResources();
mDiameter = res.getDimensionPixelSize(R.dimen.floating_rotation_button_diameter);
mMargin = Math.max(res.getDimensionPixelSize(R.dimen.floating_rotation_button_min_margin),
Resources res = context.getResources();
int defaultMargin = Math.max(
res.getDimensionPixelSize(R.dimen.floating_rotation_button_min_margin),
res.getDimensionPixelSize(R.dimen.rounded_corner_content_padding));
int taskbarMarginLeft =
res.getDimensionPixelSize(R.dimen.floating_rotation_button_taskbar_left_margin);
int taskbarMarginBottom =
res.getDimensionPixelSize(R.dimen.floating_rotation_button_taskbar_bottom_margin);
mPositionCalculator = new FloatingRotationButtonPositionCalculator(defaultMargin,
taskbarMarginLeft, taskbarMarginBottom);
final int diameter = res.getDimensionPixelSize(R.dimen.floating_rotation_button_diameter);
mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft,
taskbarMarginBottom));
}
@Override
@@ -72,8 +96,8 @@ public class FloatingRotationButton implements RotationButton {
}
@Override
public void setVisibilityChangedCallback(Consumer<Boolean> visibilityChangedCallback) {
mVisibilityChangedCallback = visibilityChangedCallback;
public void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback) {
mUpdatesCallback = updatesCallback;
}
@Override
@@ -86,45 +110,39 @@ public class FloatingRotationButton implements RotationButton {
if (!mCanShow || mIsShowing) {
return false;
}
mIsShowing = true;
int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(mDiameter, mDiameter,
mMargin, mMargin, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags,
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
mContainerSize,
mContainerSize,
0, 0, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("FloatingRotationButton");
lp.setFitInsetsTypes(0 /*types */);
switch (mWindowManager.getDefaultDisplay().getRotation()) {
case Surface.ROTATION_0:
lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
break;
case Surface.ROTATION_90:
lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
break;
case Surface.ROTATION_180:
lp.gravity = Gravity.TOP | Gravity.RIGHT;
break;
case Surface.ROTATION_270:
lp.gravity = Gravity.TOP | Gravity.LEFT;
break;
default:
break;
}
mWindowManager.addView(mKeyButtonView, lp);
mDisplayRotation = mWindowManager.getDefaultDisplay().getRotation();
mPosition = mPositionCalculator
.calculatePosition(mDisplayRotation, mIsTaskbarVisible, mIsTaskbarStashed);
lp.gravity = mPosition.getGravity();
((FrameLayout.LayoutParams) mKeyButtonView.getLayoutParams()).gravity =
mPosition.getGravity();
updateTranslation(mPosition, /* animate */ false);
mWindowManager.addView(mKeyButtonContainer, lp);
if (mKeyButtonDrawable != null && mKeyButtonDrawable.canAnimate()) {
mKeyButtonDrawable.resetAnimation();
mKeyButtonDrawable.startAnimation();
}
mKeyButtonView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5,
int i6, int i7) {
if (mIsShowing && mVisibilityChangedCallback != null) {
mVisibilityChangedCallback.accept(true);
}
mKeyButtonView.removeOnLayoutChangeListener(this);
}
});
if (mUpdatesCallback != null) {
mUpdatesCallback.onVisibilityChanged(true);
}
return true;
}
@@ -133,10 +151,10 @@ public class FloatingRotationButton implements RotationButton {
if (!mIsShowing) {
return false;
}
mWindowManager.removeViewImmediate(mKeyButtonView);
mWindowManager.removeViewImmediate(mKeyButtonContainer);
mIsShowing = false;
if (mVisibilityChangedCallback != null) {
mVisibilityChangedCallback.accept(false);
if (mUpdatesCallback != null) {
mUpdatesCallback.onVisibilityChanged(false);
}
return true;
}
@@ -183,4 +201,43 @@ public class FloatingRotationButton implements RotationButton {
hide();
}
}
public void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) {
mIsTaskbarVisible = taskbarVisible;
mIsTaskbarStashed = taskbarStashed;
if (!mIsShowing) return;
final Position newPosition = mPositionCalculator
.calculatePosition(mDisplayRotation, mIsTaskbarVisible, mIsTaskbarStashed);
if (newPosition.getTranslationX() != mPosition.getTranslationX()
|| newPosition.getTranslationY() != mPosition.getTranslationY()) {
updateTranslation(newPosition, /* animate */ true);
mPosition = newPosition;
}
}
private void updateTranslation(Position position, boolean animate) {
final int translationX = position.getTranslationX();
final int translationY = position.getTranslationY();
if (animate) {
mKeyButtonView
.animate()
.translationX(translationX)
.translationY(translationY)
.setDuration(MARGIN_ANIMATION_DURATION_MILLIS)
.setInterpolator(new AccelerateDecelerateInterpolator())
.withEndAction(() -> {
if (mUpdatesCallback != null && mIsShowing) {
mUpdatesCallback.onPositionChanged();
}
})
.start();
} else {
mKeyButtonView.setTranslationX(translationX);
mKeyButtonView.setTranslationY(translationY);
}
}
}

View File

@@ -0,0 +1,65 @@
package com.android.systemui.navigationbar.gestural
import android.view.Gravity
import android.view.Surface
/**
* Calculates gravity and translation that is necessary to display
* the button in the correct position based on the current state
*/
internal class FloatingRotationButtonPositionCalculator(
private val defaultMargin: Int,
private val taskbarMarginLeft: Int,
private val taskbarMarginBottom: Int
) {
fun calculatePosition(
currentRotation: Int,
taskbarVisible: Boolean,
taskbarStashed: Boolean
): Position {
val isTaskbarSide = currentRotation == Surface.ROTATION_0
|| currentRotation == Surface.ROTATION_90
val useTaskbarMargin = isTaskbarSide && taskbarVisible && !taskbarStashed
val gravity = resolveGravity(currentRotation)
val marginLeft = if (useTaskbarMargin) taskbarMarginLeft else defaultMargin
val marginBottom = if (useTaskbarMargin) taskbarMarginBottom else defaultMargin
val translationX =
if (gravity and Gravity.RIGHT == Gravity.RIGHT) {
-marginLeft
} else {
marginLeft
}
val translationY =
if (gravity and Gravity.BOTTOM == Gravity.BOTTOM) {
-marginBottom
} else {
marginBottom
}
return Position(
gravity = gravity,
translationX = translationX,
translationY = translationY
)
}
data class Position(
val gravity: Int,
val translationX: Int,
val translationY: Int
)
private fun resolveGravity(rotation: Int): Int =
when (rotation) {
Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.LEFT
Surface.ROTATION_90 -> Gravity.BOTTOM or Gravity.RIGHT
Surface.ROTATION_180 -> Gravity.TOP or Gravity.RIGHT
Surface.ROTATION_270 -> Gravity.TOP or Gravity.LEFT
else -> throw IllegalArgumentException("Invalid rotation $rotation")
}
}

View File

@@ -110,6 +110,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import javax.inject.Inject;
@@ -172,55 +173,34 @@ public class OverviewProxyService extends CurrentUserTracker implements
public ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@Override
public void startScreenPinning(int taskId) {
if (!verifyCaller("startScreenPinning")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
verifyCallerAndClearCallingIdentityPostMain("startScreenPinning", () ->
mStatusBarOptionalLazy.ifPresent(
statusBarLazy -> statusBarLazy.get().showScreenPinningRequest(taskId,
false /* allowCancel */));
});
} finally {
Binder.restoreCallingIdentity(token);
}
false /* allowCancel */)));
}
@Override
public void stopScreenPinning() {
if (!verifyCaller("stopScreenPinning")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
try {
ActivityTaskManager.getService().stopSystemLockTaskMode();
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to stop screen pinning");
}
});
} finally {
Binder.restoreCallingIdentity(token);
}
verifyCallerAndClearCallingIdentityPostMain("stopScreenPinning", () -> {
try {
ActivityTaskManager.getService().stopSystemLockTaskMode();
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to stop screen pinning");
}
});
}
// TODO: change the method signature to use (boolean inputFocusTransferStarted)
@Override
public void onStatusBarMotionEvent(MotionEvent event) {
if (!verifyCaller("onStatusBarMotionEvent")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
verifyCallerAndClearCallingIdentity("onStatusBarMotionEvent", () -> {
// TODO move this logic to message queue
mStatusBarOptionalLazy.ifPresent(statusBarLazy -> {
StatusBar statusBar = statusBarLazy.get();
if (event.getActionMasked() == ACTION_DOWN) {
statusBar.getPanelController().startExpandLatencyTracking();
}
mHandler.post(()-> {
mHandler.post(() -> {
int action = event.getActionMasked();
if (action == ACTION_DOWN) {
mInputFocusTransferStarted = true;
@@ -232,50 +212,38 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
if (action == ACTION_UP || action == ACTION_CANCEL) {
mInputFocusTransferStarted = false;
float velocity = (event.getY() - mInputFocusTransferStartY)
/ (event.getEventTime() - mInputFocusTransferStartMillis);
statusBar.onInputFocusTransfer(mInputFocusTransferStarted,
action == ACTION_CANCEL,
(event.getY() - mInputFocusTransferStartY)
/ (event.getEventTime() - mInputFocusTransferStartMillis));
velocity);
}
event.recycle();
});
});
} finally {
Binder.restoreCallingIdentity(token);
}
});
}
@Override
public void onBackPressed() throws RemoteException {
if (!verifyCaller("onBackPressed")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
notifyBackAction(true, -1, -1, true, false);
});
} finally {
Binder.restoreCallingIdentity(token);
}
notifyBackAction(true, -1, -1, true, false);
});
}
@Override
public void setHomeRotationEnabled(boolean enabled) {
if (!verifyCaller("setHomeRotationEnabled")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
mHandler.post(() -> notifyHomeRotationEnabled(enabled));
});
} finally {
Binder.restoreCallingIdentity(token);
}
verifyCallerAndClearCallingIdentityPostMain("setHomeRotationEnabled", () ->
mHandler.post(() -> notifyHomeRotationEnabled(enabled)));
}
@Override
public void notifyTaskbarStatus(boolean visible, boolean stashed) {
verifyCallerAndClearCallingIdentityPostMain("notifyTaskbarStatus", () ->
onTaskbarStatusUpdated(visible, stashed));
}
private boolean sendEvent(int action, int code) {
@@ -292,124 +260,74 @@ public class OverviewProxyService extends CurrentUserTracker implements
@Override
public void onOverviewShown(boolean fromHome) {
if (!verifyCaller("onOverviewShown")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onOverviewShown(fromHome);
}
});
} finally {
Binder.restoreCallingIdentity(token);
}
verifyCallerAndClearCallingIdentityPostMain("onOverviewShown", () -> {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onOverviewShown(fromHome);
}
});
}
@Override
public Rect getNonMinimizedSplitScreenSecondaryBounds() {
if (!verifyCaller("getNonMinimizedSplitScreenSecondaryBounds")) {
return null;
}
final long token = Binder.clearCallingIdentity();
try {
return mLegacySplitScreenOptional.map(splitScreen ->
splitScreen.getDividerView().getNonMinimizedSplitScreenSecondaryBounds())
.orElse(null);
} finally {
Binder.restoreCallingIdentity(token);
}
return verifyCallerAndClearCallingIdentity(
"getNonMinimizedSplitScreenSecondaryBounds",
() -> mLegacySplitScreenOptional.map(splitScreen ->
splitScreen
.getDividerView()
.getNonMinimizedSplitScreenSecondaryBounds())
.orElse(null)
);
}
@Override
public void setNavBarButtonAlpha(float alpha, boolean animate) {
if (!verifyCaller("setNavBarButtonAlpha")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mNavBarButtonAlpha = alpha;
mHandler.post(() -> notifyNavBarButtonAlphaChanged(alpha, animate));
} finally {
Binder.restoreCallingIdentity(token);
}
verifyCallerAndClearCallingIdentityPostMain("setNavBarButtonAlpha", () ->
notifyNavBarButtonAlphaChanged(alpha, animate));
}
@Override
public void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
if (!verifyCaller("onAssistantProgress")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyAssistantProgress(progress));
} finally {
Binder.restoreCallingIdentity(token);
}
verifyCallerAndClearCallingIdentityPostMain("onAssistantProgress", () ->
notifyAssistantProgress(progress));
}
@Override
public void onAssistantGestureCompletion(float velocity) {
if (!verifyCaller("onAssistantGestureCompletion")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyAssistantGestureCompletion(velocity));
} finally {
Binder.restoreCallingIdentity(token);
}
verifyCallerAndClearCallingIdentityPostMain("onAssistantGestureCompletion", () ->
notifyAssistantGestureCompletion(velocity));
}
@Override
public void startAssistant(Bundle bundle) {
if (!verifyCaller("startAssistant")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyStartAssistant(bundle));
} finally {
Binder.restoreCallingIdentity(token);
}
verifyCallerAndClearCallingIdentityPostMain("startAssistant", () ->
notifyStartAssistant(bundle));
}
@Override
public void notifyAccessibilityButtonClicked(int displayId) {
if (!verifyCaller("notifyAccessibilityButtonClicked")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
AccessibilityManager.getInstance(mContext)
.notifyAccessibilityButtonClicked(displayId);
} finally {
Binder.restoreCallingIdentity(token);
}
verifyCallerAndClearCallingIdentity("notifyAccessibilityButtonClicked", () ->
AccessibilityManager.getInstance(mContext)
.notifyAccessibilityButtonClicked(displayId));
}
@Override
public void notifyAccessibilityButtonLongClicked() {
if (!verifyCaller("notifyAccessibilityButtonLongClicked")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
final Intent intent =
new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
} finally {
Binder.restoreCallingIdentity(token);
}
verifyCallerAndClearCallingIdentity("notifyAccessibilityButtonLongClicked",
() -> {
final Intent intent =
new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
final String chooserClassName = AccessibilityButtonChooserActivity
.class.getName();
intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
});
}
@Override
public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
Insets visibleInsets, int taskId) {
Insets visibleInsets, int taskId) {
// Deprecated
}
@@ -421,43 +339,22 @@ public class OverviewProxyService extends CurrentUserTracker implements
@Override
public void notifySwipeToHomeFinished() {
if (!verifyCaller("notifySwipeToHomeFinished")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mPipOptional.ifPresent(
pip -> pip.setPinnedStackAnimationType(
PipAnimationController.ANIM_TYPE_ALPHA));
} finally {
Binder.restoreCallingIdentity(token);
}
verifyCallerAndClearCallingIdentity("notifySwipeToHomeFinished", () ->
mPipOptional.ifPresent(
pip -> pip.setPinnedStackAnimationType(
PipAnimationController.ANIM_TYPE_ALPHA)));
}
@Override
public void notifySwipeUpGestureStarted() {
if (!verifyCaller("notifySwipeUpGestureStarted")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifySwipeUpGestureStartedInternal());
} finally {
Binder.restoreCallingIdentity(token);
}
verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () ->
notifySwipeUpGestureStartedInternal());
}
@Override
public void notifyPrioritizedRotation(@Surface.Rotation int rotation) {
if (!verifyCaller("notifyPrioritizedRotation")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyPrioritizedRotationInternal(rotation));
} finally {
Binder.restoreCallingIdentity(token);
}
verifyCallerAndClearCallingIdentityPostMain("notifyPrioritizedRotation", () ->
notifyPrioritizedRotationInternal(rotation));
}
@Override
@@ -477,15 +374,8 @@ public class OverviewProxyService extends CurrentUserTracker implements
@Override
public void expandNotificationPanel() {
if (!verifyCaller("expandNotificationPanel")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN);
} finally {
Binder.restoreCallingIdentity(token);
}
verifyCallerAndClearCallingIdentity("expandNotificationPanel",
() -> mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN));
}
private boolean verifyCaller(String reason) {
@@ -497,6 +387,29 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
return true;
}
private <T> T verifyCallerAndClearCallingIdentity(String reason, Supplier<T> supplier) {
if (!verifyCaller(reason)) {
return null;
}
final long token = Binder.clearCallingIdentity();
try {
return supplier.get();
} finally {
Binder.restoreCallingIdentity(token);
}
}
private void verifyCallerAndClearCallingIdentity(String reason, Runnable runnable) {
verifyCallerAndClearCallingIdentity(reason, () -> {
runnable.run();
return null;
});
}
private void verifyCallerAndClearCallingIdentityPostMain(String reason, Runnable runnable) {
verifyCallerAndClearCallingIdentity(reason, () -> mHandler.post(runnable));
}
};
private final Runnable mDeferredConnectionCallback = () -> {
@@ -883,6 +796,12 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
}
private void onTaskbarStatusUpdated(boolean visible, boolean stashed) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onTaskbarStatusUpdated(visible, stashed);
}
}
private void notifyConnectionChanged() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null);
@@ -1049,6 +968,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
/** Notify changes in the nav bar button alpha */
default void onNavBarButtonAlphaChanged(float alpha, boolean animate) {}
default void onHomeRotationEnabled(boolean enabled) {}
default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {}
default void onSystemUiStateChanged(int sysuiStateFlags) {}
default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
default void onAssistantGestureCompletion(float velocity) {}

View File

@@ -59,7 +59,16 @@ public class NavigationBarRotationContextTest extends SysuiTestCase {
final View view = new View(mContext);
mRotationButton = mock(RotationButton.class);
mRotationButtonController = new RotationButtonController(mContext, 0, 0);
mRotationButtonController.setRotationButton(mRotationButton, (visibility) -> {});
mRotationButtonController.setRotationButton(mRotationButton,
new RotationButton.RotationButtonUpdatesCallback() {
@Override
public void onVisibilityChanged(boolean isVisible) {
}
@Override
public void onPositionChanged() {
}
});
// Due to a mockito issue, only spy the object after setting the initial state
mRotationButtonController = spy(mRotationButtonController);
doReturn(view).when(mRotationButton).getCurrentView();

View File

@@ -0,0 +1,127 @@
package com.android.systemui.navigationbar.gestural
import android.view.Gravity
import android.view.Surface
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.navigationbar.gestural.FloatingRotationButtonPositionCalculator.Position
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@SmallTest
internal class FloatingRotationButtonPositionCalculatorTest(private val testCase: TestCase)
: SysuiTestCase() {
private val calculator = FloatingRotationButtonPositionCalculator(
MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM
)
@Test
fun calculatePosition() {
val position = calculator.calculatePosition(
testCase.rotation,
testCase.taskbarVisible,
testCase.taskbarStashed
)
assertThat(position).isEqualTo(testCase.expectedPosition)
}
internal class TestCase(
val rotation: Int,
val taskbarVisible: Boolean,
val taskbarStashed: Boolean,
val expectedPosition: Position
) {
override fun toString(): String =
"when rotation = $rotation, " +
"taskbarVisible = $taskbarVisible, " +
"taskbarStashed = $taskbarStashed - " +
"expected $expectedPosition"
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<TestCase> =
listOf(
TestCase(
rotation = Surface.ROTATION_0,
taskbarVisible = false,
taskbarStashed = false,
expectedPosition = Position(
gravity = Gravity.BOTTOM or Gravity.LEFT,
translationX = MARGIN_DEFAULT,
translationY = -MARGIN_DEFAULT
)
),
TestCase(
rotation = Surface.ROTATION_90,
taskbarVisible = false,
taskbarStashed = false,
expectedPosition = Position(
gravity = Gravity.BOTTOM or Gravity.RIGHT,
translationX = -MARGIN_DEFAULT,
translationY = -MARGIN_DEFAULT
)
),
TestCase(
rotation = Surface.ROTATION_180,
taskbarVisible = false,
taskbarStashed = false,
expectedPosition = Position(
gravity = Gravity.TOP or Gravity.RIGHT,
translationX = -MARGIN_DEFAULT,
translationY = MARGIN_DEFAULT
)
),
TestCase(
rotation = Surface.ROTATION_270,
taskbarVisible = false,
taskbarStashed = false,
expectedPosition = Position(
gravity = Gravity.TOP or Gravity.LEFT,
translationX = MARGIN_DEFAULT,
translationY = MARGIN_DEFAULT
)
),
TestCase(
rotation = Surface.ROTATION_0,
taskbarVisible = true,
taskbarStashed = false,
expectedPosition = Position(
gravity = Gravity.BOTTOM or Gravity.LEFT,
translationX = MARGIN_TASKBAR_LEFT,
translationY = -MARGIN_TASKBAR_BOTTOM
)
),
TestCase(
rotation = Surface.ROTATION_0,
taskbarVisible = true,
taskbarStashed = true,
expectedPosition = Position(
gravity = Gravity.BOTTOM or Gravity.LEFT,
translationX = MARGIN_DEFAULT,
translationY = -MARGIN_DEFAULT
)
),
TestCase(
rotation = Surface.ROTATION_90,
taskbarVisible = true,
taskbarStashed = false,
expectedPosition = Position(
gravity = Gravity.BOTTOM or Gravity.RIGHT,
translationX = -MARGIN_TASKBAR_LEFT,
translationY = -MARGIN_TASKBAR_BOTTOM
)
)
)
private const val MARGIN_DEFAULT = 10
private const val MARGIN_TASKBAR_LEFT = 20
private const val MARGIN_TASKBAR_BOTTOM = 30
}
}