Refactored the contextual group of buttons that appear on the nav bar
Refactored NavigationBarView's contextual buttons to keep track of just one button's visiblity with its container group. The visiblity is determined by the priority of the buttons. For example, accessibility has the highest priority, if that is visible, no other buttons can take its place, however if menu button is visible, then rotation can appear instead. Bug: 116041410 Test: atest NavigationBarContextTest Change-Id: Ic0762c7b7121a313b7c08989f9ab761426372c5c
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.statusbar.phone;
|
||||
|
||||
import android.annotation.DrawableRes;
|
||||
import android.annotation.IdRes;
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
|
||||
import com.android.systemui.statusbar.policy.KeyButtonView;
|
||||
|
||||
/**
|
||||
* Simple contextual button that is added to the {@link ContextualButtonGroup}. Extend if need extra
|
||||
* functionality.
|
||||
*/
|
||||
public class ContextualButton extends ButtonDispatcher {
|
||||
|
||||
protected final @DrawableRes int mIconResId;
|
||||
|
||||
/**
|
||||
* Create a contextual button that will use a {@link KeyButtonView} and
|
||||
* {@link KeyButtonDrawable} get and show the button from xml to its icon drawable.
|
||||
* @param buttonResId the button view from xml layout
|
||||
* @param iconResId icon resource to be used
|
||||
*/
|
||||
public ContextualButton(@IdRes int buttonResId, @DrawableRes int iconResId) {
|
||||
super(buttonResId);
|
||||
mIconResId = iconResId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the drawable from resource id, should reapply the previous dark intensity.
|
||||
*/
|
||||
public void updateIcon() {
|
||||
final KeyButtonDrawable currentDrawable = getImageDrawable();
|
||||
KeyButtonDrawable drawable = getNewDrawable();
|
||||
if (currentDrawable != null) {
|
||||
drawable.setDarkIntensity(currentDrawable.getDarkIntensity());
|
||||
}
|
||||
setImageDrawable(drawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibility(int visibility) {
|
||||
super.setVisibility(visibility);
|
||||
|
||||
// Stop any active animations if hidden
|
||||
final KeyButtonDrawable currentDrawable = getImageDrawable();
|
||||
if (visibility != View.VISIBLE && currentDrawable != null && currentDrawable.canAnimate()) {
|
||||
currentDrawable.clearAnimationCallbacks();
|
||||
currentDrawable.resetAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
protected KeyButtonDrawable getNewDrawable() {
|
||||
return KeyButtonDrawable.create(getContext(), mIconResId, false /* shadow */);
|
||||
}
|
||||
|
||||
protected Context getContext() {
|
||||
return getCurrentView().getContext();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.statusbar.phone;
|
||||
|
||||
import android.annotation.IdRes;
|
||||
import android.annotation.NonNull;
|
||||
import android.view.View;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ContextualButtonGroup extends ButtonDispatcher {
|
||||
private static final int INVALID_INDEX = -1;
|
||||
|
||||
// List of pairs that contains the button and if the button was visible within this group
|
||||
private final List<ButtonData> mButtonData = new ArrayList<>();
|
||||
|
||||
public ContextualButtonGroup(@IdRes int containerId) {
|
||||
super(containerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a contextual button to the group. The order of adding increases in its priority. The
|
||||
* priority is used to determine which button should be visible when setting multiple button's
|
||||
* visibility {@see setButtonVisiblity}.
|
||||
* @param button the button added to the group
|
||||
*/
|
||||
public void addButton(@NonNull ContextualButton button) {
|
||||
mButtonData.add(new ButtonData(button));
|
||||
}
|
||||
|
||||
public ContextualButton getContextButton(@IdRes int buttonResId) {
|
||||
int index = getContextButtonIndex(buttonResId);
|
||||
if (index != INVALID_INDEX) {
|
||||
return mButtonData.get(index).button;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ContextualButton getVisibleContextButton() {
|
||||
for (int i = mButtonData.size() - 1; i >= 0; --i) {
|
||||
if (mButtonData.get(i).markedVisible) {
|
||||
return mButtonData.get(i).button;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the visibility of the button by {@param buttonResId} with {@param visible}. Only one
|
||||
* button is shown at a time. The input button will only show up if it has higher priority than
|
||||
* a previous button, otherwise it will be marked as visible and shown later if all higher
|
||||
* priority buttons are invisible. Therefore hiding a button will show the next marked visible
|
||||
* button. This group's view will be visible if at least one button is visible.
|
||||
* @return if the button is visible after operation
|
||||
* @throws RuntimeException if the input id does not match any of the ids in the group
|
||||
*/
|
||||
public int setButtonVisiblity(@IdRes int buttonResId, boolean visible) {
|
||||
final int index = getContextButtonIndex(buttonResId);
|
||||
if (index == INVALID_INDEX) {
|
||||
throw new RuntimeException("Cannot find the button id of " + buttonResId
|
||||
+ " in context group");
|
||||
}
|
||||
setVisibility(View.INVISIBLE);
|
||||
mButtonData.get(index).markedVisible = visible;
|
||||
|
||||
// Make all buttons invisible except the first markedVisible button
|
||||
boolean alreadyFoundVisibleButton = false;
|
||||
int i = mButtonData.size() - 1;
|
||||
for (; i >= 0; --i) {
|
||||
final ButtonData buttonData = mButtonData.get(i);
|
||||
if (!alreadyFoundVisibleButton && buttonData.markedVisible) {
|
||||
buttonData.setVisibility(View.VISIBLE);
|
||||
setVisibility(View.VISIBLE);
|
||||
alreadyFoundVisibleButton = true;
|
||||
} else {
|
||||
buttonData.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
return mButtonData.get(index).button.getVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
* See if button is group visible. Group visible determines if a button can be visible when
|
||||
* higher priority buttons go invisible.
|
||||
* @param buttonResId the button to see if it is group visible
|
||||
* @return true if button is group visible
|
||||
*/
|
||||
public boolean isButtonVisibleWithinGroup(@IdRes int buttonResId) {
|
||||
final int index = getContextButtonIndex(buttonResId);
|
||||
return index != INVALID_INDEX && mButtonData.get(index).markedVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all the icons that are attached to this group. This will get all the buttons to update
|
||||
* their icons for their buttons.
|
||||
*/
|
||||
public void updateIcons() {
|
||||
for (ButtonData data : mButtonData) {
|
||||
data.button.updateIcon();
|
||||
}
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw) {
|
||||
pw.println("ContextualButtonGroup {");
|
||||
pw.println(" getVisibleContextButton(): " + getVisibleContextButton());
|
||||
pw.println(" isVisible(): " + isVisible());
|
||||
pw.println(" mButtonData [ ");
|
||||
for (int i = mButtonData.size() - 1; i >= 0; --i) {
|
||||
final ButtonData data = mButtonData.get(i);
|
||||
pw.println(" " + i + ": markedVisible=" + data.markedVisible
|
||||
+ " visible=" + data.button.getVisibility()
|
||||
+ " alpha=" + data.button.getAlpha());
|
||||
}
|
||||
pw.println(" ]");
|
||||
pw.println(" }");
|
||||
}
|
||||
|
||||
private int getContextButtonIndex(@IdRes int buttonResId) {
|
||||
for (int i = 0; i < mButtonData.size(); ++i) {
|
||||
if (mButtonData.get(i).button.getId() == buttonResId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return INVALID_INDEX;
|
||||
}
|
||||
|
||||
private final static class ButtonData {
|
||||
ContextualButton button;
|
||||
boolean markedVisible;
|
||||
|
||||
ButtonData(ContextualButton button) {
|
||||
this.button = button;
|
||||
this.markedVisible = false;
|
||||
}
|
||||
|
||||
void setVisibility(int visiblity) {
|
||||
button.setVisibility(visiblity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -463,7 +463,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
|
||||
style = rotationCCW ? R.style.RotateButtonCCWStart0 :
|
||||
R.style.RotateButtonCWStart0;
|
||||
}
|
||||
mNavigationBarView.updateRotateSuggestionButtonStyle(style, true);
|
||||
mNavigationBarView.updateRotateSuggestionButtonStyle(style);
|
||||
}
|
||||
|
||||
if (mNavigationBarWindowState != WINDOW_STATE_SHOWING) {
|
||||
|
||||
@@ -36,8 +36,6 @@ import android.content.res.Configuration;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
@@ -46,7 +44,6 @@ import androidx.annotation.ColorInt;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.Display;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
@@ -59,7 +56,6 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.DockedStackExistsListener;
|
||||
import com.android.systemui.Interpolators;
|
||||
@@ -106,10 +102,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
boolean mVertical;
|
||||
private int mCurrentRotation = -1;
|
||||
|
||||
boolean mShowMenu;
|
||||
boolean mShowAccessibilityButton;
|
||||
boolean mLongClickableAccessibilityButton;
|
||||
boolean mShowRotateButton;
|
||||
int mDisabledFlags = 0;
|
||||
int mNavigationIconHints = 0;
|
||||
|
||||
@@ -125,10 +118,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
private KeyButtonDrawable mHomeDefaultIcon;
|
||||
private KeyButtonDrawable mRecentIcon;
|
||||
private KeyButtonDrawable mDockedIcon;
|
||||
private KeyButtonDrawable mImeIcon;
|
||||
private KeyButtonDrawable mMenuIcon;
|
||||
private KeyButtonDrawable mAccessibilityIcon;
|
||||
private KeyButtonDrawable mRotateSuggestionIcon;
|
||||
|
||||
private GestureHelper mGestureHelper;
|
||||
private final DeadZone mDeadZone;
|
||||
@@ -151,6 +140,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
private boolean mDockedStackExists;
|
||||
|
||||
private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
|
||||
private final ContextualButtonGroup mContextualButtonGroup;
|
||||
private Configuration mConfiguration;
|
||||
|
||||
private NavigationBarInflaterView mNavigationInflaterView;
|
||||
@@ -159,8 +149,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
private RecentsOnboarding mRecentsOnboarding;
|
||||
private NotificationPanelView mPanelView;
|
||||
|
||||
private int mRotateBtnStyle = R.style.RotateButtonCCWStart90;
|
||||
|
||||
/**
|
||||
* Helper that is responsible for showing the right toast when a disallowed activity operation
|
||||
* occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in
|
||||
@@ -279,17 +267,30 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
Context.WINDOW_SERVICE)).getDefaultDisplay();
|
||||
|
||||
mVertical = false;
|
||||
mShowMenu = false;
|
||||
|
||||
mShowAccessibilityButton = false;
|
||||
mLongClickableAccessibilityButton = false;
|
||||
|
||||
// Set up the context group of buttons
|
||||
mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
|
||||
final ContextualButton menuButton = new ContextualButton(R.id.menu,
|
||||
R.drawable.ic_sysbar_menu);
|
||||
final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
|
||||
R.drawable.ic_ime_switcher_default);
|
||||
final RotationContextButton rotateSuggestionButton = new RotationContextButton(
|
||||
R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button,
|
||||
R.style.RotateButtonCCWStart90);
|
||||
final ContextualButton accessibilityButton =
|
||||
new ContextualButton(R.id.accessibility_button,
|
||||
R.drawable.ic_sysbar_accessibility_button);
|
||||
mContextualButtonGroup.addButton(menuButton);
|
||||
mContextualButtonGroup.addButton(imeSwitcherButton);
|
||||
mContextualButtonGroup.addButton(rotateSuggestionButton);
|
||||
mContextualButtonGroup.addButton(accessibilityButton);
|
||||
|
||||
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
|
||||
mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
|
||||
|
||||
mConfiguration = new Configuration();
|
||||
mConfiguration.updateFrom(context.getResources().getConfiguration());
|
||||
reloadNavIcons();
|
||||
|
||||
mScreenPinningNotify = new ScreenPinningNotify(mContext);
|
||||
mBarTransitions = new NavigationBarTransitions(this);
|
||||
@@ -297,14 +298,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
|
||||
mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
|
||||
mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
|
||||
mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
|
||||
mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
|
||||
mButtonDispatchers.put(R.id.accessibility_button,
|
||||
new ButtonDispatcher(R.id.accessibility_button));
|
||||
mButtonDispatchers.put(R.id.rotate_suggestion,
|
||||
new ButtonDispatcher(R.id.rotate_suggestion));
|
||||
mButtonDispatchers.put(R.id.menu_container,
|
||||
new ButtonDispatcher(R.id.menu_container));
|
||||
mButtonDispatchers.put(R.id.menu, menuButton);
|
||||
mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton);
|
||||
mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
|
||||
mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
|
||||
mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
|
||||
mDeadZone = new DeadZone(this);
|
||||
}
|
||||
|
||||
@@ -432,10 +430,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
return mButtonDispatchers.get(R.id.rotate_suggestion);
|
||||
}
|
||||
|
||||
public ButtonDispatcher getMenuContainer() {
|
||||
return mButtonDispatchers.get(R.id.menu_container);
|
||||
}
|
||||
|
||||
public SparseArray<ButtonDispatcher> getButtonDispatchers() {
|
||||
return mButtonDispatchers;
|
||||
}
|
||||
@@ -473,14 +467,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
}
|
||||
if (densityChange || dirChange) {
|
||||
mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent);
|
||||
mMenuIcon = getDrawable(R.drawable.ic_sysbar_menu);
|
||||
|
||||
mAccessibilityIcon = getDrawable(R.drawable.ic_sysbar_accessibility_button,
|
||||
false /* hasShadow */);
|
||||
|
||||
mImeIcon = getDrawable(R.drawable.ic_ime_switcher_default, false /* hasShadow */);
|
||||
|
||||
updateRotateSuggestionButtonStyle(mRotateBtnStyle, false);
|
||||
mContextualButtonGroup.updateIcons();
|
||||
}
|
||||
if (orientationChange || densityChange || dirChange) {
|
||||
mBackIcon = getBackDrawable();
|
||||
@@ -538,19 +525,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
}
|
||||
|
||||
private KeyButtonDrawable getDrawable(@DrawableRes int icon) {
|
||||
return getDrawable(mContext, icon, true /* hasShadow */);
|
||||
return KeyButtonDrawable.create(mContext, icon, true /* hasShadow */);
|
||||
}
|
||||
|
||||
private KeyButtonDrawable getDrawable(@DrawableRes int icon, boolean hasShadow) {
|
||||
return getDrawable(mContext, icon, hasShadow);
|
||||
}
|
||||
|
||||
private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int icon, boolean hasShadow) {
|
||||
final int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
|
||||
final int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
|
||||
Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
|
||||
Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
|
||||
return KeyButtonDrawable.create(lightContext, darkContext, icon, hasShadow);
|
||||
return KeyButtonDrawable.create(mContext, icon, hasShadow);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -609,24 +588,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
updateRecentsIcon();
|
||||
|
||||
// Update IME button visibility, a11y and rotate button always overrides the appearance
|
||||
final boolean showImeButton =
|
||||
!mShowAccessibilityButton &&
|
||||
!mShowRotateButton &&
|
||||
((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
|
||||
getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE);
|
||||
getImeSwitchButton().setImageDrawable(mImeIcon);
|
||||
updateContextualContainerVisibility();
|
||||
|
||||
// Update menu button, visibility logic in method
|
||||
setMenuVisibility(mShowMenu, true);
|
||||
getMenuButton().setImageDrawable(mMenuIcon);
|
||||
|
||||
// Update rotate button, visibility altered by a11y button logic
|
||||
getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon);
|
||||
|
||||
// Update a11y button, visibility logic in state method
|
||||
setAccessibilityButtonState(mShowAccessibilityButton, mLongClickableAccessibilityButton);
|
||||
getAccessibilityButton().setImageDrawable(mAccessibilityIcon);
|
||||
mContextualButtonGroup.setButtonVisiblity(R.id.ime_switcher,
|
||||
(mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
|
||||
|
||||
mBarTransitions.reapplyDarkIntensity();
|
||||
|
||||
@@ -782,88 +745,28 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
}
|
||||
|
||||
public void setMenuVisibility(final boolean show) {
|
||||
setMenuVisibility(show, false);
|
||||
mContextualButtonGroup.setButtonVisiblity(R.id.menu, show);
|
||||
}
|
||||
|
||||
public void setMenuVisibility(final boolean show, final boolean force) {
|
||||
if (!force && mShowMenu == show) return;
|
||||
|
||||
mShowMenu = show;
|
||||
|
||||
// Only show Menu if IME switcher, rotate and Accessibility buttons are not shown.
|
||||
final boolean shouldShow = mShowMenu &&
|
||||
!mShowAccessibilityButton &&
|
||||
!mShowRotateButton &&
|
||||
((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0);
|
||||
|
||||
getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
|
||||
updateContextualContainerVisibility();
|
||||
public void updateRotateSuggestionButtonStyle(@StyleRes int style) {
|
||||
RotationContextButton button = (RotationContextButton) mContextualButtonGroup
|
||||
.getContextButton(R.id.rotate_suggestion);
|
||||
button.setStyle(style);
|
||||
button.updateIcon();
|
||||
}
|
||||
|
||||
public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) {
|
||||
mShowAccessibilityButton = visible;
|
||||
mLongClickableAccessibilityButton = longClickable;
|
||||
if (visible) {
|
||||
// Accessibility button overrides Menu, IME switcher and rotate buttons.
|
||||
setMenuVisibility(false, true);
|
||||
getImeSwitchButton().setVisibility(View.INVISIBLE);
|
||||
setRotateButtonVisibility(false);
|
||||
}
|
||||
|
||||
getAccessibilityButton().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
|
||||
getAccessibilityButton().setLongClickable(longClickable);
|
||||
updateContextualContainerVisibility();
|
||||
}
|
||||
|
||||
public void updateRotateSuggestionButtonStyle(@StyleRes int style, boolean setIcon) {
|
||||
mRotateBtnStyle = style;
|
||||
final Context ctx = getContext();
|
||||
|
||||
// Use the supplied style to set the icon's rotation parameters
|
||||
Context rotateContext = new ContextThemeWrapper(ctx, style);
|
||||
|
||||
// Recreate the icon and set it if needed
|
||||
float previousIntensity = mRotateSuggestionIcon != null
|
||||
? mRotateSuggestionIcon.getDarkIntensity() : 0;
|
||||
mRotateSuggestionIcon = getDrawable(rotateContext, R.drawable.ic_sysbar_rotate_button,
|
||||
false /* hasShadow */);
|
||||
mRotateSuggestionIcon.setDarkIntensity(previousIntensity);
|
||||
|
||||
if (setIcon) getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon);
|
||||
mContextualButtonGroup.setButtonVisiblity(R.id.accessibility_button, visible);
|
||||
}
|
||||
|
||||
public int setRotateButtonVisibility(boolean visible) {
|
||||
// Never show if a11y is visible
|
||||
final boolean adjVisible = visible && !mShowAccessibilityButton;
|
||||
final int vis = adjVisible ? View.VISIBLE : View.INVISIBLE;
|
||||
|
||||
// No need to do anything if the request matches the current state
|
||||
if (vis == getRotateSuggestionButton().getVisibility()) return vis;
|
||||
|
||||
getRotateSuggestionButton().setVisibility(vis);
|
||||
mShowRotateButton = visible;
|
||||
updateContextualContainerVisibility();
|
||||
|
||||
// Stop any active animations if hidden
|
||||
if (!visible && mRotateSuggestionIcon.canAnimate()) {
|
||||
mRotateSuggestionIcon.clearAnimationCallbacks();
|
||||
mRotateSuggestionIcon.resetAnimation();
|
||||
}
|
||||
|
||||
// Hide/restore other button visibility, if necessary
|
||||
updateNavButtonIcons();
|
||||
|
||||
// Return applied visibility
|
||||
return vis;
|
||||
return mContextualButtonGroup.setButtonVisiblity(R.id.rotate_suggestion, visible);
|
||||
}
|
||||
|
||||
public boolean isRotateButtonVisible() { return mShowRotateButton; }
|
||||
|
||||
private void updateContextualContainerVisibility() {
|
||||
// Only show the menu container when one of its buttons are visible
|
||||
getMenuContainer().setVisibility((mShowAccessibilityButton || mShowRotateButton || mShowMenu
|
||||
|| (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0)
|
||||
? VISIBLE : INVISIBLE);
|
||||
public boolean isRotateButtonVisible() {
|
||||
return getRotateSuggestionButton().isVisible();
|
||||
}
|
||||
|
||||
void hideRecentsOnboarding() {
|
||||
@@ -897,6 +800,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
|
||||
DockedStackExistsListener.register(mDockedListener);
|
||||
updateRotatedViews();
|
||||
reloadNavIcons();
|
||||
}
|
||||
|
||||
public void onDarkIntensityChange(float intensity) {
|
||||
@@ -998,7 +902,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
|
||||
// force the low profile & disabled states into compliance
|
||||
mBarTransitions.init();
|
||||
setMenuVisibility(mShowMenu, true /* force */);
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
|
||||
@@ -1204,17 +1107,19 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|
||||
pw.println(String.format(" disabled=0x%08x vertical=%s menu=%s",
|
||||
mDisabledFlags,
|
||||
mVertical ? "true" : "false",
|
||||
mShowMenu ? "true" : "false"));
|
||||
getMenuButton().isVisible() ? "true" : "false"));
|
||||
|
||||
dumpButton(pw, "back", getBackButton());
|
||||
dumpButton(pw, "home", getHomeButton());
|
||||
dumpButton(pw, "rcnt", getRecentsButton());
|
||||
dumpButton(pw, "menu", getMenuButton());
|
||||
dumpButton(pw, "rota", getRotateSuggestionButton());
|
||||
dumpButton(pw, "a11y", getAccessibilityButton());
|
||||
|
||||
mRecentsOnboarding.dump(pw);
|
||||
|
||||
pw.println(" }");
|
||||
|
||||
mContextualButtonGroup.dump(pw);
|
||||
mRecentsOnboarding.dump(pw);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.statusbar.phone;
|
||||
|
||||
import android.annotation.DrawableRes;
|
||||
import android.annotation.IdRes;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.StyleRes;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.View;
|
||||
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
|
||||
import com.android.systemui.util.Utils;
|
||||
|
||||
public class RotationContextButton extends ContextualButton {
|
||||
|
||||
private @StyleRes int mStyleRes;
|
||||
|
||||
public RotationContextButton(@IdRes int buttonResId, @DrawableRes int iconResId,
|
||||
@StyleRes int style) {
|
||||
super(buttonResId, iconResId);
|
||||
mStyleRes = style;
|
||||
}
|
||||
|
||||
public void setStyle(@StyleRes int styleRes) {
|
||||
mStyleRes = styleRes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibility(int visibility) {
|
||||
super.setVisibility(visibility);
|
||||
|
||||
// Start the rotation animation once it becomes visible
|
||||
final KeyButtonDrawable currentDrawable = getImageDrawable();
|
||||
if (visibility == View.VISIBLE && currentDrawable != null) {
|
||||
currentDrawable.resetAnimation();
|
||||
currentDrawable.startAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyButtonDrawable getNewDrawable() {
|
||||
Context context = new ContextThemeWrapper(getContext().getApplicationContext(), mStyleRes);
|
||||
return KeyButtonDrawable.create(context, mIconResId, false /* shadow */);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.policy;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.DrawableRes;
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -35,6 +36,7 @@ import android.graphics.Rect;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.systemui.R;
|
||||
|
||||
@@ -388,6 +390,23 @@ public class KeyButtonDrawable extends Drawable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a KeyButtonDrawable with a shadow given its icon. The tint applied to the drawable
|
||||
* is determined by the dark and light theme given by the context.
|
||||
* @param ctx Context to get the drawable and determine the dark and light theme
|
||||
* @param icon the icon resource id
|
||||
* @param hasShadow if a shadow will appear with the drawable
|
||||
* @return KeyButtonDrawable
|
||||
*/
|
||||
public static KeyButtonDrawable create(@NonNull Context ctx, @DrawableRes int icon,
|
||||
boolean hasShadow) {
|
||||
final int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
|
||||
final int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
|
||||
Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
|
||||
Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
|
||||
return KeyButtonDrawable.create(lightContext, darkContext, icon, hasShadow);
|
||||
}
|
||||
|
||||
public static KeyButtonDrawable create(Context lightContext, Context darkContext,
|
||||
@DrawableRes int iconResId, boolean hasShadow) {
|
||||
return create(lightContext,
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.statusbar.phone;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.view.View;
|
||||
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
/** atest NavigationBarContextTest */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NavigationBarContextTest extends SysuiTestCase {
|
||||
private static final int GROUP_ID = 1;
|
||||
private static final int BUTTON_0_ID = GROUP_ID + 1;
|
||||
private static final int BUTTON_1_ID = GROUP_ID + 2;
|
||||
private static final int BUTTON_2_ID = GROUP_ID + 3;
|
||||
|
||||
private static final float TEST_DARK_INTENSITY = 0.6f;
|
||||
private static final float DARK_INTENSITY_ERR = 0.0002f;
|
||||
private static final int ICON_RES_ID = 1;
|
||||
|
||||
private ContextualButtonGroup mGroup;
|
||||
private ContextualButton mBtn0;
|
||||
private ContextualButton mBtn1;
|
||||
private ContextualButton mBtn2;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mGroup = new ContextualButtonGroup(GROUP_ID);
|
||||
mBtn0 = new ContextualButton(BUTTON_0_ID, ICON_RES_ID);
|
||||
mBtn1 = new ContextualButton(BUTTON_1_ID, ICON_RES_ID);
|
||||
mBtn2 = new ContextualButton(BUTTON_2_ID, ICON_RES_ID);
|
||||
|
||||
// Order of adding buttons to group determines the priority, ascending priority order
|
||||
mGroup.addButton(mBtn0);
|
||||
mGroup.addButton(mBtn1);
|
||||
mGroup.addButton(mBtn2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddGetContextButtons() throws Exception {
|
||||
assertEquals(mBtn0, mGroup.getContextButton(BUTTON_0_ID));
|
||||
assertEquals(mBtn1, mGroup.getContextButton(BUTTON_1_ID));
|
||||
assertEquals(mBtn2, mGroup.getContextButton(BUTTON_2_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetButtonVisibility() throws Exception {
|
||||
assertFalse("By default the group should be invisible.", mGroup.isVisible());
|
||||
|
||||
// Set button 1 to be visible, make sure it is the only visible button
|
||||
showButton(mBtn1);
|
||||
assertFalse(mBtn0.isVisible());
|
||||
assertTrue(mBtn1.isVisible());
|
||||
assertFalse(mBtn2.isVisible());
|
||||
|
||||
// Hide button 1 and make sure the group is also invisible
|
||||
assertNotEquals(mGroup.setButtonVisiblity(BUTTON_1_ID, false /* visible */), View.VISIBLE);
|
||||
assertFalse("No buttons are visible, group should also be hidden", mGroup.isVisible());
|
||||
assertNull("No buttons should be visible", mGroup.getVisibleContextButton());
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void testSetButtonVisibilityUnaddedButton() throws Exception {
|
||||
int id = mBtn2.getId() + 1;
|
||||
mGroup.setButtonVisiblity(id, true /* visible */);
|
||||
fail("Did not throw when setting a button with an invalid id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetHigherPriorityButton() throws Exception {
|
||||
// Show button 0
|
||||
showButton(mBtn0);
|
||||
|
||||
// Show button 1
|
||||
showButton(mBtn1);
|
||||
assertTrue("Button 0 should be visible behind",
|
||||
mGroup.isButtonVisibleWithinGroup(mBtn0.getId()));
|
||||
|
||||
// Show button 2
|
||||
showButton(mBtn2);
|
||||
assertTrue("Button 1 should be visible behind",
|
||||
mGroup.isButtonVisibleWithinGroup(mBtn1.getId()));
|
||||
assertTrue(mGroup.isButtonVisibleWithinGroup(mBtn0.getId()));
|
||||
assertTrue(mGroup.isButtonVisibleWithinGroup(mBtn1.getId()));
|
||||
assertTrue(mGroup.isButtonVisibleWithinGroup(mBtn2.getId()));
|
||||
|
||||
// Hide button 2
|
||||
assertNotEquals(mGroup.setButtonVisiblity(BUTTON_2_ID, false /* visible */), View.VISIBLE);
|
||||
assertEquals("Hiding button 2 should show button 1", mBtn1,
|
||||
mGroup.getVisibleContextButton());
|
||||
|
||||
// Hide button 1
|
||||
assertNotEquals(mGroup.setButtonVisiblity(BUTTON_1_ID, false /* visible */), View.VISIBLE);
|
||||
assertEquals("Hiding button 1 should show button 0", mBtn0,
|
||||
mGroup.getVisibleContextButton());
|
||||
|
||||
// Hide button 0, all buttons are now invisible
|
||||
assertNotEquals(mGroup.setButtonVisiblity(BUTTON_0_ID, false /* visible */), View.VISIBLE);
|
||||
assertFalse("No buttons are visible, group should also be invisible", mGroup.isVisible());
|
||||
assertNull(mGroup.getVisibleContextButton());
|
||||
assertFalse(mGroup.isButtonVisibleWithinGroup(mBtn0.getId()));
|
||||
assertFalse(mGroup.isButtonVisibleWithinGroup(mBtn1.getId()));
|
||||
assertFalse(mGroup.isButtonVisibleWithinGroup(mBtn2.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetLowerPriorityButton() throws Exception {
|
||||
// Show button 2
|
||||
showButton(mBtn2);
|
||||
|
||||
// Show button 1
|
||||
assertNotEquals(mGroup.setButtonVisiblity(BUTTON_1_ID, true /* visible */), View.VISIBLE);
|
||||
assertTrue("Showing button 1 lower priority should be hidden but visible underneath",
|
||||
mGroup.isButtonVisibleWithinGroup(BUTTON_1_ID));
|
||||
assertFalse(mBtn0.isVisible());
|
||||
assertFalse(mBtn1.isVisible());
|
||||
assertTrue(mBtn2.isVisible());
|
||||
|
||||
// Hide button 1
|
||||
assertNotEquals(mGroup.setButtonVisiblity(BUTTON_1_ID, false /* visible */), View.VISIBLE);
|
||||
assertFalse("Hiding button 1 with lower priority hides itself underneath",
|
||||
mGroup.isButtonVisibleWithinGroup(BUTTON_1_ID));
|
||||
assertTrue("A button still visible, group should also be visible", mGroup.isVisible());
|
||||
assertEquals(mBtn2, mGroup.getVisibleContextButton());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSamePriorityButton() throws Exception {
|
||||
// Show button 1
|
||||
showButton(mBtn1);
|
||||
|
||||
// Show button 1 again
|
||||
showButton(mBtn1);
|
||||
|
||||
// The original button should still be visible
|
||||
assertEquals(mBtn1, mGroup.getVisibleContextButton());
|
||||
assertFalse(mGroup.isButtonVisibleWithinGroup(mBtn0.getId()));
|
||||
assertFalse(mGroup.isButtonVisibleWithinGroup(mBtn2.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateIconsDarkIntensity() throws Exception {
|
||||
final int unusedColor = 0;
|
||||
final Drawable d = mock(Drawable.class);
|
||||
final ContextualButton button = spy(mBtn0);
|
||||
final KeyButtonDrawable kbd1 = spy(new KeyButtonDrawable(d, unusedColor, unusedColor));
|
||||
final KeyButtonDrawable kbd2 = spy(new KeyButtonDrawable(d, unusedColor, unusedColor));
|
||||
kbd1.setDarkIntensity(TEST_DARK_INTENSITY);
|
||||
kbd2.setDarkIntensity(0f);
|
||||
|
||||
// Update icon returns the drawable intensity to half
|
||||
doReturn(kbd1).when(button).getNewDrawable();
|
||||
button.updateIcon();
|
||||
assertEquals(TEST_DARK_INTENSITY, kbd1.getDarkIntensity(), DARK_INTENSITY_ERR);
|
||||
|
||||
// Return old dark intensity on new drawable after update icon
|
||||
doReturn(kbd2).when(button).getNewDrawable();
|
||||
button.updateIcon();
|
||||
assertEquals(TEST_DARK_INTENSITY, kbd2.getDarkIntensity(), DARK_INTENSITY_ERR);
|
||||
}
|
||||
|
||||
private void showButton(ContextualButton button) {
|
||||
assertEquals(View.VISIBLE, mGroup.setButtonVisiblity(button.getId(), true /* visible */));
|
||||
assertTrue("After set a button visible, group should also be visible", mGroup.isVisible());
|
||||
assertEquals(button, mGroup.getVisibleContextButton());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user