Files
Lawnchair/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
T
Sukesh Ram d82503fc42 Fix Taskbar Y-Translation with Visible Bottom Sheet
When bottom sheet is visible, such as in SUW or when app launcher pauses, the taskbar three buttons are translated upwards clashing with SUW UI.

Flag: EXEMPT bugfix
Bug: 358532768
Test: Manual
Change-Id: I0d18bda5cb7aeae894f5058df89cc413e34c00fc
2024-10-14 23:41:38 +00:00

1514 lines
66 KiB
Java

/*
* Copyright (C) 2022 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.uioverrides;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.os.Trace.TRACE_TAG_APP;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
import static com.android.launcher3.LauncherSettings.Animation.VIEW_BACKGROUND;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.NO_OFFSET;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.Utilities.isRtl;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED;
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
import static com.android.launcher3.popup.SystemShortcut.BUBBLE_SHORTCUT;
import static com.android.launcher3.popup.SystemShortcut.DONT_SUGGEST_APP;
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
import static com.android.launcher3.popup.SystemShortcut.PRIVATE_PROFILE_INSTALL;
import static com.android.launcher3.popup.SystemShortcut.UNINSTALL_APP;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.MINUS_ONE_PAGE_PROGRESS_INDEX;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.WIDGETS_PAGE_PROGRESS_INDEX;
import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_ORDINAL;
import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL;
import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.launcher3.testing.shared.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import static com.android.wm.shell.Flags.enableBubbleAnything;
import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.media.permission.SafeCloseable;
import android.os.Build;
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.os.SystemProperties;
import android.os.Trace;
import android.util.AttributeSet;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.View;
import android.widget.AnalogClock;
import android.widget.TextClock;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
import android.window.OnBackInvokedDispatcher;
import android.window.RemoteTransition;
import android.window.SplashScreen;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.app.viewcapture.ViewCaptureFactory;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepAccessibilityDelegate;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.proxy.ProxyActivityStarter;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.taskbar.TaskbarManager;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.uioverrides.QuickstepWidgetHolder.QuickstepHolderFactory;
import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.ObjectWrapper;
import com.android.launcher3.util.PendingRequestArgs;
import com.android.launcher3.util.PendingSplitSelectInfo;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
import com.android.launcher3.util.StartActivityParams;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.widget.LauncherWidgetHolder;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TouchInteractionService.TISBinder;
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.AsyncClockEventDelegate;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LauncherUnfoldAnimationController;
import com.android.quickstep.util.QuickstepOnboardingPrefs;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.SplitToWorkspaceController;
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.util.unfold.LauncherUnfoldTransitionController;
import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.unfold.RemoteUnfoldSharedComponent;
import com.android.systemui.unfold.UnfoldTransitionFactory;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import com.android.systemui.unfold.dagger.UnfoldMain;
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
import com.android.systemui.unfold.updates.RotationChangeProvider;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import kotlin.Unit;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
SystemShortcut.BubbleActivityStarter {
private static final boolean TRACE_LAYOUTS =
SystemProperties.getBoolean("persist.debug.trace_layouts", false);
private static final String TRACE_RELAYOUT_CLASS =
SystemProperties.get("persist.debug.trace_request_layout_class", null);
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
protected static final String RING_APPEAR_ANIMATION_PREFIX = "RingAppearAnimation\t";
private FixedContainerItems mAllAppsPredictions;
private HotseatPredictionController mHotseatPredictionController;
private DepthController mDepthController;
private QuickstepTransitionManager mAppTransitionManager;
private OverviewActionsView<?> mActionsView;
private TISBindHelper mTISBindHelper;
private @Nullable LauncherTaskbarUIController mTaskbarUIController;
// Will be updated when dragging from taskbar.
private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
private SplitSelectStateController mSplitSelectStateController;
private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
private SplitToWorkspaceController mSplitToWorkspaceController;
private BubbleBarLocation mBubbleBarLocation;
/**
* If Launcher restarted while in the middle of an Overview split select, it needs this data to
* recover. In all other cases this will remain null.
*/
private PendingSplitSelectInfo mPendingSplitSelectInfo = null;
@Nullable
private DesktopRecentsTransitionController mDesktopRecentsTransitionController;
private SafeCloseable mViewCapture;
private boolean mEnableWidgetDepth;
private boolean mIsPredictiveBackToHomeInProgress;
private boolean mCanShowAllAppsEducationView;
private boolean mIsOverlayVisible;
public static QuickstepLauncher getLauncher(Context context) {
return fromContext(context);
}
@Override
protected void setupViews() {
super.setupViews();
mActionsView = findViewById(R.id.overview_actions_view);
RecentsView<?,?> overviewPanel = getOverviewPanel();
SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
mSplitSelectStateController =
new SplitSelectStateController(this, getStateManager(),
getDepthController(), getStatsLogManager(),
systemUiProxy, RecentsModel.INSTANCE.get(this),
() -> onStateBack());
RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(asContext());
// TODO(b/337863494): Explore use of the same OverviewComponentObserver across launcher
OverviewComponentObserver overviewComponentObserver = new OverviewComponentObserver(
asContext(), deviceState);
if (DesktopModeStatus.canEnterDesktopMode(this)) {
mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
getStateManager(), systemUiProxy, getIApplicationThread(),
getDepthController());
}
overviewPanel.init(mActionsView, mSplitSelectStateController,
mDesktopRecentsTransitionController);
mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
mSplitSelectStateController, overviewComponentObserver, deviceState);
mSplitToWorkspaceController = new SplitToWorkspaceController(this,
mSplitSelectStateController);
mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
mAppTransitionManager = buildAppTransitionManager();
mAppTransitionManager.registerRemoteAnimations();
mAppTransitionManager.registerRemoteTransitions();
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
mDepthController = new DepthController(this);
if (DesktopModeStatus.canEnterDesktopMode(this)) {
mSplitSelectStateController.initSplitFromDesktopController(this,
overviewComponentObserver);
}
mHotseatPredictionController = new HotseatPredictionController(this);
mEnableWidgetDepth = SystemProperties.getBoolean("ro.launcher.depth.widget", true);
getWorkspace().addOverlayCallback(progress ->
onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX));
addBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
}
@Override
public void logAppLaunch(StatsLogManager statsLogManager, ItemInfo info,
InstanceId instanceId) {
// If the app launch is from any of the surfaces in AllApps then add the InstanceId from
// LiveSearchManager to recreate the AllApps session on the server side.
if (mAllAppsSessionLogId != null && ALL_APPS.equals(
getStateManager().getCurrentStableState())) {
instanceId = mAllAppsSessionLogId;
}
StatsLogger logger = statsLogManager.logger().withItemInfo(info).withInstanceId(instanceId);
if (mAllAppsPredictions != null
&& (info.itemType == ITEM_TYPE_APPLICATION
|| info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) {
int count = mAllAppsPredictions.items.size();
for (int i = 0; i < count; i++) {
ItemInfo targetInfo = mAllAppsPredictions.items.get(i);
if (targetInfo.itemType == info.itemType
&& targetInfo.user.equals(info.user)
&& Objects.equals(targetInfo.getIntent(), info.getIntent())) {
logger.withRank(i);
break;
}
}
}
logger.log(LAUNCHER_APP_LAUNCH_TAP);
mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
}
@Override
protected void completeAddShortcut(Intent data, int container, int screenId, int cellX,
int cellY, PendingRequestArgs args) {
if (container == CONTAINER_HOTSEAT) {
mHotseatPredictionController.onDeferredDrop(cellX, cellY);
}
super.completeAddShortcut(data, container, screenId, cellX, cellY, args);
}
@Override
protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
return new QuickstepAccessibilityDelegate(this);
}
/**
* Returns Prediction controller for hybrid hotseat
*/
public HotseatPredictionController getHotseatPredictionController() {
return mHotseatPredictionController;
}
@Override
public void enableHotseatEdu(boolean enable) {
super.enableHotseatEdu(enable);
mHotseatPredictionController.enableHotseatEdu(enable);
}
/**
* Builds the {@link QuickstepTransitionManager} instance to use for managing transitions.
*/
protected QuickstepTransitionManager buildAppTransitionManager() {
return new QuickstepTransitionManager(this);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
onStateOrResumeChanging(false /* inTransition */);
}
@Override
public RunnableList startActivitySafely(View v, Intent intent, ItemInfo item) {
// Only pause is taskbar controller is not present until the transition (if it exists) ends
mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
PredictionRowView<?> predictionRowView =
getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class);
// Pause the prediction row updates until the transition (if it exists) ends.
predictionRowView.setPredictionUiUpdatePaused(true);
RunnableList result = super.startActivitySafely(v, intent, item);
if (result == null) {
mHotseatPredictionController.setPauseUIUpdate(false);
predictionRowView.setPredictionUiUpdatePaused(false);
} else {
result.add(() -> {
mHotseatPredictionController.setPauseUIUpdate(false);
predictionRowView.setPredictionUiUpdatePaused(false);
});
}
return result;
}
@Override
public void startBinding() {
super.startBinding();
mHotseatPredictionController.verifyUIUpdateNotPaused();
}
@Override
protected void onActivityFlagsChanged(int changeBits) {
if ((changeBits & ACTIVITY_STATE_STARTED) != 0) {
mDepthController.setActivityStarted(isStarted());
}
if ((changeBits & ACTIVITY_STATE_RESUMED) != 0 && mTaskbarUIController != null) {
mTaskbarUIController.onLauncherPausedOrResumed(isPaused());
}
super.onActivityFlagsChanged(changeBits);
if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED
| ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) {
onStateOrResumeChanging((getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0);
}
}
@Override
protected void showAllAppsFromIntent(boolean alreadyOnHome) {
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
super.showAllAppsFromIntent(alreadyOnHome);
}
protected void onItemClicked(View view) {
if (!mSplitToWorkspaceController.handleSecondAppSelectionForSplit(view)) {
QuickstepLauncher.super.getItemOnClickListener().onClick(view);
}
}
@Override
public View.OnClickListener getItemOnClickListener() {
return this::onItemClicked;
}
@Override
public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
// Order matters as it affects order of appearance in popup container
List<SystemShortcut.Factory> shortcuts = new ArrayList(Arrays.asList(
APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController));
shortcuts.addAll(getSplitShortcuts());
shortcuts.add(WIDGETS);
shortcuts.add(INSTALL);
if (Flags.enablePrivateSpaceInstallShortcut()) {
shortcuts.add(PRIVATE_PROFILE_INSTALL);
}
if (Flags.enableShortcutDontSuggestApp()) {
shortcuts.add(DONT_SUGGEST_APP);
}
if (Flags.enablePrivateSpace()) {
shortcuts.add(UNINSTALL_APP);
}
if (enableBubbleAnything()) {
shortcuts.add(BUBBLE_SHORTCUT);
}
return shortcuts.stream();
}
private List<SystemShortcut.Factory<QuickstepLauncher>> getSplitShortcuts() {
if (!mDeviceProfile.isTablet || mSplitSelectStateController.isSplitSelectActive()) {
return Collections.emptyList();
}
RecentsView recentsView = getOverviewPanel();
// TODO(b/266482558): Pull it out of PagedOrentationHandler for split from workspace.
List<SplitPositionOption> positions =
recentsView.getPagedOrientationHandler().getSplitPositionOptions(
mDeviceProfile);
List<SystemShortcut.Factory<QuickstepLauncher>> splitShortcuts = new ArrayList<>();
for (SplitPositionOption position : positions) {
splitShortcuts.add(getSplitSelectShortcutByPosition(position));
}
return splitShortcuts;
}
/**
* Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
*/
private void onStateOrResumeChanging(boolean inTransition) {
LauncherState state = getStateManager().getState();
boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
if (started) {
DeviceProfile profile = getDeviceProfile();
boolean visible = (state == NORMAL || state == OVERVIEW)
&& isUserActive()
&& !profile.isVerticalBarLayout()
&& !mIsOverlayVisible;
SystemUiProxy.INSTANCE.get(this)
.setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
}
if (state == NORMAL && !inTransition) {
((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
}
}
@Override
public void onOverlayVisibilityChanged(boolean visible) {
super.onOverlayVisibilityChanged(visible);
mIsOverlayVisible = visible;
}
@Override
public void bindExtraContainerItems(FixedContainerItems item) {
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mAllAppsPredictions = item;
PredictionRowView<?> predictionRowView =
getAppsView().getFloatingHeaderView().findFixedRowByType(
PredictionRowView.class);
predictionRowView.setPredictedApps(item.items);
} else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
mHotseatPredictionController.setPredictedItems(item);
} else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
getWidgetPickerDataProvider().setWidgetRecommendations(item.items);
}
}
@Override
public void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) {
super.bindWorkspaceComponentsRemoved(matcher);
mHotseatPredictionController.onModelItemsRemoved(matcher);
}
@Override
public void onDestroy() {
if (mAppTransitionManager != null) {
mAppTransitionManager.onActivityDestroyed();
}
mAppTransitionManager = null;
mIsPredictiveBackToHomeInProgress = false;
if (mUnfoldTransitionProgressProvider != null) {
SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null);
mUnfoldTransitionProgressProvider.destroy();
}
mTISBindHelper.onDestroy();
if (mLauncherUnfoldAnimationController != null) {
mLauncherUnfoldAnimationController.onDestroy();
}
if (mSplitSelectStateController != null) {
mSplitSelectStateController.onDestroy();
}
super.onDestroy();
mHotseatPredictionController.destroy();
mSplitWithKeyboardShortcutController.onDestroy();
if (mViewCapture != null) mViewCapture.close();
removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
}
@Override
public void onStateSetEnd(LauncherState state) {
super.onStateSetEnd(state);
handlePendingActivityRequest();
switch (state.ordinal) {
case HINT_STATE_ORDINAL: {
Workspace<?> workspace = getWorkspace();
getStateManager().goToState(NORMAL);
if (workspace.getNextPage() != Workspace.DEFAULT_PAGE) {
workspace.post(workspace::moveToDefaultScreen);
}
break;
}
case HINT_STATE_TWO_BUTTON_ORDINAL: {
getStateManager().goToState(OVERVIEW);
getDragLayer().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
break;
}
case OVERVIEW_STATE_ORDINAL: {
RecentsView rv = getOverviewPanel();
sendCustomAccessibilityEvent(
rv.getPageAt(rv.getCurrentPage()), TYPE_VIEW_FOCUSED, null);
break;
}
case QUICK_SWITCH_STATE_ORDINAL: {
RecentsView rv = getOverviewPanel();
TaskView currentPageTask = rv.getCurrentPageTaskView();
TaskView fallbackTask = rv.getTaskViewAt(0);
if (currentPageTask != null || fallbackTask != null) {
TaskView taskToLaunch = currentPageTask;
if (currentPageTask == null) {
taskToLaunch = fallbackTask;
ActiveGestureProtoLogProxy.logQuickSwitchFromHomeFallback(
rv.getCurrentPage());
}
taskToLaunch.launchWithoutAnimation(success -> {
if (!success) {
getStateManager().goToState(OVERVIEW);
} else {
getStateManager().moveToRestState();
}
return Unit.INSTANCE;
});
} else {
ActiveGestureProtoLogProxy.logQuickSwitchFromHomeFailed(rv.getCurrentPage());
getStateManager().goToState(NORMAL);
}
break;
}
}
}
@Override
public TouchController[] createTouchControllers() {
NavigationMode mode = DisplayController.getNavigationMode(this);
ArrayList<TouchController> list = new ArrayList<>();
list.add(getDragController());
BiConsumer<AnimatorSet, Long> splitAnimator = (animatorSet, duration) ->
animatorSet.play(mSplitSelectStateController.getSplitAnimationController()
.createPlaceholderDismissAnim(this, LAUNCHER_SPLIT_SELECTION_EXIT_HOME,
duration));
switch (mode) {
case NO_BUTTON:
list.add(new NoButtonQuickSwitchTouchController(this));
list.add(new NavBarToHomeTouchController(this, splitAnimator));
list.add(new NoButtonNavbarToOverviewTouchController(this, splitAnimator));
break;
case TWO_BUTTONS:
list.add(new TwoButtonNavbarTouchController(this));
list.add(getDeviceProfile().isVerticalBarLayout()
? new TransposedQuickSwitchTouchController(this)
: new QuickSwitchTouchController(this));
list.add(new PortraitStatesTouchController(this));
break;
case THREE_BUTTONS:
list.add(new NoButtonQuickSwitchTouchController(this));
list.add(new NavBarToHomeTouchController(this, splitAnimator));
list.add(new NoButtonNavbarToOverviewTouchController(this, splitAnimator));
list.add(new PortraitStatesTouchController(this));
break;
default:
list.add(new PortraitStatesTouchController(this));
break;
}
if (!getDeviceProfile().isMultiWindowMode) {
list.add(new StatusBarTouchController(this));
}
list.add(new LauncherTaskViewController(this));
return list.toArray(new TouchController[list.size()]);
}
@Override
public AtomicAnimationFactory createAtomicAnimationFactory() {
return new QuickstepAtomicAnimationFactory(this);
}
@Override
protected LauncherWidgetHolder createAppWidgetHolder() {
final QuickstepHolderFactory factory =
(QuickstepHolderFactory) LauncherWidgetHolder.HolderFactory.newFactory(this);
return factory.newInstance(this,
appWidgetId -> getWorkspace().removeWidget(appWidgetId),
new QuickstepInteractionHandler(this));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mPendingSplitSelectInfo = ObjectWrapper.unwrap(
savedInstanceState.getIBinder(PENDING_SPLIT_SELECT_INFO));
}
addMultiWindowModeChangedListener(mDepthController);
initUnfoldTransitionProgressProvider();
mViewCapture = ViewCaptureFactory.getInstance(this).startCapture(getWindow());
getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
QuickstepOnboardingPrefs.setup(this);
View.setTraceLayoutSteps(TRACE_LAYOUTS);
View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
}
@Override
protected boolean initDeviceProfile(InvariantDeviceProfile idp) {
final boolean ret = super.initDeviceProfile(idp);
mDeviceProfile.isPredictiveBackSwipe =
getApplicationInfo().isOnBackInvokedCallbackEnabled();
return ret;
}
@Override
public void startSplitSelection(SplitSelectSource splitSelectSource) {
RecentsView recentsView = getOverviewPanel();
// Check if there is already an instance of this app running, if so, initiate the split
// using that.
mSplitSelectStateController.findLastActiveTasksAndRunCallback(
Collections.singletonList(splitSelectSource.getItemInfo().getComponentKey()),
false /* findExactPairMatch */,
foundTasks -> {
@Nullable Task foundTask = foundTasks[0];
boolean taskWasFound = foundTask != null;
splitSelectSource.alreadyRunningTaskId = taskWasFound
? foundTask.key.id
: INVALID_TASK_ID;
if (enableSplitContextually()) {
startSplitToHome(splitSelectSource);
} else {
recentsView.initiateSplitSelect(splitSelectSource);
}
}
);
}
/** TODO(b/266482558) Migrate into SplitSelectStateController or someplace split specific. */
private void startSplitToHome(SplitSelectSource source) {
AbstractFloatingView.closeAllOpenViews(this);
int splitPlaceholderSize = getResources().getDimensionPixelSize(
R.dimen.split_placeholder_size);
int splitPlaceholderInset = getResources().getDimensionPixelSize(
R.dimen.split_placeholder_inset);
Rect tempRect = new Rect();
mSplitSelectStateController.setInitialTaskSelect(source.intent,
source.position.stagePosition, source.getItemInfo(), source.splitEvent,
source.alreadyRunningTaskId);
RecentsView recentsView = getOverviewPanel();
recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds(
splitPlaceholderSize, splitPlaceholderInset, getDeviceProfile(),
mSplitSelectStateController.getActiveSplitStagePosition(), tempRect);
PendingAnimation anim = new PendingAnimation(TABLET_HOME_TO_SPLIT.getDuration());
RectF startingTaskRect = new RectF();
final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView(this,
source.getView(), null /* thumbnail */, source.getDrawable(), startingTaskRect);
floatingTaskView.setAlpha(1);
floatingTaskView.addStagingAnimation(anim, startingTaskRect, tempRect,
false /* fadeWithThumbnail */, true /* isStagedTask */);
floatingTaskView.setOnClickListener(view ->
mSplitSelectStateController.getSplitAnimationController().
playAnimPlaceholderToFullscreen(this, view, Optional.empty()));
floatingTaskView.setContentDescription(source.getItemInfo().contentDescription);
mSplitSelectStateController.setFirstFloatingTaskView(floatingTaskView);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
getDragLayer().removeView(floatingTaskView);
mSplitSelectStateController.getSplitAnimationController()
.removeSplitInstructionsView(QuickstepLauncher.this);
mSplitSelectStateController.resetState();
}
});
anim.add(mSplitSelectStateController.getSplitAnimationController()
.getShowSplitInstructionsAnim(this).buildAnim());
anim.buildAnim().start();
}
@Override
public boolean isSplitSelectionActive() {
if (mSplitSelectStateController == null) {
return false;
}
return mSplitSelectStateController.isSplitSelectActive();
}
public boolean areBothSplitAppsConfirmed() {
return mSplitSelectStateController.isBothSplitAppsConfirmed();
}
@Override
public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
if (mTaskbarUIController != null) {
mTaskbarUIController.onStateTransitionCompletedAfterSwipeToHome(finalState);
}
}
@Override
protected void onResume() {
super.onResume();
if (mLauncherUnfoldAnimationController != null) {
mLauncherUnfoldAnimationController.onResume();
}
}
@Override
protected void onPause() {
if (mLauncherUnfoldAnimationController != null) {
mLauncherUnfoldAnimationController.onPause();
}
super.onPause();
if (enableSplitContextually()) {
// If Launcher pauses before both split apps are selected, exit split screen.
if (!mSplitSelectStateController.isBothSplitAppsConfirmed() &&
!mSplitSelectStateController.isLaunchingFirstAppFullscreen()) {
mSplitSelectStateController
.logExitReason(LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
mSplitSelectStateController.getSplitAnimationController()
.playPlaceholderDismissAnim(this, LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
}
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
if (overviewCommandHelper != null) {
overviewCommandHelper.clearPendingCommands();
}
}
public QuickstepTransitionManager getAppTransitionManager() {
return mAppTransitionManager;
}
@Override
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
// After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
// as a part of quickstep, so that high-res thumbnails can load the next time we enter
// overview
RecentsModel.INSTANCE.get(this).getThumbnailCache()
.getHighResLoadingState().setVisible(true);
}
@Override
protected void handleGestureContract(Intent intent) {
if (FeatureFlags.SEPARATE_RECENTS_ACTIVITY.get()) {
super.handleGestureContract(intent);
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
RecentsModel.INSTANCE.get(this).onTrimMemory(level);
}
@Override
public void onUiChangedWhileSleeping() {
// Remove the snapshot because the content view may have obvious changes.
UI_HELPER_EXECUTOR.execute(
() -> ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this));
}
@Override
public void onAllAppsTransition(float progress) {
super.onAllAppsTransition(progress);
onTaskbarInAppDisplayProgressUpdate(progress, ALL_APPS_PAGE_PROGRESS_INDEX);
}
@Override
public void onWidgetsTransition(float progress) {
super.onWidgetsTransition(progress);
onTaskbarInAppDisplayProgressUpdate(progress, WIDGETS_PAGE_PROGRESS_INDEX);
if (mEnableWidgetDepth) {
getDepthController().widgetDepth.setValue(Utilities.mapToRange(
progress, 0f, 1f, 0f, getDeviceProfile().bottomSheetDepth, EMPHASIZED));
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return tryHandleBackKey(event) || super.dispatchKeyEvent(event);
}
// TODO (b/267248420) Once the recents input consumer has been removed, there is no need to
// handle the back key specially.
private boolean tryHandleBackKey(KeyEvent event) {
// Unlike normal activity, recents can receive input event from InputConsumer, so the input
// event won't go through ViewRootImpl#InputStage#onProcess.
// So when receive back key, try to do the same check thing in
// ViewRootImpl#NativePreImeInputStage#onProcess
if (event.getKeyCode() != KeyEvent.KEYCODE_BACK
|| event.getAction() != KeyEvent.ACTION_UP || event.isCanceled()) {
return false;
}
getOnBackAnimationCallback().onBackInvoked();
return true;
}
@Override
protected void registerBackDispatcher() {
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
new OnBackAnimationCallback() {
@Nullable OnBackAnimationCallback mActiveOnBackAnimationCallback;
@Override
public void onBackStarted(@NonNull BackEvent backEvent) {
if (mActiveOnBackAnimationCallback != null) {
mActiveOnBackAnimationCallback.onBackCancelled();
}
mActiveOnBackAnimationCallback = getOnBackAnimationCallback();
mActiveOnBackAnimationCallback.onBackStarted(backEvent);
}
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Override
public void onBackInvoked() {
// Recreate mActiveOnBackAnimationCallback if necessary to avoid NPE
// because:
// 1. b/260636433: In 3-button-navigation mode, onBackStarted() is not
// called on ACTION_DOWN before onBackInvoked() is called in ACTION_UP.
// 2. Launcher#onBackPressed() will call onBackInvoked() without calling
// onBackInvoked() beforehand.
if (mActiveOnBackAnimationCallback == null) {
mActiveOnBackAnimationCallback = getOnBackAnimationCallback();
}
mActiveOnBackAnimationCallback.onBackInvoked();
mActiveOnBackAnimationCallback = null;
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked");
}
@Override
public void onBackProgressed(@NonNull BackEvent backEvent) {
if (!FeatureFlags.IS_STUDIO_BUILD
&& mActiveOnBackAnimationCallback == null) {
return;
}
mActiveOnBackAnimationCallback.onBackProgressed(backEvent);
}
@Override
public void onBackCancelled() {
if (!FeatureFlags.IS_STUDIO_BUILD
&& mActiveOnBackAnimationCallback == null) {
return;
}
mActiveOnBackAnimationCallback.onBackCancelled();
mActiveOnBackAnimationCallback = null;
}
});
}
private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) {
TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
if (taskbarManager == null
|| taskbarManager.getCurrentActivityContext() == null
|| mTaskbarUIController == null) {
return;
}
mTaskbarUIController.onTaskbarInAppDisplayProgressUpdate(progress, flag);
}
@Override
public void startIntentSenderForResult(IntentSender intent, int requestCode,
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
if (requestCode != -1) {
mPendingActivityRequestCode = requestCode;
StartActivityParams params = new StartActivityParams(this, requestCode);
params.intentSender = intent;
params.fillInIntent = fillInIntent;
params.flagsMask = flagsMask;
params.flagsValues = flagsValues;
params.extraFlags = extraFlags;
params.options = options;
startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
} else {
super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
flagsValues, extraFlags, options);
}
}
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
if (requestCode != -1) {
mPendingActivityRequestCode = requestCode;
StartActivityParams params = new StartActivityParams(this, requestCode);
params.intent = intent;
params.options = options;
startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
} else {
super.startActivityForResult(intent, requestCode, options);
}
}
@Override
public void setResumed() {
DesktopVisibilityController desktopVisibilityController = getDesktopVisibilityController();
if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
&& desktopVisibilityController != null
&& desktopVisibilityController.areDesktopTasksVisible()
&& !desktopVisibilityController.isRecentsGestureInProgress()) {
// Return early to skip setting activity to appear as resumed
// TODO: b/333533253 - Remove after flag rollout
return;
}
super.setResumed();
}
@Override
protected void onDeferredResumed() {
super.onDeferredResumed();
handlePendingActivityRequest();
}
private void handlePendingActivityRequest() {
if (mPendingActivityRequestCode != -1 && isInState(NORMAL)
&& ((getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
// Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher.
onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null);
// ProxyActivityStarter is started with clear task to reset the task after which it
// removes the task itself.
startActivity(ProxyActivityStarter.getLaunchIntent(this, null));
}
}
private void onTISConnected(TISBinder binder) {
TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
if (taskbarManager != null) {
taskbarManager.setActivity(this);
}
mTISBindHelper.setPredictiveBackToHomeInProgress(mIsPredictiveBackToHomeInProgress);
}
@Override
public void runOnBindToTouchInteractionService(Runnable r) {
mTISBindHelper.runOnBindToTouchInteractionService(r);
}
private void initUnfoldTransitionProgressProvider() {
if (!enableUnfoldStateAnimation()) {
final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
if (config.isEnabled()) {
initRemotelyCalculatedUnfoldAnimation(config);
}
} else {
ProxyUnfoldTransitionProvider provider =
SystemUiProxy.INSTANCE.get(this).getUnfoldTransitionProvider();
if (provider != null) {
new LauncherUnfoldTransitionController(this, provider);
}
}
}
/** Receives animation progress from sysui process. */
private void initRemotelyCalculatedUnfoldAnimation(UnfoldTransitionConfig config) {
RemoteUnfoldSharedComponent unfoldComponent =
UnfoldTransitionFactory.createRemoteUnfoldSharedComponent(
/* context= */ this,
config,
getMainExecutor(),
getMainThreadHandler(),
/* backgroundExecutor= */ UI_HELPER_EXECUTOR,
/* bgHandler= */ UI_HELPER_EXECUTOR.getHandler(),
/* tracingTagPrefix= */ "launcher",
getSystemService(DisplayManager.class)
);
final RemoteUnfoldTransitionReceiver remoteUnfoldTransitionProgressProvider =
unfoldComponent.getRemoteTransitionProgress().orElseThrow(
() -> new IllegalStateException(
"Trying to create getRemoteTransitionProgress when the transition "
+ "is disabled"));
mUnfoldTransitionProgressProvider = remoteUnfoldTransitionProgressProvider;
SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(
remoteUnfoldTransitionProgressProvider);
initUnfoldAnimationController(mUnfoldTransitionProgressProvider,
unfoldComponent.getRotationChangeProvider());
}
private void initUnfoldAnimationController(UnfoldTransitionProgressProvider progressProvider,
@UnfoldMain RotationChangeProvider rotationChangeProvider) {
mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
/* launcher= */ this,
getWindowManager(),
progressProvider,
rotationChangeProvider
);
}
public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
mTaskbarUIController = taskbarUIController;
}
public @Nullable LauncherTaskbarUIController getTaskbarUIController() {
return mTaskbarUIController;
}
/** Provides the translation X for the hotseat item. */
public int getHotseatItemTranslationX(ItemInfo itemInfo) {
int translationX = 0;
if (isBubbleBarEnabled()
&& enableBubbleBarInPersistentTaskBar()
&& mBubbleBarLocation != null) {
boolean isRtl = isRtl(getResources());
boolean isBubblesOnLeft = mBubbleBarLocation.isOnLeft(isRtl);
translationX += mDeviceProfile
.getHotseatTranslationXForBubbleBar(isBubblesOnLeft, isRtl);
}
if (isBubbleBarEnabled() && hasBubbles()) {
// TODO(368379159) : create a class to reuse computation logic
float adjustedBorderSpace =
mDeviceProfile.getHotseatAdjustedBorderSpaceForBubbleBar(this);
if (Float.compare(adjustedBorderSpace, 0f) != 0) {
float borderSpaceDelta = adjustedBorderSpace - mDeviceProfile.hotseatBorderSpace;
translationX +=
(int) (mDeviceProfile.iconSizePx + itemInfo.cellX * borderSpaceDelta);
}
}
return translationX;
}
public SplitToWorkspaceController getSplitToWorkspaceController() {
return mSplitToWorkspaceController;
}
@Override
protected void handleSplitAnimationGoingToHome(StatsLogManager.EventEnum splitDismissReason) {
super.handleSplitAnimationGoingToHome(splitDismissReason);
mSplitSelectStateController.getSplitAnimationController()
.playPlaceholderDismissAnim(this, splitDismissReason);
}
@Override
public void dismissSplitSelection(StatsLogManager.LauncherEvent splitDismissEvent) {
super.dismissSplitSelection(splitDismissEvent);
mSplitSelectStateController.getSplitAnimationController()
.playPlaceholderDismissAnim(this, splitDismissEvent);
}
@Override
public OverviewActionsView<?> getActionsView() {
return mActionsView;
}
@Override
protected void closeOpenViews(boolean animate) {
super.closeOpenViews(animate);
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
}
@Override
public void collectStateHandlers(List<StateHandler<LauncherState>> out) {
super.collectStateHandlers(out);
out.add(getDepthController());
out.add(new RecentsViewStateController(this));
}
public DepthController getDepthController() {
return mDepthController;
}
@Nullable
@Override
public DesktopVisibilityController getDesktopVisibilityController() {
return mTISBindHelper.getDesktopVisibilityController();
}
@Nullable
public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() {
return mUnfoldTransitionProgressProvider;
}
@Override
public boolean supportsAdaptiveIconAnimation(View clickedView) {
return true;
}
@Override
public float[] getNormalOverviewScaleAndOffset() {
return DisplayController.getNavigationMode(this).hasGestures
? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET};
}
@Override
public void finishBindingItems(IntSet pagesBoundFirst) {
super.finishBindingItems(pagesBoundFirst);
// Instantiate and initialize WellbeingModel now that its loading won't interfere with
// populating workspace.
// TODO: Find a better place for this
WellbeingModel.INSTANCE.get(this);
if (mLauncherUnfoldAnimationController != null) {
// This is needed in case items are rebound while the unfold animation is in progress.
mLauncherUnfoldAnimationController.updateRegisteredViewsIfNeeded();
}
}
@Override
public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
ActivityOptionsWrapper activityOptions = mAppTransitionManager.getActivityLaunchOptions(
v, item != null ? item : (ItemInfo) v.getTag());
if (mLastTouchUpTime > 0) {
activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
mLastTouchUpTime);
}
if (item != null && (item.animationType == DEFAULT_NO_ICON
|| item.animationType == VIEW_BACKGROUND)) {
activityOptions.options.setSplashScreenStyle(
SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR);
} else {
activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
}
activityOptions.options.setLaunchDisplayId(
(v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
: Display.DEFAULT_DISPLAY);
activityOptions.options.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
return activityOptions;
}
@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
@BinderThread
public void enterStageSplitFromRunningApp(boolean leftOrTop) {
mSplitWithKeyboardShortcutController.enterStageSplit(leftOrTop);
}
@Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
super.onDisplayInfoChanged(context, info, flags);
// When changing screens, force moving to rest state similar to StatefulActivity.onStop, as
// StatefulActivity isn't called consistently.
if ((flags & CHANGE_ACTIVE_SCREEN) != 0) {
// Do not animate moving to rest state, as it can clash with Launcher#onIdpChanged
// where reapplyUi calls StateManager's reapplyState during the state change animation,
// and cancel the state change unexpectedly. The screen will be off during screen
// transition, hiding the unanimated transition.
getStateManager().moveToRestState(/* isAnimated = */false);
}
if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
getDragLayer().recreateControllers();
if (mActionsView != null) {
mActionsView.updateVerticalMargin(info.getNavigationMode());
}
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// If Launcher shuts downs during split select, we save some extra data in the recovery
// bundle to allow graceful recovery. The normal LauncherState restore mechanism doesn't
// work in this case because restoring straight to OverviewSplitSelect without staging data,
// or before the tasks themselves have loaded into Overview, causes a crash. So we tell
// Launcher to first restore into Overview state, wait for the relevant tasks and icons to
// load in, and then proceed to OverviewSplitSelect.
if (isInState(OVERVIEW_SPLIT_SELECT)) {
// Launcher will restart in Overview and then transition to OverviewSplitSelect.
outState.putIBinder(PENDING_SPLIT_SELECT_INFO, ObjectWrapper.wrap(
new PendingSplitSelectInfo(
mSplitSelectStateController.getInitialTaskId(),
mSplitSelectStateController.getActiveSplitStagePosition(),
mSplitSelectStateController.getSplitEvent())
));
outState.putInt(RUNTIME_STATE, OVERVIEW.ordinal);
}
}
/**
* When Launcher restarts, it sometimes needs to recover to a split selection state.
* This function checks if such a recovery is needed.
* @return a boolean representing whether the launcher is waiting to recover to
* OverviewSplitSelect state.
*/
public boolean hasPendingSplitSelectInfo() {
return mPendingSplitSelectInfo != null;
}
/**
* See {@link #hasPendingSplitSelectInfo()}
*/
public @Nullable PendingSplitSelectInfo getPendingSplitSelectInfo() {
return mPendingSplitSelectInfo;
}
/**
* When the launcher has successfully recovered to OverviewSplitSelect state, this function
* deletes the recovery data, returning it to a null state.
*/
public void finishSplitSelectRecovery() {
mPendingSplitSelectInfo = null;
}
/**
* Sets flag whether a predictive back-to-home animation is in progress
*/
public void setPredictiveBackToHomeInProgress(boolean isInProgress) {
mIsPredictiveBackToHomeInProgress = isInProgress;
mTISBindHelper.setPredictiveBackToHomeInProgress(isInProgress);
}
public boolean getPredictiveBackToHomeInProgress() {
return mIsPredictiveBackToHomeInProgress;
}
@Override
public boolean areDesktopTasksVisible() {
DesktopVisibilityController desktopVisibilityController = getDesktopVisibilityController();
if (desktopVisibilityController != null) {
return desktopVisibilityController.areDesktopTasksVisible();
}
return false;
}
@Override
protected void onDeviceProfileInitiated() {
super.onDeviceProfileInitiated();
SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
}
@Override
public void dispatchDeviceProfileChanged() {
super.dispatchDeviceProfileChanged();
Trace.instantForTrack(TRACE_TAG_APP, "QuickstepLauncher#DeviceProfileChanged",
getDeviceProfile().toSmallString());
SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
if (taskbarManager != null) {
taskbarManager.debugWhyTaskbarNotDestroyed("QuickstepLauncher#onDeviceProfileChanged");
}
}
/**
* Launches the given {@link GroupTask} in splitscreen.
*/
public void launchSplitTasks(
@NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
// SplitBounds can be null if coming from Taskbar launch.
final boolean firstTaskIsLeftTopTask = isFirstTaskLeftTopTask(groupTask);
// task2 should never be null when calling this method. Allow a crash to catch invalid calls
Task task1 = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
Task task2 = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
mSplitSelectStateController.launchExistingSplitPair(
null /* launchingTaskView */,
task1.key.id,
task2.key.id,
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
/* callback= */ success -> mSplitSelectStateController.resetState(),
/* freezeTaskList= */ false,
groupTask.mSplitBounds == null
? SNAP_TO_2_50_50
: groupTask.mSplitBounds.snapPosition,
remoteTransition);
}
private static boolean isFirstTaskLeftTopTask(@NonNull GroupTask groupTask) {
return groupTask.mSplitBounds == null
|| groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
}
/**
* Launches two apps as an app pair.
*/
public void launchAppPair(AppPairIcon appPairIcon) {
// Potentially show the Taskbar education once the app pair launch finishes
mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon,
CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE,
(success) -> {
if (success && mTaskbarUIController != null) {
mTaskbarUIController.showEduOnAppLaunch();
}
});
}
public boolean canStartHomeSafely() {
OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
}
@Override
public boolean isBubbleBarEnabled() {
return (mTaskbarUIController != null && mTaskbarUIController.isBubbleBarEnabled());
}
@Override
public boolean hasBubbles() {
return (mTaskbarUIController != null && mTaskbarUIController.hasBubbles());
}
@NonNull
public TISBindHelper getTISBindHelper() {
return mTISBindHelper;
}
@Override
public boolean handleIncorrectSplitTargetSelection() {
if (!enableSplitContextually() || !mSplitSelectStateController.isSplitSelectActive()) {
return false;
}
mSplitSelectStateController.getSplitInstructionsView().goBoing();
return true;
}
@Override
public void showShortcutBubble(ShortcutInfo info) {
if (info == null) return;
SystemUiProxy.INSTANCE.get(this).showShortcutBubble(info);
}
@Override
public void showAppBubble(Intent intent) {
if (intent == null || intent.getPackage() == null) return;
SystemUiProxy.INSTANCE.get(this).showAppBubble(intent);
}
/** Sets the location of the bubble bar */
public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
mBubbleBarLocation = bubbleBarLocation;
}
private static final class LauncherTaskViewController extends
TaskViewTouchController<QuickstepLauncher> {
LauncherTaskViewController(QuickstepLauncher activity) {
super(activity);
}
@Override
protected boolean isRecentsInteractive() {
return mContainer.isInState(OVERVIEW) || mContainer.isInState(OVERVIEW_MODAL_TASK);
}
@Override
protected boolean isRecentsModal() {
return mContainer.isInState(OVERVIEW_MODAL_TASK);
}
@Override
protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
mContainer.getStateManager().setCurrentUserControlledAnimation(animController);
}
}
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(prefix, fd, writer, args);
if (mDepthController != null) {
mDepthController.dump(prefix, writer);
}
RecentsView recentsView = getOverviewPanel();
writer.println("\nQuickstepLauncher:");
writer.println(prefix + "\tmOrientationState: " + (recentsView == null ? "recentsNull" :
recentsView.getPagedViewOrientedState()));
if (recentsView != null) {
recentsView.getSplitSelectController().dump(prefix, writer);
}
if (mAppTransitionManager != null) {
mAppTransitionManager.dump(prefix + "\t" + RING_APPEAR_ANIMATION_PREFIX, writer);
}
if (mHotseatPredictionController != null) {
mHotseatPredictionController.dump(prefix, writer);
}
PredictionRowView<?> predictionRowView =
getAppsView().getFloatingHeaderView().findFixedRowByType(
PredictionRowView.class);
predictionRowView.dump(prefix, writer);
}
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
switch (name) {
case "TextClock", "android.widget.TextClock" -> {
TextClock tc = new TextClock(context, attrs);
tc.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
return tc;
}
case "AnalogClock", "android.widget.AnalogClock" -> {
AnalogClock ac = new AnalogClock(context, attrs);
ac.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
return ac;
}
}
return super.onCreateView(parent, name, context, attrs);
}
@Override
public boolean isRecentsViewVisible() {
return getStateManager().getState().isRecentsViewVisible;
}
public boolean isCanShowAllAppsEducationView() {
return mCanShowAllAppsEducationView;
}
public void setCanShowAllAppsEducationView(boolean canShowAllAppsEducationView) {
mCanShowAllAppsEducationView = canShowAllAppsEducationView;
}
}