Files
Flexera-Launcher/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
T
Cosmin Băieș 0703d614e0 Clarify state names for IME Switcher button
We had a few instances of properties referencing the IME Switcher or its
visibility, when in fact this was related to the button, not the menu.

This clarifies the names for these states, as well as adding some
documentation around where these values are used/passed.

Flag: EXEMPT bugfix
Bug: 366129400
Test: n/a
Change-Id: I962a9c3e1b0686d51e90a86e70fdf21b3b625e58
2025-01-15 16:55:19 +01:00

1425 lines
66 KiB
Java

/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.taskbar;
import static android.view.KeyEvent.ACTION_UP;
import static android.view.View.AccessibilityDelegate;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.launcher3.LauncherAnimUtils.ROTATION_DRAWABLE_PERCENT;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_SPACE;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_SMALL_SCREEN;
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
import android.animation.Animator;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
import android.content.Context;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.graphics.drawable.RotateDrawable;
import android.inputmethodservice.InputMethodService;
import android.os.Handler;
import android.util.Property;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.inputmethod.Flags;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Space;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory;
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter;
import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
import com.android.launcher3.util.DimensionUtils;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.views.BaseDragLayer;
import com.android.systemui.shared.navigationbar.KeyButtonRipple;
import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.shared.rotation.RotationButton;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.StringJoiner;
import java.util.function.IntPredicate;
/**
* Controller for managing nav bar buttons in taskbar
*/
public class NavbarButtonsViewController implements TaskbarControllers.LoggableTaskbarController,
BubbleBarController.BubbleBarLocationListener {
private final Rect mTempRect = new Rect();
/** Whether the IME Switcher button is visible. */
private static final int FLAG_IME_SWITCHER_BUTTON_VISIBLE = 1 << 0;
/** Whether the IME is visible. */
private static final int FLAG_IME_VISIBLE = 1 << 1;
private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2;
private static final int FLAG_A11Y_VISIBLE = 1 << 3;
private static final int FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE = 1 << 4;
private static final int FLAG_KEYGUARD_VISIBLE = 1 << 5;
private static final int FLAG_KEYGUARD_OCCLUDED = 1 << 6;
private static final int FLAG_DISABLE_HOME = 1 << 7;
private static final int FLAG_DISABLE_RECENTS = 1 << 8;
private static final int FLAG_DISABLE_BACK = 1 << 9;
private static final int FLAG_NOTIFICATION_SHADE_EXPANDED = 1 << 10;
private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 11;
private static final int FLAG_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 12;
private static final int FLAG_SMALL_SCREEN = 1 << 13;
private static final int FLAG_SLIDE_IN_VIEW_VISIBLE = 1 << 14;
private static final int FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING = 1 << 15;
/**
* Flags where a UI could be over Taskbar surfaces, so the color override should be disabled.
*/
private static final int FLAGS_ON_BACKGROUND_COLOR_OVERRIDE_DISABLED =
FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_VOICE_INTERACTION_WINDOW_SHOWING;
private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons";
private static final double SQUARE_ASPECT_RATIO_BOTTOM_BOUND = 0.95;
private static final double SQUARE_ASPECT_RATIO_UPPER_BOUND = 1.05;
public static final int ALPHA_INDEX_IMMERSIVE_MODE = 0;
public static final int ALPHA_INDEX_KEYGUARD_OR_DISABLE = 1;
public static final int ALPHA_INDEX_SUW = 2;
private static final int NUM_ALPHA_CHANNELS = 3;
private static final long AUTODIM_TIMEOUT_MS = 2250;
private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>();
private final ArrayList<ImageView> mAllButtons = new ArrayList<>();
private int mState;
private final TaskbarActivityContext mContext;
private final @Nullable Context mNavigationBarPanelContext;
private final WindowManagerProxy mWindowManagerProxy;
private final NearestTouchFrame mNavButtonsView;
private final Handler mHandler;
private final LinearLayout mNavButtonContainer;
// Used for IME+A11Y buttons
private final ViewGroup mEndContextualContainer;
private final ViewGroup mStartContextualContainer;
private final int mLightIconColorOnWorkspace;
private final int mDarkIconColorOnWorkspace;
/** Color to use for navbar buttons, if they are on on a Taskbar surface background. */
private final int mOnBackgroundIconColor;
private @Nullable Animator mNavBarLocationAnimator;
private @Nullable BubbleBarLocation mBubbleBarTargetLocation;
private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
this::updateNavButtonTranslationY);
private final AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay = new AnimatedFloat(
this::updateNavButtonTranslationY);
private final AnimatedFloat mTaskbarNavButtonTranslationYForIme = new AnimatedFloat(
this::updateNavButtonTranslationY);
private float mLastSetNavButtonTranslationY;
// Used for System UI state updates that should translate the nav button for in-app display.
private final AnimatedFloat mNavButtonInAppDisplayProgressForSysui = new AnimatedFloat(
this::updateNavButtonInAppDisplayProgressForSysui);
/**
* Expected nav button dark intensity piped down from {@code LightBarController} in framework
* via {@code TaskbarDelegate}.
*/
private final AnimatedFloat mTaskbarNavButtonDarkIntensity = new AnimatedFloat(
this::onDarkIntensityChanged);
/** {@code 1} if the Taskbar background color is fully opaque. */
private final AnimatedFloat mOnTaskbarBackgroundNavButtonColorOverride = new AnimatedFloat(
this::updateNavButtonColor);
/** {@code 1} if a Taskbar slide in overlay is visible over Taskbar. */
private final AnimatedFloat mSlideInViewVisibleNavButtonColorOverride = new AnimatedFloat(
this::updateNavButtonColor);
/** Disables the {@link #mOnBackgroundIconColor} override if {@code 0}. */
private final AnimatedFloat mOnBackgroundNavButtonColorOverrideMultiplier = new AnimatedFloat(
this::updateNavButtonColor);
private final RotationButtonListener mRotationButtonListener = new RotationButtonListener();
private final Rect mFloatingRotationButtonBounds = new Rect();
// Initialized in init.
private TaskbarControllers mControllers;
private boolean mIsImeRenderingNavButtons;
private ImageView mA11yButton;
@SystemUiStateFlags
private long mSysuiStateFlags;
private ImageView mBackButton;
private ImageView mHomeButton;
private MultiValueAlpha mBackButtonAlpha;
private MultiValueAlpha mHomeButtonAlpha;
private FloatingRotationButton mFloatingRotationButton;
private ImageView mImeSwitcherButton;
// Variables for moving nav buttons to a separate window above IME
private boolean mAreNavButtonsInSeparateWindow = false;
private BaseDragLayer<TaskbarActivityContext> mSeparateWindowParent; // Initialized in init.
private final ViewTreeObserver.OnComputeInternalInsetsListener mSeparateWindowInsetsComputer =
this::onComputeInsetsForSeparateWindow;
private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
private ImageView mRecentsButton;
private Space mSpace;
private TaskbarTransitions mTaskbarTransitions;
private @BarTransitions.TransitionMode int mTransitionMode;
private final Runnable mAutoDim = () -> mTaskbarTransitions.setAutoDim(true);
public NavbarButtonsViewController(TaskbarActivityContext context,
@Nullable Context navigationBarPanelContext, NearestTouchFrame navButtonsView,
Handler handler) {
mContext = context;
mNavigationBarPanelContext = navigationBarPanelContext;
mWindowManagerProxy = WindowManagerProxy.INSTANCE.get(mContext);
mNavButtonsView = navButtonsView;
mHandler = handler;
mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);
mLightIconColorOnWorkspace = context.getColor(R.color.taskbar_nav_icon_light_color_on_home);
mDarkIconColorOnWorkspace = context.getColor(R.color.taskbar_nav_icon_dark_color_on_home);
mOnBackgroundIconColor = Utilities.isDarkTheme(context)
? context.getColor(R.color.taskbar_nav_icon_light_color)
: context.getColor(R.color.taskbar_nav_icon_dark_color);
if (mContext.isPhoneMode()) {
mTaskbarTransitions = new TaskbarTransitions(mContext, mNavButtonsView);
}
}
/**
* Initializes the controller
*/
public void init(TaskbarControllers controllers) {
mControllers = controllers;
setupController();
}
protected void setupController() {
boolean isThreeButtonNav = mContext.isThreeButtonNav();
DeviceProfile deviceProfile = mContext.getDeviceProfile();
Resources resources = mContext.getResources();
int setupSize = mControllers.taskbarActivityContext.getSetupWindowSize();
Point p = DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
mContext.isPhoneMode(), mContext.isGestureNav());
ViewGroup.LayoutParams navButtonsViewLayoutParams = mNavButtonsView.getLayoutParams();
navButtonsViewLayoutParams.width = p.x;
if (!mContext.isUserSetupComplete()) {
// Setup mode in phone mode uses gesture nav.
navButtonsViewLayoutParams.height = setupSize;
} else {
navButtonsViewLayoutParams.height = p.y;
}
mNavButtonsView.setLayoutParams(navButtonsViewLayoutParams);
mIsImeRenderingNavButtons =
InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
if (!mIsImeRenderingNavButtons) {
// IME switcher
final int switcherResId = Flags.imeSwitcherRevamp()
? com.android.internal.R.drawable.ic_ime_switcher_new
: R.drawable.ic_ime_switcher;
mImeSwitcherButton = addButton(switcherResId, BUTTON_IME_SWITCH,
isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
mControllers.navButtonController, R.id.ime_switcher);
mPropertyHolders.add(new StatePropertyHolder(mImeSwitcherButton,
flags -> ((flags & FLAG_IME_SWITCHER_BUTTON_VISIBLE) != 0)
&& ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
}
mPropertyHolders.add(new StatePropertyHolder(
mControllers.taskbarViewController.getTaskbarIconAlpha()
.get(ALPHA_INDEX_KEYGUARD),
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0
&& (flags & FLAG_SCREEN_PINNING_ACTIVE) == 0));
mPropertyHolders.add(new StatePropertyHolder(
mControllers.taskbarViewController.getTaskbarIconAlpha()
.get(ALPHA_INDEX_SMALL_SCREEN),
flags -> (flags & FLAG_SMALL_SCREEN) == 0));
if (!mContext.isPhoneMode()) {
mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
.getKeyguardBgTaskbar(), flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0));
}
// Start at 1 because relevant flags are unset at init.
mOnBackgroundNavButtonColorOverrideMultiplier.value = 1;
// Force nav buttons (specifically back button) to be visible during setup wizard.
boolean isInSetup = !mContext.isUserSetupComplete();
boolean isInKidsMode = mContext.isNavBarKidsModeActive();
boolean alwaysShowButtons = isThreeButtonNav || isInSetup;
// Make sure to remove nav bar buttons translation when any of the following occur:
// - Notification shade is expanded
// - IME is showing (add separate translation for IME)
// - VoiceInteractionWindow (assistant) is showing
// - Keyboard shortcuts helper is showing
if (!mContext.isPhoneMode()) {
int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE
| FLAG_VOICE_INTERACTION_WINDOW_SHOWING | FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING;
mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui,
flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
1, 0));
// Center nav buttons in new height for IME.
float transForIme = (mContext.getDeviceProfile().taskbarHeight
- mControllers.taskbarInsetsController.getTaskbarHeightForIme()) / 2f;
// For gesture nav, nav buttons only show for IME anyway so keep them translated down.
float defaultButtonTransY = alwaysShowButtons ? 0 : transForIme;
mPropertyHolders.add(new StatePropertyHolder(mTaskbarNavButtonTranslationYForIme,
flags -> (flags & FLAG_IME_VISIBLE) != 0 && !isInKidsMode, AnimatedFloat.VALUE,
transForIme, defaultButtonTransY));
mPropertyHolders.add(new StatePropertyHolder(
mOnBackgroundNavButtonColorOverrideMultiplier,
flags -> (flags & FLAGS_ON_BACKGROUND_COLOR_OVERRIDE_DISABLED) == 0));
mPropertyHolders.add(new StatePropertyHolder(
mSlideInViewVisibleNavButtonColorOverride,
flags -> (flags & FLAG_SLIDE_IN_VIEW_VISIBLE) != 0));
}
if (alwaysShowButtons) {
initButtons(mNavButtonContainer, mEndContextualContainer,
mControllers.navButtonController);
updateButtonLayoutSpacing();
updateStateForFlag(FLAG_SMALL_SCREEN, mContext.isPhoneMode());
if (!mContext.isPhoneMode()) {
mPropertyHolders.add(new StatePropertyHolder(
mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
}
} else if (!mIsImeRenderingNavButtons) {
View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
mStartContextualContainer, mControllers.navButtonController, R.id.back);
imeDownButton.setRotation(Utilities.isRtl(resources) ? 90 : -90);
// Only show when IME is visible.
mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
flags -> (flags & FLAG_IME_VISIBLE) != 0));
}
mFloatingRotationButton = new FloatingRotationButton(
ENABLE_TASKBAR_NAVBAR_UNIFICATION ? mNavigationBarPanelContext : mContext,
R.string.accessibility_rotate_button,
R.layout.rotate_suggestion,
R.id.rotate_suggestion,
R.dimen.floating_rotation_button_min_margin,
R.dimen.rounded_corner_content_padding,
R.dimen.floating_rotation_button_taskbar_left_margin,
R.dimen.floating_rotation_button_taskbar_bottom_margin,
R.dimen.floating_rotation_button_diameter,
R.dimen.key_button_ripple_max_width,
R.bool.floating_rotation_button_position_left);
mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
mRotationButtonListener);
if (mContext.isPhoneMode()) {
mTaskbarTransitions.init();
}
applyState();
mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
// Initialize things needed to move nav buttons to separate window.
mSeparateWindowParent = new BaseDragLayer<>(mContext, null, 0) {
@Override
public void recreateControllers() {
mControllers = new TouchController[0];
}
@Override
protected boolean canFindActiveController() {
// We don't have any controllers, but we don't want any floating views such as
// folder to intercept, either. This ensures nav buttons can always be pressed.
return false;
}
};
mSeparateWindowParent.recreateControllers();
if (BubbleBarController.isBubbleBarEnabled()) {
mNavButtonsView.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
onLayoutsUpdated()
);
}
}
private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
TaskbarNavButtonController navButtonController) {
mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
mNavButtonContainer, mControllers.navButtonController, R.id.back);
mBackButtonAlpha = new MultiValueAlpha(mBackButton, NUM_ALPHA_CHANNELS);
mBackButtonAlpha.setUpdateVisibility(true);
mPropertyHolders.add(new StatePropertyHolder(
mBackButtonAlpha.get(ALPHA_INDEX_KEYGUARD_OR_DISABLE),
flags -> {
// Show only if not disabled, and if not on the keyguard or otherwise only when
// the bouncer or a lockscreen app is showing above the keyguard
boolean showingOnKeyguard = (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||
(flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 ||
(flags & FLAG_KEYGUARD_OCCLUDED) != 0;
return (flags & FLAG_DISABLE_BACK) == 0
&& (!mContext.isGestureNav() || !mContext.isUserSetupComplete())
&& ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard);
}));
// Hide back button in SUW if keyboard is showing (IME draws its own back).
if (mIsImeRenderingNavButtons) {
mPropertyHolders.add(new StatePropertyHolder(
mBackButtonAlpha.get(ALPHA_INDEX_SUW),
flags -> (flags & FLAG_IME_VISIBLE) == 0));
}
mPropertyHolders.add(new StatePropertyHolder(mBackButton,
flags -> (flags & FLAG_IME_VISIBLE) != 0,
ROTATION_DRAWABLE_PERCENT, 1f, 0f));
// Translate back button to be at end/start of other buttons for keyguard (only after SUW
// since it is laid to align with SUW actions while in that state)
int navButtonSize = mContext.getResources().getDimensionPixelSize(
R.dimen.taskbar_nav_buttons_size);
boolean isRtl = Utilities.isRtl(mContext.getResources());
if (!mContext.isPhoneMode()) {
mPropertyHolders.add(new StatePropertyHolder(
mBackButton, flags -> mContext.isUserSetupComplete()
&& ((flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
|| (flags & FLAG_KEYGUARD_VISIBLE) != 0),
VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
}
// home button
mHomeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
navButtonController, R.id.home);
mHomeButtonAlpha = new MultiValueAlpha(mHomeButton, NUM_ALPHA_CHANNELS);
mHomeButtonAlpha.setUpdateVisibility(true);
mPropertyHolders.add(
new StatePropertyHolder(mHomeButtonAlpha.get(
ALPHA_INDEX_KEYGUARD_OR_DISABLE),
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0
&& (flags & FLAG_DISABLE_HOME) == 0 && !mContext.isGestureNav()));
// Recents button
mRecentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
navContainer, navButtonController, R.id.recent_apps);
mHitboxExtender.init(mRecentsButton, mNavButtonsView, mContext.getDeviceProfile(),
() -> {
float[] recentsCoords = new float[2];
getDescendantCoordRelativeToAncestor(mRecentsButton, mNavButtonsView,
recentsCoords, false);
return recentsCoords;
}, new Handler());
mRecentsButton.setOnClickListener(v -> {
navButtonController.onButtonClick(BUTTON_RECENTS, v);
mHitboxExtender.onRecentsButtonClicked();
});
mPropertyHolders.add(new StatePropertyHolder(mRecentsButton,
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && (flags & FLAG_DISABLE_RECENTS) == 0
&& !mContext.isNavBarKidsModeActive() && !mContext.isGestureNav()));
// A11y button
mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
endContainer, navButtonController, R.id.accessibility_button,
R.layout.taskbar_contextual_button);
mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
flags -> (flags & FLAG_A11Y_VISIBLE) != 0
&& (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
mSpace = new Space(mNavButtonsView.getContext());
mSpace.setOnClickListener(view -> navButtonController.onButtonClick(BUTTON_SPACE, view));
mSpace.setOnLongClickListener(view ->
navButtonController.onButtonLongClick(BUTTON_SPACE, view));
}
private void parseSystemUiFlags(@SystemUiStateFlags long sysUiStateFlags) {
mSysuiStateFlags = sysUiStateFlags;
boolean isImeSwitcherButtonVisible =
(sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING) != 0;
boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
boolean a11yVisible = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
boolean isBackDisabled = (sysUiStateFlags & SYSUI_STATE_BACK_DISABLED) != 0;
long shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0;
boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
boolean isVoiceInteractionWindowShowing =
(sysUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0;
boolean isKeyboardShortcutHelperShowing =
(sysUiStateFlags & SYSUI_STATE_SHORTCUT_HELPER_SHOWING) != 0;
updateStateForFlag(FLAG_IME_SWITCHER_BUTTON_VISIBLE, isImeSwitcherButtonVisible);
updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled);
updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded);
updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive);
updateStateForFlag(FLAG_VOICE_INTERACTION_WINDOW_SHOWING, isVoiceInteractionWindowShowing);
updateStateForFlag(FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING, isKeyboardShortcutHelperShowing);
if (mA11yButton != null) {
// Only used in 3 button
boolean a11yLongClickable =
(sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
mA11yButton.setLongClickable(a11yLongClickable);
updateButtonLayoutSpacing();
}
}
public void updateStateForSysuiFlags(@SystemUiStateFlags long systemUiStateFlags,
boolean skipAnim) {
if (systemUiStateFlags == mSysuiStateFlags) {
return;
}
parseSystemUiFlags(systemUiStateFlags);
applyState();
if (skipAnim) {
mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
}
}
/**
* @return {@code true} if A11y is showing in 3 button nav taskbar
*/
private boolean isA11yButtonPersistent() {
return mContext.isThreeButtonNav() && (mState & FLAG_A11Y_VISIBLE) != 0;
}
/**
* Should be called when we need to show back button for bouncer
*/
public void setBackForBouncer(boolean isBouncerVisible) {
updateStateForFlag(FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE, isBouncerVisible);
applyState();
}
/**
* Slightly misnamed, but should be called when keyguard OR AOD is showing.
* We consider keyguardVisible when it's showing bouncer OR is occlucded by another app
*/
public void setKeyguardVisible(boolean isKeyguardVisible, boolean isKeyguardOccluded) {
updateStateForFlag(FLAG_KEYGUARD_VISIBLE, isKeyguardVisible || isKeyguardOccluded);
updateStateForFlag(FLAG_KEYGUARD_OCCLUDED, isKeyguardOccluded);
applyState();
}
/** {@code true} if a slide in view is currently visible over taskbar. */
public void setSlideInViewVisible(boolean isSlideInViewVisible) {
updateStateForFlag(FLAG_SLIDE_IN_VIEW_VISIBLE, isSlideInViewVisible);
applyState();
}
/**
* Returns true if IME bar is visible
*/
public boolean isImeVisible() {
return (mState & FLAG_IME_VISIBLE) != 0;
}
public boolean isImeRenderingNavButtons() {
return mIsImeRenderingNavButtons;
}
/**
* Returns true if the home button is disabled
*/
public boolean isHomeDisabled() {
return (mState & FLAG_DISABLE_HOME) != 0;
}
/**
* Returns true if the recents (overview) button is disabled
*/
public boolean isRecentsDisabled() {
return (mState & FLAG_DISABLE_RECENTS) != 0;
}
/**
* Adds the bounds corresponding to all visible buttons to provided region
*/
public void addVisibleButtonsRegion(BaseDragLayer<?> parent, Region outRegion) {
int count = mAllButtons.size();
for (int i = 0; i < count; i++) {
View button = mAllButtons.get(i);
if (button.getVisibility() == View.VISIBLE) {
parent.getDescendantRectRelativeToSelf(button, mTempRect);
if (mHitboxExtender.extendedHitboxEnabled()) {
mTempRect.bottom += mContext.getDeviceProfile().getTaskbarOffsetY();
}
outRegion.op(mTempRect, Op.UNION);
}
}
}
/**
* Returns multi-value alpha controller for back button.
*/
public MultiValueAlpha getBackButtonAlpha() {
return mBackButtonAlpha;
}
/**
* Returns multi-value alpha controller for home button.
*/
public MultiValueAlpha getHomeButtonAlpha() {
return mHomeButtonAlpha;
}
/**
* Sets the AccessibilityDelegate for the home button.
*/
public void setHomeButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate) {
if (mHomeButton == null) {
return;
}
mHomeButton.setAccessibilityDelegate(accessibilityDelegate);
}
/**
* Sets the AccessibilityDelegate for the back button.
*/
public void setBackButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate) {
if (mBackButton == null) {
return;
}
mBackButton.setAccessibilityDelegate(accessibilityDelegate);
}
public void setWallpaperVisible(boolean isVisible) {
if (mContext.isPhoneMode()) {
mTaskbarTransitions.setWallpaperVisibility(isVisible);
}
}
public void onTransitionModeUpdated(int barMode, boolean checkBarModes) {
mTransitionMode = barMode;
if (checkBarModes) {
checkNavBarModes();
}
}
public void checkNavBarModes() {
if (mContext.isPhoneMode()) {
boolean isBarHidden = (mSysuiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
mTaskbarTransitions.transitionTo(mTransitionMode, !isBarHidden);
}
}
public void finishBarAnimations() {
if (mContext.isPhoneMode()) {
mTaskbarTransitions.finishAnimations();
}
}
public void touchAutoDim(boolean reset) {
if (mContext.isPhoneMode()) {
mTaskbarTransitions.setAutoDim(false);
mHandler.removeCallbacks(mAutoDim);
if (reset) {
mHandler.postDelayed(mAutoDim, AUTODIM_TIMEOUT_MS);
}
}
}
public void transitionTo(@BarTransitions.TransitionMode int barMode, boolean animate) {
if (mContext.isPhoneMode()) {
mTaskbarTransitions.transitionTo(barMode, animate);
}
}
/** Use to set the translationY for the all nav+contextual buttons */
public AnimatedFloat getTaskbarNavButtonTranslationY() {
return mTaskbarNavButtonTranslationY;
}
/** Use to set the translationY for the all nav+contextual buttons when in Launcher */
public AnimatedFloat getTaskbarNavButtonTranslationYForInAppDisplay() {
return mTaskbarNavButtonTranslationYForInAppDisplay;
}
/** Use to set the dark intensity for the all nav+contextual buttons */
public AnimatedFloat getTaskbarNavButtonDarkIntensity() {
return mTaskbarNavButtonDarkIntensity;
}
/** Use to override the nav button color with {@link #mOnBackgroundIconColor}. */
public AnimatedFloat getOnTaskbarBackgroundNavButtonColorOverride() {
return mOnTaskbarBackgroundNavButtonColorOverride;
}
/**
* Does not call {@link #applyState()}. Don't forget to!
*/
private void updateStateForFlag(int flag, boolean enabled) {
if (enabled) {
mState |= flag;
} else {
mState &= ~flag;
}
}
private void applyState() {
int count = mPropertyHolders.size();
for (int i = 0; i < count; i++) {
mPropertyHolders.get(i).setState(mState, mContext.isGestureNav());
}
}
private void updateNavButtonInAppDisplayProgressForSysui() {
TaskbarUIController uiController = mControllers.uiController;
if (uiController instanceof LauncherTaskbarUIController) {
((LauncherTaskbarUIController) uiController).onTaskbarInAppDisplayProgressUpdate(
mNavButtonInAppDisplayProgressForSysui.value, SYSUI_SURFACE_PROGRESS_INDEX);
}
}
/**
* Sets the translationY of the nav buttons based on the current device state.
*/
public void updateNavButtonTranslationY() {
if (mContext.isPhoneButtonNavMode()) {
return;
}
final float normalTranslationY = mTaskbarNavButtonTranslationY.value;
final float imeAdjustmentTranslationY = mTaskbarNavButtonTranslationYForIme.value;
TaskbarUIController uiController = mControllers.uiController;
final float inAppDisplayAdjustmentTranslationY =
(uiController instanceof LauncherTaskbarUIController
&& ((LauncherTaskbarUIController) uiController).shouldUseInAppLayout())
? mTaskbarNavButtonTranslationYForInAppDisplay.value : 0;
mLastSetNavButtonTranslationY = normalTranslationY
+ imeAdjustmentTranslationY
+ inAppDisplayAdjustmentTranslationY;
mNavButtonsView.setTranslationY(mLastSetNavButtonTranslationY);
}
/**
* Sets Taskbar 3-button mode icon colors based on the
* {@link #mTaskbarNavButtonDarkIntensity} value piped in from Framework. For certain cases
* in large screen taskbar where there may be opaque surfaces, the selected SystemUI button
* colors are intentionally overridden.
* <p>
* This method is also called when any of the AnimatedFloat instances change.
*/
private void updateNavButtonColor() {
final ArgbEvaluator argbEvaluator = ArgbEvaluator.getInstance();
int taskbarNavButtonColor = getSysUiIconColorOnHome(argbEvaluator);
// Only phone mode foldable button colors should be identical to SysUI navbar colors.
if (!(ENABLE_TASKBAR_NAVBAR_UNIFICATION && mContext.isPhoneMode())) {
taskbarNavButtonColor = getTaskbarButtonColor(argbEvaluator, taskbarNavButtonColor);
}
applyButtonColors(taskbarNavButtonColor);
}
/**
* Taskbar 3-button mode icon colors based on the
* {@link #mTaskbarNavButtonDarkIntensity} value piped in from Framework.
*/
private int getSysUiIconColorOnHome(ArgbEvaluator argbEvaluator) {
return (int) argbEvaluator.evaluate(getTaskbarNavButtonDarkIntensity().value,
mLightIconColorOnWorkspace, mDarkIconColorOnWorkspace);
}
/**
* If Taskbar background is opaque or slide in overlay is visible, the selected SystemUI button
* colors are intentionally overridden. The override can be disabled when
* {@link #mOnBackgroundNavButtonColorOverrideMultiplier} is {@code 0}.
*/
private int getTaskbarButtonColor(ArgbEvaluator argbEvaluator, int sysUiIconColorOnHome) {
final float sysUIColorOverride =
mOnBackgroundNavButtonColorOverrideMultiplier.value * Math.max(
mOnTaskbarBackgroundNavButtonColorOverride.value,
mSlideInViewVisibleNavButtonColorOverride.value);
return (int) argbEvaluator.evaluate(sysUIColorOverride, sysUiIconColorOnHome,
mOnBackgroundIconColor);
}
/**
* Iteratively sets button colors for each button in {@link #mAllButtons}.
*/
private void applyButtonColors(int iconColor) {
for (ImageView button : mAllButtons) {
button.setImageTintList(ColorStateList.valueOf(iconColor));
Drawable background = button.getBackground();
if (background instanceof KeyButtonRipple) {
((KeyButtonRipple) background).setDarkIntensity(
getTaskbarNavButtonDarkIntensity().value);
}
}
}
/**
* Updates Taskbar 3-Button icon colors as {@link #mTaskbarNavButtonDarkIntensity} changes.
*/
private void onDarkIntensityChanged() {
updateNavButtonColor();
if (mContext.isPhoneMode()) {
mTaskbarTransitions.onDarkIntensityChanged(getTaskbarNavButtonDarkIntensity().value);
}
}
protected ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) {
return addButton(drawableId, buttonType, parent, navButtonController, id,
R.layout.taskbar_nav_button);
}
private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id,
@LayoutRes int layoutId) {
ImageView buttonView = addButton(parent, id, layoutId);
buttonView.setImageResource(drawableId);
buttonView.setContentDescription(parent.getContext().getString(
navButtonController.getButtonContentDescription(buttonType)));
if (predictiveBackThreeButtonNav() && buttonType == BUTTON_BACK) {
// set up special touch listener for back button to support predictive back
setBackButtonTouchListener(buttonView, navButtonController);
} else {
buttonView.setOnClickListener(view ->
navButtonController.onButtonClick(buttonType, view));
buttonView.setOnLongClickListener(view ->
navButtonController.onButtonLongClick(buttonType, view));
}
return buttonView;
}
private void setBackButtonTouchListener(View buttonView,
TaskbarNavButtonController navButtonController) {
final RectF rect = new RectF();
buttonView.setOnTouchListener((v, event) -> {
int motionEventAction = event.getAction();
if (motionEventAction == MotionEvent.ACTION_DOWN) {
rect.set(0, 0, v.getWidth(), v.getHeight());
}
boolean isCancelled = motionEventAction == MotionEvent.ACTION_CANCEL
|| (!rect.contains(event.getX(), event.getY())
&& (motionEventAction == MotionEvent.ACTION_MOVE
|| motionEventAction == MotionEvent.ACTION_UP));
if (motionEventAction != MotionEvent.ACTION_DOWN
&& motionEventAction != MotionEvent.ACTION_UP && !isCancelled) {
// return early. we don't care about any other cases than DOWN, UP and CANCEL
return false;
}
int keyEventAction = motionEventAction == MotionEvent.ACTION_DOWN
? KeyEvent.ACTION_DOWN : ACTION_UP;
navButtonController.sendBackKeyEvent(keyEventAction, isCancelled);
if (motionEventAction == MotionEvent.ACTION_UP && !isCancelled) {
buttonView.performClick();
buttonView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
return false;
});
buttonView.setOnLongClickListener((view) -> {
navButtonController.onButtonLongClick(BUTTON_BACK, view);
return false;
});
}
private ImageView addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId) {
ImageView buttonView = (ImageView) mContext.getLayoutInflater()
.inflate(layoutId, parent, false);
buttonView.setId(id);
parent.addView(buttonView);
mAllButtons.add(buttonView);
return buttonView;
}
public boolean isEventOverAnyItem(MotionEvent ev) {
return mFloatingRotationButtonBounds.contains((int) ev.getX(), (int) ev.getY());
}
public void onConfigurationChanged(@Config int configChanges) {
if (mFloatingRotationButton != null) {
mFloatingRotationButton.onConfigurationChanged(configChanges);
}
if (!mContext.isUserSetupComplete()) {
handleSetupUi();
}
updateButtonLayoutSpacing();
}
private void handleSetupUi() {
// Since setup wizard only has back button enabled, it looks strange to be
// end-aligned, so start-align instead.
FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
mNavButtonContainer.getLayoutParams();
FrameLayout.LayoutParams navButtonsViewLayoutParams = (FrameLayout.LayoutParams)
mNavButtonsView.getLayoutParams();
Resources resources = mContext.getResources();
DeviceProfile deviceProfile = mContext.getDeviceProfile();
navButtonsLayoutParams.setMarginEnd(0);
navButtonsLayoutParams.gravity = Gravity.START;
mControllers.taskbarActivityContext.setTaskbarWindowSize(
mControllers.taskbarActivityContext.getSetupWindowSize());
// If SUW is on a large screen device that is landscape (or has a square aspect
// ratio) the back button has to be placed accordingly
if ((deviceProfile.isTablet && deviceProfile.isLandscape)
|| (deviceProfile.aspectRatio > SQUARE_ASPECT_RATIO_BOTTOM_BOUND
&& deviceProfile.aspectRatio < SQUARE_ASPECT_RATIO_UPPER_BOUND)) {
navButtonsLayoutParams.setMarginStart(
resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_start_margin));
navButtonsViewLayoutParams.bottomMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_back_button_suw_bottom_margin);
navButtonsLayoutParams.height = resources.getDimensionPixelSize(
R.dimen.taskbar_back_button_suw_height);
} else {
int phoneOrPortraitSetupMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_contextual_button_suw_margin);
navButtonsLayoutParams.setMarginStart(phoneOrPortraitSetupMargin);
navButtonsLayoutParams.bottomMargin = !deviceProfile.isLandscape
? 0
: phoneOrPortraitSetupMargin - (resources.getDimensionPixelSize(
R.dimen.taskbar_nav_buttons_size) / 2);
navButtonsViewLayoutParams.height = resources.getDimensionPixelSize(
R.dimen.taskbar_contextual_button_suw_height);
}
mNavButtonsView.setLayoutParams(navButtonsViewLayoutParams);
mNavButtonContainer.setLayoutParams(navButtonsLayoutParams);
}
/**
* Adds the correct spacing to 3 button nav container depending on if device is in kids mode,
* setup wizard, or normal 3 button nav.
*/
private void updateButtonLayoutSpacing() {
boolean isThreeButtonNav = mContext.isThreeButtonNav();
DeviceProfile dp = mContext.getDeviceProfile();
Resources res = mContext.getResources();
boolean isInSetup = !mContext.isUserSetupComplete();
// TODO(b/244231596) we're getting the incorrect kidsMode value in small-screen
boolean isInKidsMode = mContext.isNavBarKidsModeActive();
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
NavButtonLayoutter navButtonLayoutter =
NavButtonLayoutFactory.Companion.getUiLayoutter(
dp, mNavButtonsView, mImeSwitcherButton,
mA11yButton, mSpace, res, isInKidsMode, isInSetup, isThreeButtonNav,
mContext.isPhoneMode(), mWindowManagerProxy.getRotation(mContext));
navButtonLayoutter.layoutButtons(mContext, isA11yButtonPersistent());
updateButtonsBackground();
updateNavButtonColor();
return;
}
if (isInSetup) {
handleSetupUi();
} else if (isInKidsMode) {
int iconSize = res.getDimensionPixelSize(
R.dimen.taskbar_icon_size_kids);
int buttonWidth = res.getDimensionPixelSize(
R.dimen.taskbar_nav_buttons_width_kids);
int buttonHeight = res.getDimensionPixelSize(
R.dimen.taskbar_nav_buttons_height_kids);
int buttonRadius = res.getDimensionPixelSize(
R.dimen.taskbar_nav_buttons_corner_radius_kids);
int paddingleft = (buttonWidth - iconSize) / 2;
int paddingRight = paddingleft;
int paddingTop = (buttonHeight - iconSize) / 2;
int paddingBottom = paddingTop;
// Update icons
final RotateDrawable rotateDrawable = new RotateDrawable();
rotateDrawable.setDrawable(mContext.getDrawable(R.drawable.ic_sysbar_back_kids));
rotateDrawable.setFromDegrees(0f);
rotateDrawable.setToDegrees(-90f);
mBackButton.setImageDrawable(rotateDrawable);
mBackButton.setScaleType(ImageView.ScaleType.FIT_CENTER);
mBackButton.setPadding(paddingleft, paddingTop, paddingRight, paddingBottom);
mHomeButton.setImageDrawable(
mHomeButton.getContext().getDrawable(R.drawable.ic_sysbar_home_kids));
mHomeButton.setScaleType(ImageView.ScaleType.FIT_CENTER);
mHomeButton.setPadding(paddingleft, paddingTop, paddingRight, paddingBottom);
// Home button layout
LinearLayout.LayoutParams homeLayoutparams = new LinearLayout.LayoutParams(
buttonWidth,
buttonHeight
);
int homeButtonLeftMargin = res.getDimensionPixelSize(
R.dimen.taskbar_home_button_left_margin_kids);
homeLayoutparams.setMargins(homeButtonLeftMargin, 0, 0, 0);
mHomeButton.setLayoutParams(homeLayoutparams);
// Back button layout
LinearLayout.LayoutParams backLayoutParams = new LinearLayout.LayoutParams(
buttonWidth,
buttonHeight
);
int backButtonLeftMargin = res.getDimensionPixelSize(
R.dimen.taskbar_back_button_left_margin_kids);
backLayoutParams.setMargins(backButtonLeftMargin, 0, 0, 0);
mBackButton.setLayoutParams(backLayoutParams);
// Button backgrounds
int whiteWith10PctAlpha = Color.argb(0.1f, 1, 1, 1);
PaintDrawable buttonBackground = new PaintDrawable(whiteWith10PctAlpha);
buttonBackground.setCornerRadius(buttonRadius);
mHomeButton.setBackground(buttonBackground);
mBackButton.setBackground(buttonBackground);
// Update alignment within taskbar
FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
mNavButtonContainer.getLayoutParams();
navButtonsLayoutParams.setMarginStart(
navButtonsLayoutParams.getMarginEnd() / 2);
navButtonsLayoutParams.setMarginEnd(navButtonsLayoutParams.getMarginStart());
navButtonsLayoutParams.gravity = Gravity.CENTER;
mNavButtonContainer.requestLayout();
mHomeButton.setOnLongClickListener(null);
} else if (isThreeButtonNav) {
final RotateDrawable rotateDrawable = new RotateDrawable();
rotateDrawable.setDrawable(mContext.getDrawable(R.drawable.ic_sysbar_back));
rotateDrawable.setFromDegrees(0f);
rotateDrawable.setToDegrees(Utilities.isRtl(mContext.getResources()) ? 90f : -90f);
mBackButton.setImageDrawable(rotateDrawable);
// Setup normal 3 button
// Add spacing after the end of the last nav button
FrameLayout.LayoutParams navButtonParams =
(FrameLayout.LayoutParams) mNavButtonContainer.getLayoutParams();
navButtonParams.gravity = Gravity.END;
navButtonParams.width = FrameLayout.LayoutParams.WRAP_CONTENT;
navButtonParams.height = MATCH_PARENT;
int navMarginEnd = (int) res.getDimension(dp.inv.inlineNavButtonsEndSpacing);
int contextualWidth = mEndContextualContainer.getWidth();
// If contextual buttons are showing, we check if the end margin is enough for the
// contextual button to be showing - if not, move the nav buttons over a smidge
if (isA11yButtonPersistent() && navMarginEnd < contextualWidth) {
// Additional spacing, eat up half of space between last icon and nav button
navMarginEnd += res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2;
}
navButtonParams.setMarginEnd(navMarginEnd);
mNavButtonContainer.setLayoutParams(navButtonParams);
// Add the spaces in between the nav buttons
int spaceInBetween = res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween);
for (int i = 0; i < mNavButtonContainer.getChildCount(); i++) {
View navButton = mNavButtonContainer.getChildAt(i);
LinearLayout.LayoutParams buttonLayoutParams =
(LinearLayout.LayoutParams) navButton.getLayoutParams();
buttonLayoutParams.weight = 0;
if (i == 0) {
buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
} else if (i == mNavButtonContainer.getChildCount() - 1) {
buttonLayoutParams.setMarginStart(spaceInBetween / 2);
} else {
buttonLayoutParams.setMarginStart(spaceInBetween / 2);
buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
}
}
}
}
private void updateButtonsBackground() {
boolean clipped = !mContext.isPhoneButtonNavMode();
mNavButtonContainer.setClipToPadding(clipped);
mNavButtonContainer.setClipChildren(clipped);
mNavButtonsView.setClipToPadding(clipped);
mNavButtonsView.setClipChildren(clipped);
for (ImageView button : mAllButtons) {
updateButtonBackground(button, mContext.isPhoneButtonNavMode());
}
}
private static void updateButtonBackground(View view, boolean isPhoneButtonNavMode) {
if (isPhoneButtonNavMode) {
view.setBackground(new KeyButtonRipple(view.getContext(), view,
R.dimen.key_button_ripple_max_width));
} else {
view.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect);
}
}
public void onDestroy() {
mPropertyHolders.clear();
mControllers.rotationButtonController.unregisterListeners();
if (mFloatingRotationButton != null) {
mFloatingRotationButton.hide();
}
moveNavButtonsBackToTaskbarWindow();
mNavButtonContainer.removeAllViews();
mEndContextualContainer.removeAllViews();
mStartContextualContainer.removeAllViews();
mAllButtons.clear();
}
/**
* Moves mNavButtonsView from TaskbarDragLayer to a placeholder BaseDragLayer on a new window.
*/
public void moveNavButtonsToNewWindow() {
if (mAreNavButtonsInSeparateWindow) {
return;
}
if (mIsImeRenderingNavButtons) {
// IME is rendering the nav buttons, so we don't need to create a new layer for them.
return;
}
mSeparateWindowParent.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {
mSeparateWindowParent.getViewTreeObserver().addOnComputeInternalInsetsListener(
mSeparateWindowInsetsComputer);
}
@Override
public void onViewDetachedFromWindow(View view) {
mSeparateWindowParent.removeOnAttachStateChangeListener(this);
mSeparateWindowParent.getViewTreeObserver().removeOnComputeInternalInsetsListener(
mSeparateWindowInsetsComputer);
}
});
mAreNavButtonsInSeparateWindow = true;
mContext.getDragLayer().removeView(mNavButtonsView);
mSeparateWindowParent.addView(mNavButtonsView);
WindowManager.LayoutParams windowLayoutParams = mContext.createDefaultWindowLayoutParams(
TYPE_NAVIGATION_BAR_PANEL, NAV_BUTTONS_SEPARATE_WINDOW_TITLE);
mContext.addWindowView(mSeparateWindowParent, windowLayoutParams);
}
/**
* Moves mNavButtonsView from its temporary window and reattaches it to TaskbarDragLayer.
*/
public void moveNavButtonsBackToTaskbarWindow() {
if (!mAreNavButtonsInSeparateWindow) {
return;
}
mAreNavButtonsInSeparateWindow = false;
mContext.removeWindowView(mSeparateWindowParent);
mSeparateWindowParent.removeView(mNavButtonsView);
mContext.getDragLayer().addView(mNavButtonsView);
}
private void onComputeInsetsForSeparateWindow(ViewTreeObserver.InternalInsetsInfo insetsInfo) {
addVisibleButtonsRegion(mSeparateWindowParent, insetsInfo.touchableRegion);
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
}
/**
* Called whenever a new ui controller is set, and should update anything that depends on the
* ui controller.
*/
public void onUiControllerChanged() {
updateNavButtonInAppDisplayProgressForSysui();
updateNavButtonTranslationY();
}
@Override
public void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "NavbarButtonsViewController:");
pw.println(prefix + "\tmState=" + getStateString(mState));
pw.println(prefix + "\tmFloatingRotationButtonBounds=" + mFloatingRotationButtonBounds);
pw.println(prefix + "\tmSysuiStateFlags=" + QuickStepContract.getSystemUiStateString(
mSysuiStateFlags));
pw.println(prefix + "\tLast set nav button translationY=" + mLastSetNavButtonTranslationY);
pw.println(prefix + "\t\tmTaskbarNavButtonTranslationY="
+ mTaskbarNavButtonTranslationY.value);
pw.println(prefix + "\t\tmTaskbarNavButtonTranslationYForInAppDisplay="
+ mTaskbarNavButtonTranslationYForInAppDisplay.value);
pw.println(prefix + "\t\tmTaskbarNavButtonTranslationYForIme="
+ mTaskbarNavButtonTranslationYForIme.value);
pw.println(prefix + "\t\tmTaskbarNavButtonDarkIntensity="
+ mTaskbarNavButtonDarkIntensity.value);
pw.println(prefix + "\t\tmSlideInViewVisibleNavButtonColorOverride="
+ mSlideInViewVisibleNavButtonColorOverride.value);
pw.println(prefix + "\t\tmOnTaskbarBackgroundNavButtonColorOverride="
+ mOnTaskbarBackgroundNavButtonColorOverride.value);
pw.println(prefix + "\t\tmOnBackgroundNavButtonColorOverrideMultiplier="
+ mOnBackgroundNavButtonColorOverrideMultiplier.value);
mNavButtonsView.dumpLogs(prefix + "\t", pw);
if (mContext.isPhoneMode()) {
mTaskbarTransitions.dumpLogs(prefix + "\t", pw);
}
}
private static String getStateString(int flags) {
StringJoiner str = new StringJoiner("|");
appendFlag(str, flags, FLAG_IME_SWITCHER_BUTTON_VISIBLE,
"FLAG_IME_SWITCHER_BUTTON_VISIBLE");
appendFlag(str, flags, FLAG_IME_VISIBLE, "FLAG_IME_VISIBLE");
appendFlag(str, flags, FLAG_ROTATION_BUTTON_VISIBLE, "FLAG_ROTATION_BUTTON_VISIBLE");
appendFlag(str, flags, FLAG_A11Y_VISIBLE, "FLAG_A11Y_VISIBLE");
appendFlag(str, flags, FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE,
"FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE");
appendFlag(str, flags, FLAG_KEYGUARD_VISIBLE, "FLAG_KEYGUARD_VISIBLE");
appendFlag(str, flags, FLAG_KEYGUARD_OCCLUDED, "FLAG_KEYGUARD_OCCLUDED");
appendFlag(str, flags, FLAG_DISABLE_HOME, "FLAG_DISABLE_HOME");
appendFlag(str, flags, FLAG_DISABLE_RECENTS, "FLAG_DISABLE_RECENTS");
appendFlag(str, flags, FLAG_DISABLE_BACK, "FLAG_DISABLE_BACK");
appendFlag(str, flags, FLAG_NOTIFICATION_SHADE_EXPANDED,
"FLAG_NOTIFICATION_SHADE_EXPANDED");
appendFlag(str, flags, FLAG_SCREEN_PINNING_ACTIVE, "FLAG_SCREEN_PINNING_ACTIVE");
appendFlag(str, flags, FLAG_VOICE_INTERACTION_WINDOW_SHOWING,
"FLAG_VOICE_INTERACTION_WINDOW_SHOWING");
appendFlag(str, flags, FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING,
"FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING");
return str.toString();
}
public TouchController getTouchController() {
return mHitboxExtender;
}
/**
* @param alignment 0 -> Taskbar, 1 -> Workspace
*/
public void updateTaskbarAlignment(float alignment) {
mHitboxExtender.onAnimationProgressToOverview(alignment);
}
/** Adjusts navigation buttons layout accordingly to the bubble bar position. */
@Override
public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
boolean locationUpdated = location != mBubbleBarTargetLocation;
if (locationUpdated) {
cancelExistingNavBarAnimation();
} else {
endExistingAnimation();
}
mNavButtonContainer.setTranslationX(getNavBarTranslationX(location));
mBubbleBarTargetLocation = location;
}
/** Animates navigation buttons accordingly to the bubble bar position. */
@Override
public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
if (location == mBubbleBarTargetLocation) return;
cancelExistingNavBarAnimation();
mBubbleBarTargetLocation = location;
int finalX = getNavBarTranslationX(location);
Animator teleportAnimator = BarsLocationAnimatorHelper
.getTeleportAnimatorForNavButtons(location, mNavButtonContainer, finalX);
teleportAnimator.addListener(forEndCallback(() -> mNavBarLocationAnimator = null));
mNavBarLocationAnimator = teleportAnimator;
mNavBarLocationAnimator.start();
}
private void endExistingAnimation() {
if (mNavBarLocationAnimator != null) {
mNavBarLocationAnimator.end();
mNavBarLocationAnimator = null;
}
}
private void cancelExistingNavBarAnimation() {
if (mNavBarLocationAnimator != null) {
mNavBarLocationAnimator.cancel();
mNavBarLocationAnimator = null;
}
}
private int getNavBarTranslationX(BubbleBarLocation location) {
boolean isNavbarOnRight = location.isOnLeft(mNavButtonsView.isLayoutRtl());
DeviceProfile dp = mContext.getDeviceProfile();
float navBarTargetStartX;
if (!mContext.isUserSetupComplete()) {
// Skip additional translations on the nav bar container while in SUW layout
return 0;
} else if (mContext.shouldStartAlignTaskbar()) {
int navBarSpacing = dp.inlineNavButtonsEndSpacingPx;
// If the taskbar is start aligned the navigation bar is aligned to the start or end of
// the container, depending on the bubble bar location
if (isNavbarOnRight) {
navBarTargetStartX = dp.widthPx - navBarSpacing - mNavButtonContainer.getWidth();
} else {
navBarTargetStartX = navBarSpacing;
}
} else {
// If the task bar is not start aligned, the navigation bar is located in the center
// between the taskbar and screen edges, depending on the bubble bar location.
float navbarWidth = mNavButtonContainer.getWidth();
Rect taskbarBounds = mControllers.taskbarViewController
.getTransientTaskbarIconLayoutBoundsInParent();
if (isNavbarOnRight) {
if (mNavButtonsView.isLayoutRtl()) {
float taskBarEnd = taskbarBounds.right;
navBarTargetStartX = (dp.widthPx + taskBarEnd - navbarWidth) / 2;
} else {
navBarTargetStartX = mNavButtonContainer.getLeft();
}
} else {
float taskBarStart = taskbarBounds.left;
navBarTargetStartX = (taskBarStart - navbarWidth) / 2;
}
}
return (int) navBarTargetStartX - mNavButtonContainer.getLeft();
}
/** Adjusts the navigation buttons layout position according to the bubble bar location. */
public void onLayoutsUpdated() {
// no need to do anything if on phone, or if taskbar or navbar views were not placed on
// screen.
Rect transientTaskbarIconLayoutBoundsInParent = mControllers.taskbarViewController
.getTransientTaskbarIconLayoutBoundsInParent();
if (mContext.getDeviceProfile().isPhone
|| transientTaskbarIconLayoutBoundsInParent.isEmpty()
|| mNavButtonsView.getWidth() == 0) {
return;
}
if (mControllers.bubbleControllers.isPresent()) {
if (mBubbleBarTargetLocation == null) {
// only set bubble bar location if it was not set before
mBubbleBarTargetLocation = mControllers.bubbleControllers.get()
.bubbleBarViewController.getBubbleBarLocation();
}
onBubbleBarLocationUpdated(mBubbleBarTargetLocation);
}
}
private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
@Override
public void onVisibilityChanged(boolean isVisible) {
if (isVisible) {
mFloatingRotationButton.getCurrentView()
.getBoundsOnScreen(mFloatingRotationButtonBounds);
} else {
mFloatingRotationButtonBounds.setEmpty();
}
}
}
private static class StatePropertyHolder {
private final float mEnabledValue, mDisabledValue;
private final ObjectAnimator mAnimator;
private final IntPredicate mEnableCondition;
private boolean mIsEnabled = true;
StatePropertyHolder(View view, IntPredicate enableCondition) {
this(view, enableCondition, LauncherAnimUtils.VIEW_ALPHA, 1, 0);
mAnimator.addListener(new AlphaUpdateListener(view));
}
StatePropertyHolder(MultiProperty alphaProperty,
IntPredicate enableCondition) {
this(alphaProperty, enableCondition, MULTI_PROPERTY_VALUE, 1, 0);
}
StatePropertyHolder(AnimatedFloat animatedFloat, IntPredicate enableCondition) {
this(animatedFloat, enableCondition, AnimatedFloat.VALUE, 1, 0);
}
<T> StatePropertyHolder(T target, IntPredicate enabledCondition,
Property<T, Float> property, float enabledValue, float disabledValue) {
mEnableCondition = enabledCondition;
mEnabledValue = enabledValue;
mDisabledValue = disabledValue;
mAnimator = ObjectAnimator.ofFloat(target, property, enabledValue, disabledValue);
}
public void setState(int flags, boolean skipAnimation) {
boolean isEnabled = mEnableCondition.test(flags);
if (mIsEnabled != isEnabled) {
mIsEnabled = isEnabled;
mAnimator.cancel();
mAnimator.setFloatValues(mIsEnabled ? mEnabledValue : mDisabledValue);
mAnimator.start();
if (skipAnimation) {
mAnimator.end();
}
}
}
public void endAnimation() {
if (mAnimator.isRunning()) {
mAnimator.end();
}
}
}
}