diff options
7 files changed, 555 insertions, 140 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java new file mode 100644 index 000000000000..4f957bfcbbcc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java @@ -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(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java new file mode 100644 index 000000000000..1b039663ab73 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java @@ -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); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 66486cedcfb5..cbbb0e3dea96 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -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) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 9d13ea22b281..aebcb9f27e24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java new file mode 100644 index 000000000000..15e189cc8baf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java @@ -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 */); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java index 945d9b9ebf6b..2340786d81fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java @@ -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, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java new file mode 100644 index 000000000000..c792459418f3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java @@ -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()); + } +} |