/* * 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.os.Trace.TRACE_TAG_APP; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED; import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE; import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY; import static com.android.launcher3.Flags.enableCursorHoverStates; import static com.android.launcher3.Utilities.calculateTextHeight; import static com.android.launcher3.Utilities.isRunningInTestHarness; import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION; import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate; import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN; import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING; import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN; import static com.android.launcher3.testing.shared.ResourceUtils.getBoolByName; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING; import static com.android.wm.shell.Flags.enableTinyTaskbar; import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.app.ActivityOptions; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo.Config; import android.content.pm.LauncherApps; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.IRemoteCallback; import android.os.Process; import android.os.Trace; import android.provider.Settings; import android.util.Log; import android.view.Display; import android.view.Gravity; import android.view.Surface; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.core.graphics.Insets; import androidx.core.view.WindowInsetsCompat; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherPrefs; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.R; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.apppairs.AppPairIcon; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.AutohideSuspendFlag; import com.android.launcher3.taskbar.TaskbarTranslationController.TransitionCallback; import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController; import com.android.launcher3.taskbar.bubbles.BubbleBarController; import com.android.launcher3.taskbar.bubbles.BubbleBarPinController; import com.android.launcher3.taskbar.bubbles.BubbleBarView; import com.android.launcher3.taskbar.bubbles.BubbleBarViewController; import com.android.launcher3.taskbar.bubbles.BubbleControllers; import com.android.launcher3.taskbar.bubbles.BubbleDismissController; import com.android.launcher3.taskbar.bubbles.BubbleDragController; import com.android.launcher3.taskbar.bubbles.BubblePinController; import com.android.launcher3.taskbar.bubbles.BubbleStashController; import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController; import com.android.launcher3.taskbar.navbutton.NearestTouchFrame; import com.android.launcher3.taskbar.overlay.TaskbarOverlayController; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy; import com.android.launcher3.util.ActivityOptionsWrapper; import com.android.launcher3.util.ApiWrapper; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.Executors; import com.android.launcher3.util.NavigationMode; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.SettingsCache; import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource; import com.android.launcher3.util.TraceHelper; import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.util.ViewCache; import com.android.launcher3.views.ActivityContext; import com.android.quickstep.LauncherActivityInterface; import com.android.quickstep.NavHandle; import com.android.quickstep.RecentsModel; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; import com.android.systemui.unfold.updates.RotationChangeProvider; import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider; import java.io.PrintWriter; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Consumer; /** * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements * that are used by both Launcher and Taskbar (such as Folder) to reference a generic * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer. */ public class TaskbarActivityContext extends BaseTaskbarContext { private static final String IME_DRAWS_IME_NAV_BAR_RES_NAME = "config_imeDrawsImeNavBar"; private static final String TAG = "TaskbarActivityContext"; private static final String WINDOW_TITLE = "Taskbar"; private final @Nullable Context mNavigationBarPanelContext; private final TaskbarDragLayer mDragLayer; private final TaskbarControllers mControllers; private final WindowManager mWindowManager; private DeviceProfile mDeviceProfile; private WindowManager.LayoutParams mWindowLayoutParams; private boolean mIsFullscreen; // The size we should return to when we call setTaskbarWindowFullscreen(false) private int mLastRequestedNonFullscreenSize; private NavigationMode mNavMode; private boolean mImeDrawsImeNavBar; private final ViewCache mViewCache = new ViewCache(); private final boolean mIsSafeModeEnabled; private final boolean mIsUserSetupComplete; private final boolean mIsNavBarForceVisible; private final boolean mIsNavBarKidsMode; private boolean mIsDestroyed = false; // The flag to know if the window is excluded from magnification region computation. private boolean mIsExcludeFromMagnificationRegion = false; private boolean mBindingItems = false; private boolean mAddedWindow = false; // The bounds of the taskbar items relative to TaskbarDragLayer private final Rect mTransientTaskbarBounds = new Rect(); private final TaskbarShortcutMenuAccessibilityDelegate mAccessibilityDelegate; private DeviceProfile mTransientTaskbarDeviceProfile; private DeviceProfile mPersistentTaskbarDeviceProfile; private final LauncherPrefs mLauncherPrefs; public TaskbarActivityContext(Context windowContext, @Nullable Context navigationBarPanelContext, DeviceProfile launcherDp, TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider unfoldTransitionProgressProvider) { super(windowContext); mNavigationBarPanelContext = navigationBarPanelContext; applyDeviceProfile(launcherDp); final Resources resources = getResources(); mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false); mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode", () -> getPackageManager().isSafeMode()); // TODO(b/244231596) For shared Taskbar window, update this value in applyDeviceProfile() // instead so to get correct value when recreating the taskbar SettingsCache settingsCache = SettingsCache.INSTANCE.get(this); mIsUserSetupComplete = settingsCache.getValue( Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0); mIsNavBarKidsMode = settingsCache.getValue( Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0); mIsNavBarForceVisible = mIsNavBarKidsMode; // Get display and corners first, as views might use them in constructor. Display display = windowContext.getDisplay(); Context c = getApplicationContext(); mWindowManager = c.getSystemService(WindowManager.class); // Inflate views. int taskbarLayout = DisplayController.isTransientTaskbar(this) && !isPhoneMode() ? R.layout.transient_taskbar : R.layout.taskbar; mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(taskbarLayout, null, false); TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view); TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim); NearestTouchFrame navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view); StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle); BubbleBarView bubbleBarView = mDragLayer.findViewById(R.id.taskbar_bubbles); StashedHandleView bubbleHandleView = mDragLayer.findViewById(R.id.stashed_bubble_handle); mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this); // If Bubble bar is present, TaskbarControllers depends on it so build it first. Optional bubbleControllersOptional = Optional.empty(); BubbleBarController.onTaskbarRecreated(); if (BubbleBarController.isBubbleBarEnabled() && bubbleBarView != null) { bubbleControllersOptional = Optional.of(new BubbleControllers( new BubbleBarController(this, bubbleBarView), new BubbleBarViewController(this, bubbleBarView), new BubbleStashController(this), new BubbleStashedHandleViewController(this, bubbleHandleView), new BubbleDragController(this), new BubbleDismissController(this, mDragLayer), new BubbleBarPinController(this, mDragLayer, () -> DisplayController.INSTANCE.get(this).getInfo().currentSize), new BubblePinController(this, mDragLayer, () -> DisplayController.INSTANCE.get(this).getInfo().currentSize) )); } // Construct controllers. RotationButtonController rotationButtonController = new RotationButtonController(this, c.getColor(R.color.floating_rotation_button_light_color), c.getColor(R.color.floating_rotation_button_dark_color), R.drawable.ic_sysbar_rotate_button_ccw_start_0, R.drawable.ic_sysbar_rotate_button_ccw_start_90, R.drawable.ic_sysbar_rotate_button_cw_start_0, R.drawable.ic_sysbar_rotate_button_cw_start_90, () -> getDisplay().getRotation()); rotationButtonController.setBgExecutor(Executors.UI_HELPER_EXECUTOR); mControllers = new TaskbarControllers(this, new TaskbarDragController(this), buttonController, new NavbarButtonsViewController(this, mNavigationBarPanelContext, navButtonsView), rotationButtonController, new TaskbarDragLayerController(this, mDragLayer), new TaskbarViewController(this, taskbarView), new TaskbarScrimViewController(this, taskbarScrimView), new TaskbarUnfoldAnimationController(this, unfoldTransitionProgressProvider, mWindowManager, new RotationChangeProvider(c.getSystemService(DisplayManager.class), this, UI_HELPER_EXECUTOR.getHandler(), getMainThreadHandler())), new TaskbarKeyguardController(this), new StashedHandleViewController(this, stashedHandleView), new TaskbarStashController(this), new TaskbarAutohideSuspendController(this), new TaskbarPopupController(this), new TaskbarForceVisibleImmersiveController(this), new TaskbarOverlayController(this, launcherDp), new TaskbarAllAppsController(), new TaskbarInsetsController(this), new VoiceInteractionWindowController(this), new TaskbarTranslationController(this), new TaskbarSpringOnStashController(this), new TaskbarRecentAppsController( RecentsModel.INSTANCE.get(this), LauncherActivityInterface.INSTANCE::getDesktopVisibilityController), TaskbarEduTooltipController.newInstance(this), new KeyboardQuickSwitchController(), new TaskbarPinningController(this, () -> DisplayController.INSTANCE.get(this).getInfo().isInDesktopMode()), bubbleControllersOptional); mLauncherPrefs = LauncherPrefs.get(this); } /** Updates {@link DeviceProfile} instances for any Taskbar windows. */ public void updateDeviceProfile(DeviceProfile launcherDp) { applyDeviceProfile(launcherDp); mControllers.taskbarOverlayController.updateLauncherDeviceProfile(launcherDp); AbstractFloatingView.closeAllOpenViewsExcept(this, false, TYPE_REBIND_SAFE); // Reapply fullscreen to take potential new screen size into account. setTaskbarWindowFullscreen(mIsFullscreen); dispatchDeviceProfileChanged(); } /** * Copy the original DeviceProfile, match the number of hotseat icons and qsb width and update * the icon size */ private void applyDeviceProfile(DeviceProfile originDeviceProfile) { Consumer overrideProvider = deviceProfile -> { // Taskbar should match the number of icons of hotseat deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons; // Same QSB width to have a smooth animation deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth; // Update icon size deviceProfile.iconSizePx = deviceProfile.taskbarIconSize; deviceProfile.updateIconSize(1f, this); }; mDeviceProfile = originDeviceProfile.toBuilder(this) .withDimensionsOverride(overrideProvider).build(); if (DisplayController.isTransientTaskbar(this)) { mTransientTaskbarDeviceProfile = mDeviceProfile; mPersistentTaskbarDeviceProfile = mDeviceProfile .toBuilder(this) .withDimensionsOverride(overrideProvider) .setIsTransientTaskbar(false) .build(); } else { mPersistentTaskbarDeviceProfile = mDeviceProfile; mTransientTaskbarDeviceProfile = mDeviceProfile .toBuilder(this) .withDimensionsOverride(overrideProvider) .setIsTransientTaskbar(true) .build(); } mNavMode = DisplayController.getNavigationMode(this); } /** Called when the visibility of the bubble bar changed. */ public void bubbleBarVisibilityChanged(boolean isVisible) { mControllers.uiController.adjustHotseatForBubbleBar(isVisible); mControllers.taskbarViewController.resetIconAlignmentController(); } public void init(@NonNull TaskbarSharedState sharedState) { mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, getResources(), false); mLastRequestedNonFullscreenSize = getDefaultTaskbarWindowSize(); mWindowLayoutParams = createAllWindowParams(); // Initialize controllers after all are constructed. mControllers.init(sharedState); // This may not be necessary and can be reverted once we move towards recreating all // controllers without re-creating the window mControllers.rotationButtonController.onNavigationModeChanged(mNavMode.resValue); updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */); disableNavBarElements(sharedState.disableNavBarDisplayId, sharedState.disableNavBarState1, sharedState.disableNavBarState2, false /* animate */); onSystemBarAttributesChanged(sharedState.systemBarAttrsDisplayId, sharedState.systemBarAttrsBehavior); onNavButtonsDarkIntensityChanged(sharedState.navButtonsDarkIntensity); onNavigationBarLumaSamplingEnabled(sharedState.mLumaSamplingDisplayId, sharedState.mIsLumaSamplingEnabled); if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) { // W/ the flag not set this entire class gets re-created, which resets the value of // mIsDestroyed. We re-use the class for small-screen, so we explicitly have to mark // this class as non-destroyed mIsDestroyed = false; } if (!enableTaskbarNoRecreate() && !mAddedWindow) { mWindowManager.addView(mDragLayer, mWindowLayoutParams); mAddedWindow = true; } else { notifyUpdateLayoutParams(); } } /** * @return {@code true} if the device profile isn't a large screen profile and we are using a * single window for taskbar and navbar. */ public boolean isPhoneMode() { return ENABLE_TASKBAR_NAVBAR_UNIFICATION && mDeviceProfile.isPhone && !mDeviceProfile.isTaskbarPresent; } /** * @return {@code true} if {@link #isPhoneMode()} is true and we're using 3 button-nav */ public boolean isPhoneButtonNavMode() { return isPhoneMode() && isThreeButtonNav(); } /** * @return {@code true} if {@link #isPhoneMode()} is true and we're using gesture nav */ public boolean isPhoneGestureNavMode() { return isPhoneMode() && !isThreeButtonNav(); } /** Returns {@code true} iff a tiny version of taskbar is shown on phone. */ public boolean isTinyTaskbar() { return enableTinyTaskbar() && mDeviceProfile.isPhone && mDeviceProfile.isTaskbarPresent; } /** * Returns if software keyboard is docked or input toolbar is placed at the taskbar area */ public boolean isImeDocked() { View dragLayer = getDragLayer(); WindowInsets insets = dragLayer.getRootWindowInsets(); if (insets == null) { return false; } WindowInsetsCompat insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets, dragLayer.getRootView()); if (insetsCompat.isVisible(WindowInsetsCompat.Type.ime())) { Insets imeInsets = insetsCompat.getInsets(WindowInsetsCompat.Type.ime()); return imeInsets.bottom >= getResources().getDimensionPixelSize( R.dimen.floating_ime_inset_height); } else { return false; } } /** * Show Taskbar upon receiving broadcast */ public void showTaskbarFromBroadcast() { mControllers.taskbarStashController.showTaskbarFromBroadcast(); } /** Toggles Taskbar All Apps overlay. */ public void toggleAllApps() { mControllers.taskbarAllAppsController.toggle(); } /** Toggles Taskbar All Apps overlay with keyboard ready for search. */ public void toggleAllAppsSearch() { mControllers.taskbarAllAppsController.toggleSearch(); } @Override public DeviceProfile getDeviceProfile() { return mDeviceProfile; } @Override public void dispatchDeviceProfileChanged() { super.dispatchDeviceProfileChanged(); Trace.instantForTrack(TRACE_TAG_APP, "TaskbarActivityContext#DeviceProfileChanged", getDeviceProfile().toSmallString()); } @NonNull public LauncherPrefs getLauncherPrefs() { return mLauncherPrefs; } /** * Returns the View bounds of transient taskbar. */ public Rect getTransientTaskbarBounds() { return mTransientTaskbarBounds; } protected float getCurrentTaskbarWidth() { return mControllers.taskbarViewController.getCurrentVisualTaskbarWidth(); } @Override public StatsLogManager getStatsLogManager() { // Used to mock, can't mock a default interface method directly return super.getStatsLogManager(); } /** * Creates LayoutParams for adding a view directly to WindowManager as a new window. * * @param type The window type to pass to the created WindowManager.LayoutParams. * @param title The window title to pass to the created WindowManager.LayoutParams. */ public WindowManager.LayoutParams createDefaultWindowLayoutParams(int type, String title) { int windowFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SLIPPERY | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; if (DisplayController.isTransientTaskbar(this) && !isRunningInTestHarness()) { windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; } WindowManager.LayoutParams windowLayoutParams = new WindowManager.LayoutParams( MATCH_PARENT, mLastRequestedNonFullscreenSize, type, windowFlags, PixelFormat.TRANSLUCENT); windowLayoutParams.setTitle(title); windowLayoutParams.packageName = getPackageName(); windowLayoutParams.gravity = Gravity.BOTTOM; windowLayoutParams.setFitInsetsTypes(0); windowLayoutParams.receiveInsetsIgnoringZOrder = true; windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; windowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; windowLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; windowLayoutParams.accessibilityTitle = getString( isPhoneMode() ? R.string.taskbar_phone_a11y_title : R.string.taskbar_a11y_title); return windowLayoutParams; } /** * Creates {@link WindowManager.LayoutParams} for Taskbar, and also sets LP.paramsForRotation * for taskbar */ private WindowManager.LayoutParams createAllWindowParams() { final int windowType = ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL; WindowManager.LayoutParams windowLayoutParams = createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE); windowLayoutParams.paramsForRotation = new WindowManager.LayoutParams[4]; for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { WindowManager.LayoutParams lp = createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE); if (isPhoneButtonNavMode()) { populatePhoneButtonNavModeWindowLayoutParams(rot, lp); } windowLayoutParams.paramsForRotation[rot] = lp; } // Override with current layout params WindowManager.LayoutParams currentParams = windowLayoutParams.paramsForRotation[getDisplay().getRotation()]; windowLayoutParams.width = currentParams.width; windowLayoutParams.height = currentParams.height; windowLayoutParams.gravity = currentParams.gravity; return windowLayoutParams; } /** * Update {@link WindowManager.LayoutParams} with values specific to phone and 3 button * navigation users */ private void populatePhoneButtonNavModeWindowLayoutParams(int rot, WindowManager.LayoutParams lp) { lp.width = WindowManager.LayoutParams.MATCH_PARENT; lp.height = WindowManager.LayoutParams.MATCH_PARENT; lp.gravity = Gravity.BOTTOM; // Override with per-rotation specific values switch (rot) { case Surface.ROTATION_0, Surface.ROTATION_180 -> { lp.height = mLastRequestedNonFullscreenSize; } case Surface.ROTATION_90 -> { lp.width = mLastRequestedNonFullscreenSize; lp.gravity = Gravity.END; } case Surface.ROTATION_270 -> { lp.width = mLastRequestedNonFullscreenSize; lp.gravity = Gravity.START; } } } public void onConfigurationChanged(@Config int configChanges) { mControllers.onConfigurationChanged(configChanges); if (!mIsUserSetupComplete) { setTaskbarWindowSize(getSetupWindowSize()); } } public boolean isThreeButtonNav() { return mNavMode == NavigationMode.THREE_BUTTONS; } public boolean isGestureNav() { return mNavMode == NavigationMode.NO_BUTTON; } public boolean imeDrawsImeNavBar() { return mImeDrawsImeNavBar; } public int getCornerRadius() { return isPhoneMode() ? 0 : getResources().getDimensionPixelSize( R.dimen.persistent_taskbar_corner_radius); } public WindowManager.LayoutParams getWindowLayoutParams() { return mWindowLayoutParams; } @Override public TaskbarDragLayer getDragLayer() { return mDragLayer; } @Override public Rect getFolderBoundingBox() { return mControllers.taskbarDragLayerController.getFolderBoundingBox(); } @Override public TaskbarDragController getDragController() { return mControllers.taskbarDragController; } @Nullable public BubbleControllers getBubbleControllers() { return mControllers.bubbleControllers.orElse(null); } @NonNull public NavHandle getNavHandle() { return mControllers.stashedHandleViewController; } @Override public ViewCache getViewCache() { return mViewCache; } @Override public View.OnClickListener getItemOnClickListener() { return this::onTaskbarIconClicked; } /** * Change from hotseat/predicted hotseat to taskbar container. */ @Override public void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) { if (!itemInfoBuilder.hasContainerInfo()) { return; } LauncherAtom.ContainerInfo oldContainer = itemInfoBuilder.getContainerInfo(); LauncherAtom.TaskBarContainer.Builder taskbarBuilder = LauncherAtom.TaskBarContainer.newBuilder(); if (mControllers.uiController.isInOverviewUi()) { taskbarBuilder.setTaskSwitcherContainer( LauncherAtom.TaskSwitcherContainer.newBuilder()); } if (oldContainer.hasPredictedHotseatContainer()) { LauncherAtom.PredictedHotseatContainer predictedHotseat = oldContainer.getPredictedHotseatContainer(); if (predictedHotseat.hasIndex()) { taskbarBuilder.setIndex(predictedHotseat.getIndex()); } if (predictedHotseat.hasCardinality()) { taskbarBuilder.setCardinality(predictedHotseat.getCardinality()); } itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder() .setTaskBarContainer(taskbarBuilder)); } else if (oldContainer.hasHotseat()) { LauncherAtom.HotseatContainer hotseat = oldContainer.getHotseat(); if (hotseat.hasIndex()) { taskbarBuilder.setIndex(hotseat.getIndex()); } itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder() .setTaskBarContainer(taskbarBuilder)); } else if (oldContainer.hasFolder() && oldContainer.getFolder().hasHotseat()) { LauncherAtom.FolderContainer.Builder folderBuilder = oldContainer.getFolder() .toBuilder(); LauncherAtom.HotseatContainer hotseat = folderBuilder.getHotseat(); if (hotseat.hasIndex()) { taskbarBuilder.setIndex(hotseat.getIndex()); } folderBuilder.setTaskbar(taskbarBuilder); folderBuilder.clearHotseat(); itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder() .setFolder(folderBuilder)); } else if (oldContainer.hasAllAppsContainer()) { itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder() .setAllAppsContainer(oldContainer.getAllAppsContainer().toBuilder() .setTaskbarContainer(taskbarBuilder))); } else if (oldContainer.hasPredictionContainer()) { itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder() .setPredictionContainer(oldContainer.getPredictionContainer().toBuilder() .setTaskbarContainer(taskbarBuilder))); } } @Override public DotInfo getDotInfoForItem(ItemInfo info) { return getPopupDataProvider().getDotInfoForItem(info); } @NonNull @Override public PopupDataProvider getPopupDataProvider() { return mControllers.taskbarPopupController.getPopupDataProvider(); } @Override public View.AccessibilityDelegate getAccessibilityDelegate() { return mAccessibilityDelegate; } @Override public boolean isBindingItems() { return mBindingItems; } public void setBindingItems(boolean bindingItems) { mBindingItems = bindingItems; } @Override public void onDragStart() { setTaskbarWindowFullscreen(true); } @Override public void onDragEnd() { onDragEndOrViewRemoved(); } @Override public void onPopupVisibilityChanged(boolean isVisible) { setTaskbarWindowFocusable(isVisible); } @Override public void onSplitScreenMenuButtonClicked() { PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(this); if (popup != null) { popup.addOnCloseCallback(() -> { mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true); }); } } @Override public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) { RunnableList callbacks = new RunnableList(); ActivityOptions options = ActivityOptions.makeCustomAnimation(this, 0, 0); options.setSplashScreenStyle(splashScreenStyle); options.setPendingIntentBackgroundActivityStartMode( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); IRemoteCallback endCallback = completeRunnableListCallback(callbacks); options.setOnAnimationAbortListener(endCallback); options.setOnAnimationFinishedListener(endCallback); return new ActivityOptionsWrapper(options, callbacks); } @Override public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) { return makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED); } /** * Sets a new data-source for this taskbar instance */ public void setUIController(@NonNull TaskbarUIController uiController) { mControllers.setUiController(uiController); } /** * Sets the flag indicating setup UI is visible */ public void setSetupUIVisible(boolean isVisible) { mControllers.taskbarStashController.setSetupUIVisible(isVisible); } /** * Called when this instance of taskbar is no longer needed */ public void onDestroy() { mIsDestroyed = true; setUIController(TaskbarUIController.DEFAULT); mControllers.onDestroy(); if (!enableTaskbarNoRecreate() && !ENABLE_TASKBAR_NAVBAR_UNIFICATION) { mWindowManager.removeViewImmediate(mDragLayer); mAddedWindow = false; } } public boolean isDestroyed() { return mIsDestroyed; } public void updateSysuiStateFlags(@SystemUiStateFlags long systemUiStateFlags, boolean fromInit) { mControllers.navbarButtonsViewController.updateStateForSysuiFlags(systemUiStateFlags, fromInit); boolean isShadeVisible = (systemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE) != 0; onNotificationShadeExpandChanged(isShadeVisible, fromInit); mControllers.taskbarViewController.setRecentsButtonDisabled( mControllers.navbarButtonsViewController.isRecentsDisabled() || isNavBarKidsModeActive()); mControllers.stashedHandleViewController.setIsHomeButtonDisabled( mControllers.navbarButtonsViewController.isHomeDisabled()); mControllers.stashedHandleViewController.updateStateForSysuiFlags(systemUiStateFlags); mControllers.taskbarKeyguardController.updateStateForSysuiFlags(systemUiStateFlags); mControllers.taskbarStashController.updateStateForSysuiFlags( systemUiStateFlags, fromInit || !isUserSetupComplete()); mControllers.taskbarScrimViewController.updateStateForSysuiFlags(systemUiStateFlags, fromInit); mControllers.navButtonController.updateSysuiFlags(systemUiStateFlags); mControllers.taskbarForceVisibleImmersiveController.updateSysuiFlags(systemUiStateFlags); mControllers.voiceInteractionWindowController.setIsVoiceInteractionWindowVisible( (systemUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0, fromInit); mControllers.uiController.updateStateForSysuiFlags(systemUiStateFlags); mControllers.bubbleControllers.ifPresent(controllers -> { controllers.bubbleBarController.updateStateForSysuiFlags(systemUiStateFlags); controllers.bubbleStashedHandleViewController.setIsHomeButtonDisabled( mControllers.navbarButtonsViewController.isHomeDisabled()); }); } /** * Hides the taskbar icons and background when the notication shade is expanded. */ private void onNotificationShadeExpandChanged(boolean isExpanded, boolean skipAnim) { float alpha = isExpanded ? 0 : 1; AnimatorSet anim = new AnimatorSet(); anim.play(mControllers.taskbarViewController.getTaskbarIconAlpha().get( TaskbarViewController.ALPHA_INDEX_NOTIFICATION_EXPANDED).animateToValue(alpha)); anim.play(mControllers.taskbarDragLayerController.getNotificationShadeBgTaskbar() .animateToValue(alpha)); anim.start(); if (skipAnim) { anim.end(); } } public void onRotationProposal(int rotation, boolean isValid) { mControllers.rotationButtonController.onRotationProposal(rotation, isValid); } public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) { if (displayId != getDisplayId()) { return; } mControllers.rotationButtonController.onDisable2FlagChanged(state2); } public void onSystemBarAttributesChanged(int displayId, int behavior) { mControllers.rotationButtonController.onBehaviorChanged(displayId, behavior); } public void onNavButtonsDarkIntensityChanged(float darkIntensity) { mControllers.navbarButtonsViewController.getTaskbarNavButtonDarkIntensity() .updateValue(darkIntensity); } public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) { mControllers.stashedHandleViewController.onNavigationBarLumaSamplingEnabled(displayId, enable); } /** * Called to update a {@link AutohideSuspendFlag} with a new value. */ public void setAutohideSuspendFlag(@AutohideSuspendFlag int flag, boolean newValue) { mControllers.taskbarAutohideSuspendController.updateFlag(flag, newValue); } /** * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size. */ public void setTaskbarWindowFullscreen(boolean fullscreen) { setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_FULLSCREEN, fullscreen); mIsFullscreen = fullscreen; setTaskbarWindowSize(fullscreen ? MATCH_PARENT : mLastRequestedNonFullscreenSize); } /** * Called when drag ends or when a view is removed from the DragLayer. */ void onDragEndOrViewRemoved() { boolean isDragInProgress = mControllers.taskbarDragController.isSystemDragInProgress(); // Overlay AFVs are in a separate window and do not require Taskbar to be fullscreen. if (!isDragInProgress && !AbstractFloatingView.hasOpenView( this, TYPE_ALL & ~TYPE_TASKBAR_OVERLAY_PROXY)) { // Reverts Taskbar window to its original size setTaskbarWindowFullscreen(false); } setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_DRAGGING, isDragInProgress); } public boolean isTaskbarWindowFullscreen() { return mIsFullscreen; } /** * Updates the TaskbarContainer size (pass {@link #getDefaultTaskbarWindowSize()} to reset). */ public void setTaskbarWindowSize(int size) { // In landscape phone button nav mode, we should set the task bar width instead of height // because this is the only case in which the nav bar is not on the display bottom. boolean landscapePhoneButtonNav = isPhoneButtonNavMode() && mDeviceProfile.isLandscape; if ((landscapePhoneButtonNav ? mWindowLayoutParams.width : mWindowLayoutParams.height) == size || mIsDestroyed) { return; } if (size == MATCH_PARENT) { size = mDeviceProfile.heightPx; } else { mLastRequestedNonFullscreenSize = size; if (mIsFullscreen) { // We still need to be fullscreen, so defer any change to our height until we call // setTaskbarWindowFullscreen(false). For example, this could happen when dragging // from the gesture region, as the drag will cancel the gesture and reset launcher's // state, which in turn normally would reset the taskbar window height as well. return; } } if (landscapePhoneButtonNav) { mWindowLayoutParams.width = size; for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { mWindowLayoutParams.paramsForRotation[rot].width = size; } } else { mWindowLayoutParams.height = size; for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { mWindowLayoutParams.paramsForRotation[rot].height = size; } } mControllers.runAfterInit( mControllers.taskbarInsetsController ::onTaskbarOrBubblebarWindowHeightOrInsetsChanged); notifyUpdateLayoutParams(); } /** * Returns the default size (in most cases height, but in 3-button phone mode, width) of the * window, including the static corner radii above taskbar. */ public int getDefaultTaskbarWindowSize() { Resources resources = getResources(); if (isPhoneMode()) { return isThreeButtonNav() ? resources.getDimensionPixelSize(R.dimen.taskbar_phone_size) : resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size); } if (!isUserSetupComplete()) { return getSetupWindowSize(); } boolean shouldTreatAsTransient = DisplayController.isTransientTaskbar(this) || (enableTaskbarPinning() && !isThreeButtonNav()); int extraHeightForTaskbarTooltips = enableCursorHoverStates() ? resources.getDimensionPixelSize(R.dimen.arrow_toast_arrow_height) + (resources.getDimensionPixelSize(R.dimen.taskbar_tooltip_vertical_padding) * 2) + calculateTextHeight( resources.getDimensionPixelSize(R.dimen.arrow_toast_text_size)) : 0; // Return transient taskbar window height when pinning feature is enabled, so taskbar view // does not get cut off during pinning animation. if (shouldTreatAsTransient) { DeviceProfile transientTaskbarDp = mDeviceProfile.toBuilder(this) .setIsTransientTaskbar(true).build(); return transientTaskbarDp.taskbarHeight + (2 * transientTaskbarDp.taskbarBottomMargin) + Math.max(extraHeightForTaskbarTooltips, resources.getDimensionPixelSize( R.dimen.transient_taskbar_shadow_blur)); } return mDeviceProfile.taskbarHeight + getCornerRadius() + extraHeightForTaskbarTooltips; } public int getSetupWindowSize() { return getResources().getDimensionPixelSize(R.dimen.taskbar_suw_frame); } public DeviceProfile getTransientTaskbarDeviceProfile() { return mTransientTaskbarDeviceProfile; } public DeviceProfile getPersistentTaskbarDeviceProfile() { return mPersistentTaskbarDeviceProfile; } /** * Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar * window. */ public void setTaskbarWindowFocusable(boolean focusable) { if (focusable) { mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE; } else { mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE; } notifyUpdateLayoutParams(); } /** * Applies forcibly show flag to taskbar window iff transient taskbar is unstashed. */ public void applyForciblyShownFlagWhileTransientTaskbarUnstashed(boolean shouldForceShow) { if (!DisplayController.isTransientTaskbar(this)) { return; } if (shouldForceShow) { mWindowLayoutParams.forciblyShownTypes |= WindowInsets.Type.navigationBars(); } else { mWindowLayoutParams.forciblyShownTypes &= ~WindowInsets.Type.navigationBars(); } notifyUpdateLayoutParams(); } /** * Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar * window. If we're now focusable, also move nav buttons to a separate window above IME. */ public void setTaskbarWindowFocusableForIme(boolean focusable) { if (focusable) { mControllers.navbarButtonsViewController.moveNavButtonsToNewWindow(); } else { mControllers.navbarButtonsViewController.moveNavButtonsBackToTaskbarWindow(); } setTaskbarWindowFocusable(focusable); } /** Adds the given view to WindowManager with the provided LayoutParams (creates new window). */ public void addWindowView(View view, WindowManager.LayoutParams windowLayoutParams) { if (!view.isAttachedToWindow()) { mWindowManager.addView(view, windowLayoutParams); } } /** Removes the given view from WindowManager. See {@link #addWindowView}. */ public void removeWindowView(View view) { if (view.isAttachedToWindow()) { mWindowManager.removeViewImmediate(view); } } @Override public void startSplitSelection(SplitSelectSource splitSelectSource) { mControllers.uiController.startSplitSelection(splitSelectSource); } protected void onTaskbarIconClicked(View view) { TaskbarUIController taskbarUIController = mControllers.uiController; RecentsView recents = taskbarUIController.getRecentsView(); boolean shouldCloseAllOpenViews = true; Object tag = view.getTag(); if (tag instanceof Task) { Task task = (Task) tag; ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key, ActivityOptions.makeBasic()); mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true); } else if (tag instanceof FolderInfo) { // Tapping an expandable folder icon on Taskbar shouldCloseAllOpenViews = false; expandFolder((FolderIcon) view); } else if (tag instanceof AppPairInfo api) { // Tapping an app pair icon on Taskbar if (recents != null && recents.isSplitSelectionActive()) { Toast.makeText(this, "Unable to split with an app pair. Select another app.", Toast.LENGTH_SHORT).show(); } else { // Else launch the selected app pair launchFromTaskbar(recents, view, api.getContents()); mControllers.uiController.onTaskbarIconLaunched(api); mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true); } } else if (tag instanceof WorkspaceItemInfo) { // Tapping a launchable icon on Taskbar WorkspaceItemInfo info = (WorkspaceItemInfo) tag; if (!info.isDisabled() || !ItemClickHandler.handleDisabledItemClicked(info, this)) { if (recents != null && recents.isSplitSelectionActive()) { // If we are selecting a second app for split, launch the split tasks taskbarUIController.triggerSecondAppForSplit(info, info.intent, view); } else { // Else launch the selected task Intent intent = new Intent(info.getIntent()) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) { Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); } else if (info.isPromise()) { TestLogging.recordEvent( TestProtocol.SEQUENCE_MAIN, "start: taskbarPromiseIcon"); intent = ApiWrapper.INSTANCE.get(this).getAppMarketActivityIntent( info.getTargetPackage(), Process.myUserHandle()); startActivity(intent); } else if (info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) { TestLogging.recordEvent( TestProtocol.SEQUENCE_MAIN, "start: taskbarDeepShortcut"); String id = info.getDeepShortcutId(); String packageName = intent.getPackage(); getSystemService(LauncherApps.class) .startShortcut(packageName, id, null, null, info.user); } else { launchFromTaskbar(recents, view, Collections.singletonList(info)); } } catch (NullPointerException | ActivityNotFoundException | SecurityException e) { Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT) .show(); Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e); return; } } // If the app was launched from a folder, stash the taskbar after it closes Folder f = Folder.getOpen(this); if (f != null && f.getInfo().id == info.container) { f.addOnFolderStateChangedListener(new Folder.OnFolderStateChangedListener() { @Override public void onFolderStateChanged(int newState) { if (newState == Folder.STATE_CLOSED) { f.removeOnFolderStateChangedListener(this); mControllers.taskbarStashController .updateAndAnimateTransientTaskbar(true); } } }); } mControllers.uiController.onTaskbarIconLaunched(info); mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true); } } else if (tag instanceof AppInfo) { // Tapping an item in AllApps AppInfo info = (AppInfo) tag; if (recents != null && taskbarUIController.getRecentsView().isSplitSelectionActive()) { // If we are selecting a second app for split, launch the split tasks taskbarUIController.triggerSecondAppForSplit(info, info.intent, view); } else { launchFromTaskbar(recents, view, Collections.singletonList(info)); } mControllers.uiController.onTaskbarIconLaunched(info); mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true); } else if (tag instanceof ItemClickProxy) { ((ItemClickProxy) tag).onItemClicked(view); } else { Log.e(TAG, "Unknown type clicked: " + tag); } if (shouldCloseAllOpenViews) { AbstractFloatingView.closeAllOpenViews(this); } } /** * Runs when the user taps a Taskbar icon in TaskbarActivityContext (Overview or inside an app), * and calls the appropriate method to animate and launch. */ private void launchFromTaskbar(@Nullable RecentsView recents, @Nullable View launchingIconView, List itemInfos) { if (isInApp()) { launchFromInAppTaskbar(recents, launchingIconView, itemInfos); } else { launchFromOverviewTaskbar(recents, launchingIconView, itemInfos); } } /** * Runs when the user taps a Taskbar icon while inside an app. */ private void launchFromInAppTaskbar(@Nullable RecentsView recents, @Nullable View launchingIconView, List itemInfos) { if (recents == null) { return; } boolean tappedAppPair = itemInfos.size() == 2; if (tappedAppPair) { // If the icon is an app pair, the logic gets a bit complicated because we play // different animations depending on which app (or app pair) is currently running on // screen, so delegate logic to appPairsController. recents.getSplitSelectController().getAppPairsController() .handleAppPairLaunchInApp((AppPairIcon) launchingIconView, itemInfos); } else { // Tapped a single app, nothing complicated here. startItemInfoActivity(itemInfos.get(0), null /*foundTask*/); } } /** * Run when the user taps a Taskbar icon while in Overview. If the tapped app is currently * visible to the user in Overview, or is part of a visible split pair, we expand the TaskView * as if the user tapped on it (preserving the split pair). Otherwise, launch it normally * (potentially breaking a split pair). */ private void launchFromOverviewTaskbar(@Nullable RecentsView recents, @Nullable View launchingIconView, List itemInfos) { if (recents == null) { return; } boolean isLaunchingAppPair = itemInfos.size() == 2; // Convert the list of ItemInfo instances to a list of ComponentKeys List componentKeys = itemInfos.stream().map(ItemInfo::getComponentKey).toList(); recents.getSplitSelectController().findLastActiveTasksAndRunCallback( componentKeys, isLaunchingAppPair, foundTasks -> { @Nullable Task foundTask = foundTasks[0]; if (foundTask != null) { TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id); if (foundTaskView != null && foundTaskView.isVisibleToUser()) { TestLogging.recordEvent( TestProtocol.SEQUENCE_MAIN, "start: taskbarAppIcon"); foundTaskView.launchTasks(); return; } } if (isLaunchingAppPair) { // Finish recents animation if it's running before launching to ensure // we get both leashes for the animation mControllers.uiController.setSkipNextRecentsAnimEnd(); recents.switchToScreenshot(() -> recents.finishRecentsAnimation(true /*toRecents*/, false /*shouldPip*/, () -> recents .getSplitSelectController() .getAppPairsController() .launchAppPair((AppPairIcon) launchingIconView, -1 /*cuj*/))); } else { startItemInfoActivity(itemInfos.get(0), foundTask); } } ); } /** * Starts an activity with the information provided by the "info" param. However, if * taskInRecents is present, it will prioritize re-launching an existing instance via * {@link ActivityManagerWrapper#startActivityFromRecents(int, ActivityOptions)} */ private void startItemInfoActivity(ItemInfo info, @Nullable Task taskInRecents) { Intent intent = new Intent(info.getIntent()) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: taskbarAppIcon"); if (info.user.equals(Process.myUserHandle())) { // TODO(b/216683257): Use startActivityForResult for search results that require it. if (taskInRecents != null) { // Re launch instance from recents ActivityOptionsWrapper opts = getActivityLaunchOptions(null, info); opts.options.setLaunchDisplayId( getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId()); if (ActivityManagerWrapper.getInstance() .startActivityFromRecents(taskInRecents.key, opts.options)) { mControllers.uiController.getRecentsView() .addSideTaskLaunchCallback(opts.onEndCallback); return; } } startActivity(intent); } else { getSystemService(LauncherApps.class).startMainActivity( intent.getComponent(), info.user, intent.getSourceBounds(), null); } } catch (NullPointerException | ActivityNotFoundException | SecurityException e) { Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT) .show(); Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e); } } /** Expands a folder icon when it is clicked */ private void expandFolder(FolderIcon folderIcon) { Folder folder = folderIcon.getFolder(); folder.setPriorityOnFolderStateChangedListener( new Folder.OnFolderStateChangedListener() { @Override public void onFolderStateChanged(int newState) { if (newState == Folder.STATE_OPEN) { setTaskbarWindowFocusableForIme(true); } else if (newState == Folder.STATE_CLOSED) { // Defer by a frame to ensure we're no longer fullscreen and thus // won't jump. getDragLayer().post(() -> setTaskbarWindowFocusableForIme(false)); folder.setPriorityOnFolderStateChangedListener(null); } } }); setTaskbarWindowFullscreen(true); getDragLayer().post(() -> { folder.animateOpen(); getStatsLogManager().logger().withItemInfo(folder.mInfo).log(LAUNCHER_FOLDER_OPEN); folder.iterateOverItems((itemInfo, itemView) -> { mControllers.taskbarViewController .setClickAndLongClickListenersForIcon(itemView); // To play haptic when dragging, like other Taskbar items do. itemView.setHapticFeedbackEnabled(true); return false; }); }); } /** * Returns whether the taskbar is currently visually stashed. */ public boolean isTaskbarStashed() { return mControllers.taskbarStashController.isStashed(); } /** * Called when we want to unstash taskbar when user performs swipes up gesture. */ public void onSwipeToUnstashTaskbar() { boolean wasStashed = mControllers.taskbarStashController.isStashed(); mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(/* stash= */ false); boolean isStashed = mControllers.taskbarStashController.isStashed(); if (isStashed != wasStashed) { VibratorWrapper.INSTANCE.get(this).vibrateForTaskbarUnstash(); } mControllers.taskbarEduTooltipController.hide(); } /** * Called when we want to open bubblebar when user performs swipes up gesture. */ public void onSwipeToOpenBubblebar() { mControllers.bubbleControllers.ifPresent(controllers -> { controllers.bubbleStashController.showBubbleBar(/* expandBubbles= */ true); }); } /** Returns {@code true} if Taskbar All Apps is open. */ public boolean isTaskbarAllAppsOpen() { return mControllers.taskbarAllAppsController.isOpen(); } /** Toggles the Taskbar's stash state. */ public void toggleTaskbarStash() { mControllers.taskbarStashController.toggleTaskbarStash(); } /** * Plays the taskbar background alpha animation if one is not currently playing. */ public void playTaskbarBackgroundAlphaAnimation() { mControllers.taskbarStashController.playTaskbarBackgroundAlphaAnimation(); } /** * Called to start the taskbar translation spring to its settled translation (0). */ public void startTranslationSpring() { mControllers.taskbarTranslationController.startSpring(); } /** * Returns a callback to help monitor the swipe gesture. */ public TransitionCallback getTranslationCallbacks() { return mControllers.taskbarTranslationController.getTransitionCallback(); } /** * Called when a transient Autohide flag suspend status changes. */ public void onTransientAutohideSuspendFlagChanged(boolean isSuspended) { mControllers.taskbarStashController.updateTaskbarTimeout(isSuspended); } /** * Called when we detect a motion down or up/cancel in the nav region while stashed. * * @param animateForward Whether to animate towards the unstashed hint state or back to stashed. */ public void startTaskbarUnstashHint(boolean animateForward) { mControllers.taskbarStashController.startUnstashHint(animateForward); } /** * Enables the auto timeout for taskbar stashing. This method should only be used for taskbar * testing. */ @VisibleForTesting public void enableBlockingTimeoutDuringTests(boolean enableBlockingTimeout) { mControllers.taskbarStashController.enableBlockingTimeoutDuringTests(enableBlockingTimeout); } /** * Unstashes the Taskbar if it is stashed. */ @VisibleForTesting public void unstashTaskbarIfStashed() { if (DisplayController.isTransientTaskbar(this)) { mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(false); } } /** Unstashes the Bubble Bar if it is stashed. */ @VisibleForTesting public void unstashBubbleBarIfStashed() { mControllers.bubbleControllers.ifPresent(bubbleControllers -> { if (bubbleControllers.bubbleStashController.isStashed()) { bubbleControllers.bubbleStashController.showBubbleBar(false); } }); } public boolean isUserSetupComplete() { return mIsUserSetupComplete; } public boolean isNavBarKidsModeActive() { return mIsNavBarKidsMode && isThreeButtonNav(); } protected boolean isNavBarForceVisible() { return mIsNavBarForceVisible; } /** * Displays a single frame of the Launcher start from SUW animation. * * This animation is a combination of the Launcher resume animation, which animates the hotseat * icons into position, the Taskbar unstash to hotseat animation, which animates the Taskbar * stash bar into the hotseat icons, and an override to prevent showing the Taskbar all apps * button. * * This should be used to run a Taskbar unstash to hotseat animation whose progress matches a * swipe progress. * * @param duration a placeholder duration to be used to ensure all full-length * sub-animations are properly coordinated. This duration should not actually * be used since this animation tracks a swipe progress. */ protected AnimatorPlaybackController createLauncherStartFromSuwAnim(int duration) { AnimatorSet fullAnimation = new AnimatorSet(); fullAnimation.setDuration(duration); TaskbarUIController uiController = mControllers.uiController; if (uiController instanceof LauncherTaskbarUIController) { ((LauncherTaskbarUIController) uiController).addLauncherVisibilityChangedAnimation( fullAnimation, duration); } mControllers.taskbarStashController.addUnstashToHotseatAnimationFromSuw(fullAnimation, duration); View allAppsButton = mControllers.taskbarViewController.getAllAppsButtonView(); if (allAppsButton != null && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) { ValueAnimator alphaOverride = ValueAnimator.ofFloat(0, 1); alphaOverride.setDuration(duration); alphaOverride.addUpdateListener(a -> { // Override the alpha updates in the icon alignment animation. allAppsButton.setAlpha(0); }); fullAnimation.play(alphaOverride); } return AnimatorPlaybackController.wrap(fullAnimation, duration); } /** * Called when we determine the touchable region. * * @param exclude {@code true} then the magnification region computation will omit the window. */ public void excludeFromMagnificationRegion(boolean exclude) { if (mIsExcludeFromMagnificationRegion == exclude) { return; } mIsExcludeFromMagnificationRegion = exclude; if (exclude) { mWindowLayoutParams.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; } else { mWindowLayoutParams.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; } notifyUpdateLayoutParams(); } void notifyUpdateLayoutParams() { if (mDragLayer.isAttachedToWindow()) { if (enableTaskbarNoRecreate()) { mWindowManager.updateViewLayout(mDragLayer.getRootView(), mWindowLayoutParams); } else { mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams); } } } public void showPopupMenuForIcon(BubbleTextView btv) { setTaskbarWindowFullscreen(true); btv.post(() -> mControllers.taskbarPopupController.showForIcon(btv)); } public void launchKeyboardFocusedTask() { mControllers.uiController.launchKeyboardFocusedTask(); } public boolean isInApp() { return mControllers.taskbarStashController.isInApp(); } public boolean isInStashedLauncherState() { return mControllers.taskbarStashController.isInStashedLauncherState(); } protected void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "TaskbarActivityContext:"); pw.println(String.format( "%s\tmNavMode=%s", prefix, mNavMode)); pw.println(String.format( "%s\tmImeDrawsImeNavBar=%b", prefix, mImeDrawsImeNavBar)); pw.println(String.format( "%s\tmIsUserSetupComplete=%b", prefix, mIsUserSetupComplete)); pw.println(String.format( "%s\tmWindowLayoutParams.height=%dpx", prefix, mWindowLayoutParams.height)); pw.println(String.format( "%s\tmBindInProgress=%b", prefix, mBindingItems)); mControllers.dumpLogs(prefix + "\t", pw); mDeviceProfile.dump(this, prefix, pw); } @VisibleForTesting public int getTaskbarAllAppsTopPadding() { return mControllers.taskbarAllAppsController.getTaskbarAllAppsTopPadding(); } @VisibleForTesting public int getTaskbarAllAppsScroll() { return mControllers.taskbarAllAppsController.getTaskbarAllAppsScroll(); } @VisibleForTesting public float getStashedTaskbarScale() { return mControllers.stashedHandleViewController.getStashedHandleHintScale().value; } /** Closes the KeyboardQuickSwitchView without an animation if open. */ public void closeKeyboardQuickSwitchView() { mControllers.keyboardQuickSwitchController.closeQuickSwitchView(false); } boolean canToggleHomeAllApps() { return mControllers.uiController.canToggleHomeAllApps(); } @VisibleForTesting public TaskbarControllers getControllers() { return mControllers; } }