Merge "Refactored the contextual group of buttons that appear on the nav bar"

This commit is contained in:
TreeHugger Robot
2018-09-22 02:26:30 +00:00
committed by Android (Google) Code Review
7 changed files with 555 additions and 140 deletions

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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 */);
}
}

View File

@@ -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,

View File

@@ -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());
}
}