Files
Lawnchair/quickstep/src/com/android/launcher3/taskbar/TaskbarManagerImpl.java
T
Sukesh Ram 90af684118 [CD Projected Mode] Make Taskbars for Detected displays on Boot when Connected
Make Taskbars for Detected displays on Boot when Connected.

Bug: 409820188
Flag: com.android.window.flags.enable_taskbar_connected_displays
Test: m
Change-Id: Iac04fe3c5ba8faac7383ed2953ddee99240ce016
2025-04-22 14:57:43 +00:00

1813 lines
80 KiB
Java

/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.taskbar;
import static android.content.Context.RECEIVER_EXPORTED;
import static android.content.Context.RECEIVER_NOT_EXPORTED;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.content.pm.PackageManager.FEATURE_PC;
import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
import static com.android.launcher3.Flags.enableGrowthNudge;
import static com.android.launcher3.Flags.enableTaskbarUiThread;
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
import static com.android.launcher3.taskbar.growth.GrowthConstants.BROADCAST_SHOW_NUDGE;
import static com.android.launcher3.taskbar.growth.GrowthConstants.GROWTH_NUDGE_PERMISSION;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import static com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_SHOW_LOCKED_TASKBAR;
import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR;
import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
import android.animation.AnimatorSet;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Trace;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.window.DesktopExperienceFlags;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.app.displaylib.PerDisplayRepository;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.AllAppsActionManager;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.SystemDecorationChangeObserver;
import com.android.quickstep.SystemDecorationChangeObserver.DisplayDecorationListener;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.fallback.window.RecentsWindowFlags;
import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.util.ContextualSearchInvoker;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.views.RecentsViewContainer;
import com.android.server.am.Flags;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
/**
* Class to manage taskbar lifecycle
*/
public class TaskbarManagerImpl implements DisplayDecorationListener {
private static final String TAG = "TaskbarManager";
private static final boolean DEBUG = false;
private static final int TASKBAR_DESTROY_DURATION = 100;
// TODO: b/397738606 - Remove all logs with this tag after the growth framework is integrated.
public static final String GROWTH_FRAMEWORK_TAG = "Growth Framework";
/**
* All the configurations which do not initiate taskbar recreation.
* This includes all the configurations defined in Launcher's manifest entry and
* ActivityController#filterConfigChanges
*/
private static final int SKIP_RECREATE_CONFIG_CHANGES = ActivityInfo.CONFIG_WINDOW_CONFIGURATION
| ActivityInfo.CONFIG_KEYBOARD
| ActivityInfo.CONFIG_KEYBOARD_HIDDEN
| ActivityInfo.CONFIG_MCC
| ActivityInfo.CONFIG_MNC
| ActivityInfo.CONFIG_NAVIGATION
| ActivityInfo.CONFIG_ORIENTATION
| ActivityInfo.CONFIG_SCREEN_SIZE
| ActivityInfo.CONFIG_SCREEN_LAYOUT
| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(
Settings.Secure.USER_SETUP_COMPLETE);
private static final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor(
Settings.Secure.NAV_BAR_KIDS_MODE);
private final Context mBaseContext;
private final int mPrimaryDisplayId;
private final TaskbarNavButtonCallbacks mNavCallbacks;
// TODO: Remove this during the connected displays lifecycle refactor.
private final Context mPrimaryWindowContext;
private final WindowManager mPrimaryWindowManager;
private final DisplayManager mDisplayManager;
private TaskbarNavButtonController mPrimaryNavButtonController;
private ComponentCallbacks mPrimaryComponentCallbacks;
private final SimpleBroadcastReceiver mShutdownReceiver;
private final LooperExecutor mPerWindowUiExecutor =
new LooperExecutor("TaskbarUiThread", THREAD_PRIORITY_FOREGROUND);
// The source for this provider is set when Launcher is available
// We use 'non-destroyable' version here so the original provider won't be destroyed
// as it is tied to the activity lifecycle, not the taskbar lifecycle.
// It's destruction/creation will be managed by the activity.
private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
new NonDestroyableScopedUnfoldTransitionProgressProvider();
/** DisplayId - {@link TaskbarActivityContext} map for Connected Display. */
private final Map<Integer, TaskbarActivityContext>
mTaskbars = enableTaskbarUiThread() ? new ConcurrentHashMap<>() : new HashMap<>();
/** DisplayId - {@link Context} map for Connected Display. */
private final SparseArray<Context> mWindowContexts = new SparseArray<>();
/** DisplayId - {@link FrameLayout} map for Connected Display. */
private final SparseArray<FrameLayout> mRootLayouts = new SparseArray<>();
/** DisplayId - {@link Boolean} map indicating if RootLayout was added to window. */
private final SparseBooleanArray mAddedRootLayouts = new SparseBooleanArray();
/** DisplayId - {@link TaskbarNavButtonController} map for Connected Display. */
private final SparseArray<TaskbarNavButtonController> mNavButtonControllers =
new SparseArray<>();
/** DisplayId - {@link ComponentCallbacks} map for Connected Display. */
private final SparseArray<ComponentCallbacks> mComponentCallbacks = new SparseArray<>();
/** DisplayId - {@link DeviceProfile} map for Connected Display. */
private final SparseArray<DeviceProfile> mExternalDeviceProfiles = new SparseArray<>();
private StatefulActivity mActivity;
private RecentsViewContainer mRecentsViewContainer;
/** Whether this device is a desktop android device **/
private boolean mIsAndroidPC;
/** Whether this device supports freeform windows management. Can change dynamically **/
private boolean mSupportsFreeformWindowsManagement;
/**
* Cache a copy here so we can initialize state whenever taskbar is recreated, since
* this class does not get re-initialized w/ new taskbars.
*/
private final TaskbarSharedState mSharedState = new TaskbarSharedState();
/**
* We use WindowManager's ComponentCallbacks() for internal UI changes (similar to an Activity)
* which comes via a different channel
*/
private final RecreationListener mRecreationListener = new RecreationListener();
// Currently, there is a duplicative call to recreate taskbars when user enter/exit Desktop
// Mode upon getting transition callback from shell side. So, we make sure that if taskbar is
// already in recreate process due to transition callback, don't recreate for
// DisplayInfoChangeListener.
private boolean mShouldIgnoreNextDesktopModeChangeFromDisplayController = false;
private class RecreationListener implements DisplayController.DisplayInfoChangeListener {
@Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
int displayId = context.getDisplayId();
if ((flags & CHANGE_DENSITY) != 0) {
debugTaskbarManager("onDisplayInfoChanged: Display density changed", displayId);
}
if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
debugTaskbarManager("onDisplayInfoChanged: Navigation mode changed", displayId);
}
if ((flags & CHANGE_DESKTOP_MODE) != 0) {
debugTaskbarManager("onDisplayInfoChanged: Desktop mode changed", displayId);
handleDisplayUpdatesForPerceptibleTasks();
}
if ((flags & CHANGE_TASKBAR_PINNING) != 0) {
debugTaskbarManager("onDisplayInfoChanged: Taskbar pinning changed", displayId);
}
if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE | CHANGE_DESKTOP_MODE
| CHANGE_TASKBAR_PINNING | CHANGE_SHOW_LOCKED_TASKBAR)) != 0) {
TaskbarActivityContext taskbarActivityContext = getCurrentActivityContext();
if ((flags & CHANGE_SHOW_LOCKED_TASKBAR) != 0) {
debugTaskbarManager("onDisplayInfoChanged: show locked taskbar changed!",
displayId);
recreateTaskbars();
} else if ((flags & CHANGE_DESKTOP_MODE) != 0) {
if (mShouldIgnoreNextDesktopModeChangeFromDisplayController) {
mShouldIgnoreNextDesktopModeChangeFromDisplayController = false;
return;
}
// Only Handles Special Exit Cases for Desktop Mode Taskbar Recreation.
if (((flags & CHANGE_TASKBAR_PINNING) != 0) || (taskbarActivityContext != null
&& !taskbarActivityContext.showLockedTaskbarOnHome()
&& !taskbarActivityContext.showDesktopTaskbarForFreeformDisplay())) {
recreateTaskbars();
}
} else {
recreateTaskbars();
}
}
}
}
private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> {
debugPrimaryTaskbar("Settings changed! Recreating Taskbar!");
recreateTaskbars();
};
private PerceptibleTaskListener mTaskStackListener;
private class PerceptibleTaskListener implements TaskStackChangeListener {
private ArraySet<Integer> mPerceptibleTasks = new ArraySet<Integer>();
@Override
public void onTaskMovedToFront(int taskId) {
// This listens to any Task, so we filter them by the ones shown in the launcher.
// For Tasks restored after startup, they will by default not be Perceptible, and no
// need to until user interacts with it by bringing it to the foreground.
for (Entry<Integer, TaskbarActivityContext> entry : mTaskbars.entrySet()) {
// get pinned tasks - we care about all tasks, not just the one moved to the front
Set<Integer> taskbarPinnedTasks =
entry.getValue().getControllers().taskbarViewController
.getTaskIdsForPinnedApps();
// filter out tasks already marked as perceptible
taskbarPinnedTasks.removeAll(mPerceptibleTasks);
// add the filtered tasks as perceptible
for (int pinnedTaskId : taskbarPinnedTasks) {
ActivityManagerWrapper.getInstance()
.setTaskIsPerceptible(pinnedTaskId, true);
mPerceptibleTasks.add(pinnedTaskId);
}
}
}
/**
* Launcher also can display recently launched tasks that are not pinned. Also add
* these as perceptible
*/
@Override
public void onRecentTaskListUpdated() {
for (Entry<Integer, TaskbarActivityContext> entry : mTaskbars.entrySet()) {
for (GroupTask gTask : entry.getValue().getControllers()
.taskbarRecentAppsController.getShownTasks()) {
for (Task task : gTask.getTasks()) {
int taskId = task.key.id;
if (!mPerceptibleTasks.contains(taskId)) {
ActivityManagerWrapper.getInstance()
.setTaskIsPerceptible(taskId, true);
mPerceptibleTasks.add(taskId);
}
}
}
}
}
@Override
public void onTaskRemoved(int taskId) {
mPerceptibleTasks.remove(taskId);
}
public void unregisterListener() {
for (Integer taskId : mPerceptibleTasks) {
ActivityManagerWrapper.getInstance().setTaskIsPerceptible(taskId, false);
}
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
mTaskStackListener);
}
}
private final DesktopVisibilityController.TaskbarDesktopModeListener
mTaskbarDesktopModeListener =
new DesktopVisibilityController.TaskbarDesktopModeListener() {
@Override
public void onExitDesktopMode(int duration) {
for (Entry<Integer, TaskbarActivityContext> entry : mTaskbars.entrySet()) {
int displayId = entry.getKey();
if (isExternalDisplay(displayId)) {
continue;
}
TaskbarActivityContext taskbarActivityContext = entry.getValue();
if (taskbarActivityContext != null
&& !taskbarActivityContext.isInOverview()
&& !taskbarActivityContext.showDesktopTaskbarForFreeformDisplay()) {
mShouldIgnoreNextDesktopModeChangeFromDisplayController = true;
AnimatorSet animatorSet = taskbarActivityContext.onDestroyAnimation(
TASKBAR_DESTROY_DURATION);
animatorSet.addListener(AnimatorListeners.forEndCallback(
() -> recreateTaskbarForDisplay(mPrimaryDisplayId, duration)));
animatorSet.start();
}
}
}
@Override
public void onEnterDesktopMode(int duration) {
for (Entry<Integer, TaskbarActivityContext> entry : mTaskbars.entrySet()) {
int displayId = entry.getKey();
if (isExternalDisplay(displayId)) {
continue;
}
TaskbarActivityContext taskbarActivityContext = entry.getValue();
if (taskbarActivityContext != null
&& !taskbarActivityContext.showDesktopTaskbarForFreeformDisplay()) {
mShouldIgnoreNextDesktopModeChangeFromDisplayController = true;
AnimatorSet animatorSet = taskbarActivityContext.onDestroyAnimation(
TASKBAR_DESTROY_DURATION);
animatorSet.addListener(AnimatorListeners.forEndCallback(
() -> recreateTaskbarForDisplay(mPrimaryDisplayId, duration)));
animatorSet.start();
}
}
}
@Override
public void onTaskbarCornerRoundingUpdate(
boolean doesAnyTaskRequireTaskbarRounding) {
//NO-OP
}
};
private boolean mUserUnlocked = false;
private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver;
private final SimpleBroadcastReceiver mGrowthBroadcastReceiver;
private final AllAppsActionManager mAllAppsActionManager;
private final PerDisplayRepository<RecentsWindowManager> mRecentsWindowManagerRepository;
private final Runnable mActivityOnDestroyCallback = new Runnable() {
@Override
public void run() {
int displayId = mPrimaryDisplayId;
debugTaskbarManager("onActivityDestroyed:", displayId);
if (mActivity != null) {
displayId = mActivity.getDisplayId();
mActivity.removeOnDeviceProfileChangeListener(
mDebugActivityDeviceProfileChanged);
debugTaskbarManager("onActivityDestroyed: unregistering callbacks", displayId);
mActivity.removeEventCallback(EVENT_DESTROYED, this);
}
if (mActivity == mRecentsViewContainer) {
mRecentsViewContainer = null;
}
mActivity = null;
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
debugTaskbarManager("onActivityDestroyed: setting taskbarUIController", displayId);
taskbar.setUIController(TaskbarUIController.DEFAULT);
} else {
debugTaskbarManager("onActivityDestroyed: taskbar is null!", displayId);
}
mUnfoldProgressProvider.setSourceProvider(null);
}
};
UnfoldTransitionProgressProvider.TransitionProgressListener mUnfoldTransitionProgressListener =
new UnfoldTransitionProgressProvider.TransitionProgressListener() {
@Override
public void onTransitionStarted() {
debugPrimaryTaskbar("fold/unfold transition started getting called.");
}
@Override
public void onTransitionProgress(float progress) {
debugPrimaryTaskbar(
"fold/unfold transition progress getting called. | progress="
+ progress);
}
@Override
public void onTransitionFinishing() {
debugPrimaryTaskbar(
"fold/unfold transition finishing getting called.");
}
@Override
public void onTransitionFinished() {
debugPrimaryTaskbar(
"fold/unfold transition finished getting called.");
}
};
@SuppressLint("WrongConstant")
public TaskbarManagerImpl(
Context context,
AllAppsActionManager allAppsActionManager,
TaskbarNavButtonCallbacks navCallbacks,
PerDisplayRepository<RecentsWindowManager> recentsWindowManagerRepository) {
mBaseContext = context;
mPrimaryDisplayId = mBaseContext.getDisplayId();
mAllAppsActionManager = allAppsActionManager;
mNavCallbacks = navCallbacks;
mRecentsWindowManagerRepository = recentsWindowManagerRepository;
// Set up primary display.
debugPrimaryTaskbar("TaskbarManager constructor");
mDisplayManager = mBaseContext.getSystemService(DisplayManager.class);
mPrimaryWindowContext = createWindowContext(mPrimaryDisplayId);
mPrimaryWindowManager = mPrimaryWindowContext.getSystemService(WindowManager.class);
DesktopVisibilityController.INSTANCE.get(
mPrimaryWindowContext).registerTaskbarDesktopModeListener(
mTaskbarDesktopModeListener);
createTaskbarRootLayout(mPrimaryDisplayId);
createNavButtonController(mPrimaryDisplayId);
createAndRegisterComponentCallbacks(mPrimaryDisplayId);
SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
SystemDecorationChangeObserver.getINSTANCE().get(mPrimaryWindowContext)
.registerDisplayDecorationListener(this);
mShutdownReceiver =
new SimpleBroadcastReceiver(
mPrimaryWindowContext, UI_HELPER_EXECUTOR, i -> destroyAllTaskbars());
mTaskbarBroadcastReceiver =
new SimpleBroadcastReceiver(mPrimaryWindowContext,
UI_HELPER_EXECUTOR, this::showTaskbarFromBroadcast);
mShutdownReceiver.register(Intent.ACTION_SHUTDOWN);
if (enableGrowthNudge()) {
// TODO: b/397739323 - Add permission to limit access to Growth Framework.
mGrowthBroadcastReceiver =
new SimpleBroadcastReceiver(
mPrimaryWindowContext, UI_HELPER_EXECUTOR, this::showGrowthNudge);
mGrowthBroadcastReceiver.register(null, GROWTH_NUDGE_PERMISSION, RECEIVER_EXPORTED,
BROADCAST_SHOW_NUDGE);
} else {
mGrowthBroadcastReceiver = null;
}
UI_HELPER_EXECUTOR.execute(() -> {
mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
mPrimaryWindowContext,
SYSTEM_ACTION_ID_TASKBAR,
new Intent(ACTION_SHOW_TASKBAR).setPackage(
mPrimaryWindowContext.getPackageName()),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mTaskbarBroadcastReceiver.register(RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
});
mIsAndroidPC = getPrimaryWindowContext().getPackageManager().hasSystemFeature(FEATURE_PC);
mSupportsFreeformWindowsManagement = getFreeformWindowsManagementInfo();
if (eligibleForPerceptibleTasks()) {
mTaskStackListener = new PerceptibleTaskListener();
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
} else {
mTaskStackListener = null;
}
addWindowContextToMap(mPrimaryDisplayId, mPrimaryWindowContext);
recreateTaskbarForDisplay(mPrimaryDisplayId, /* duration= */ 0);
// TODO b/408503553: Remove when WM is used instead of CommandQueue for system decorations.
addSystemDecorationForDisplaysAtBoot();
debugPrimaryTaskbar("TaskbarManager created");
}
/** Calls {@link #onDisplayAddSystemDecorations(int)} for all displays. */
private void addSystemDecorationForDisplaysAtBoot() {
if (mDisplayManager == null) {
return;
}
for (Display display : mDisplayManager.getDisplays()) {
onDisplayAddSystemDecorations(display.getDisplayId());
}
}
public LooperExecutor getPerWindowUiExecutor() {
return mPerWindowUiExecutor;
}
private void handleDisplayUpdatesForPerceptibleTasks() {
// 1. When desktop mode changes, detect eligibility for perceptible tasks.
// 2. When no longer eligible for perceptible tasks, turn off and clean up.
mSupportsFreeformWindowsManagement = getFreeformWindowsManagementInfo();
if (eligibleForPerceptibleTasks()) {
if (mTaskStackListener == null) {
mTaskStackListener = new PerceptibleTaskListener();
TaskStackChangeListeners.getInstance()
.registerTaskStackListener(mTaskStackListener);
}
} else {
// not eligible for perceptible tasks, so we should unregister the listener
if (mTaskStackListener != null) {
mTaskStackListener.unregisterListener();
mTaskStackListener = null;
}
}
}
private boolean getFreeformWindowsManagementInfo() {
return getPrimaryWindowContext().getPackageManager().hasSystemFeature(
FEATURE_FREEFORM_WINDOW_MANAGEMENT);
}
private void destroyAllTaskbars() {
debugPrimaryTaskbar("destroyAllTaskbars");
for (Entry<Integer, TaskbarActivityContext> entry : new ArraySet<>(mTaskbars.entrySet())) {
int displayId = entry.getKey();
debugTaskbarManager("destroyAllTaskbars: call destroyTaskbarForDisplay", displayId);
destroyTaskbarForDisplay(entry.getValue());
debugTaskbarManager("destroyAllTaskbars: call removeTaskbarRootViewFromWindow",
displayId);
removeTaskbarRootViewFromWindow(displayId);
}
}
private void destroyTaskbarForDisplay(int displayId) {
TaskbarActivityContext taskbar = mTaskbars.get(displayId);
if (taskbar == null) {
debugTaskbarManager("destroyTaskbarForDisplay: taskbar is NULL!", displayId);
return;
}
destroyTaskbarForDisplay(taskbar);
}
private void destroyTaskbarForDisplay(TaskbarActivityContext taskbar) {
final int displayId = taskbar.getDisplayId();
debugTaskbarManager("destroyTaskbarForDisplay", displayId);
taskbar.onDestroy();
// remove all defaults that we store
removeTaskbarFromMap(displayId);
DeviceProfile dp = getDeviceProfile(displayId);
if (dp == null || !isTaskbarEnabled(dp)) {
removeTaskbarRootViewFromWindow(displayId);
}
}
/**
* Show Taskbar upon receiving broadcast
*/
private void showTaskbarFromBroadcast(Intent intent) {
debugPrimaryTaskbar("destroyTaskbarForDisplay");
TaskbarActivityContext taskbar = getTaskbarForDisplay(getFocusedDisplayId());
if (ACTION_SHOW_TASKBAR.equals(intent.getAction()) && taskbar != null) {
taskbar.showTaskbarFromBroadcast();
}
}
private void showGrowthNudge(Intent intent) {
if (!enableGrowthNudge()) {
return;
}
if (BROADCAST_SHOW_NUDGE.equals(intent.getAction())) {
// TODO: b/397738606 - extract the details and create a nudge payload.
Log.d(GROWTH_FRAMEWORK_TAG, "Intent received");
}
}
/**
* Shows or hides the All Apps view in the Taskbar or Launcher, based on its current
* visibility on the System UI tracked focused display.
*/
private void toggleAllAppsSearch() {
TaskbarActivityContext taskbar = getTaskbarForDisplay(getFocusedDisplayId());
if (taskbar == null) {
// Home All Apps should be toggled from this class, because the controllers are not
// initialized when Taskbar is disabled (i.e. TaskbarActivityContext is null).
if (mActivity instanceof Launcher l) l.toggleAllApps(true);
} else {
taskbar.getControllers().uiController.toggleAllApps(true);
}
}
/**
* Displays a frame of the first Launcher reveal animation.
*
* This should be used to run a first Launcher reveal animation whose progress matches a swipe
* progress.
*/
public AnimatorPlaybackController createLauncherStartFromSuwAnim(int duration) {
TaskbarActivityContext taskbar = getTaskbarForDisplay(mPrimaryDisplayId);
return taskbar == null ? null : taskbar.createLauncherStartFromSuwAnim(duration);
}
/** Called when the user is unlocked */
public void onUserUnlocked() {
debugPrimaryTaskbar("onUserUnlocked");
mUserUnlocked = true;
DisplayController.INSTANCE.get(mPrimaryWindowContext).addChangeListener(
mRecreationListener);
debugPrimaryTaskbar("onUserUnlocked: recreating all taskbars!");
// Create DPs for all connected displays if required.
for (int i = 0; i < mWindowContexts.size(); i++) {
int displayId = mWindowContexts.keyAt(i);
if (displayId != mPrimaryDisplayId && !mExternalDeviceProfiles.contains(displayId)) {
createExternalDeviceProfile(displayId);
}
}
recreateTaskbars();
for (Entry<Integer, TaskbarActivityContext> entry: mTaskbars.entrySet()) {
int displayId = entry.getKey();
debugTaskbarManager("onUserUnlocked: addTaskbarRootViewToWindow()", displayId);
addTaskbarRootViewToWindow(entry.getValue());
}
}
/**
* Sets a {@link StatefulActivity} to act as taskbar callback
*/
public void setActivity(@NonNull StatefulActivity activity) {
debugPrimaryTaskbar("setActivity: mActivity=" + mActivity);
if (mActivity == activity) {
debugPrimaryTaskbar("setActivity: No need to set activity!");
return;
}
removeActivityCallbacksAndListeners();
mActivity = activity;
mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
debugPrimaryTaskbar("setActivity: registering activity lifecycle callbacks.");
mActivity.addEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
getUnfoldTransitionProgressProviderForActivity(activity);
if (unfoldTransitionProgressProvider != null) {
unfoldTransitionProgressProvider.addCallback(mUnfoldTransitionProgressListener);
}
mUnfoldProgressProvider.setSourceProvider(unfoldTransitionProgressProvider);
if (activity instanceof RecentsViewContainer recentsViewContainer) {
setRecentsViewContainer(recentsViewContainer);
}
}
/**
* Sets the current RecentsViewContainer, from which we create a TaskbarUIController.
*/
public void setRecentsViewContainer(@NonNull RecentsViewContainer recentsViewContainer) {
debugPrimaryTaskbar("setRecentsViewContainer");
if (mRecentsViewContainer == recentsViewContainer) {
return;
}
if (mRecentsViewContainer == mActivity) {
// When switching to RecentsWindowManager (not an Activity), the old mActivity is not
// destroyed, nor is there a new Activity to replace it. Thus if we don't clear it here,
// it will not get re-set properly if we return to the Activity (e.g. NexusLauncher).
mActivityOnDestroyCallback.run();
}
mRecentsViewContainer = recentsViewContainer;
TaskbarActivityContext taskbar = getCurrentActivityContext();
if (taskbar != null) {
taskbar.setUIController(
createTaskbarUIControllerForRecentsViewContainer(mRecentsViewContainer));
}
}
/**
* Returns an {@link UnfoldTransitionProgressProvider} to use while the given StatefulActivity
* is active.
*/
private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity(
StatefulActivity activity) {
debugPrimaryTaskbar("getUnfoldTransitionProgressProviderForActivity");
if (!enableUnfoldStateAnimation()) {
if (activity instanceof QuickstepLauncher ql) {
return ql.getUnfoldTransitionProgressProvider();
}
} else {
return SystemUiProxy.INSTANCE.get(mBaseContext).getUnfoldTransitionProvider();
}
return null;
}
/** Creates a {@link TaskbarUIController} to use with non default displays. */
private TaskbarUIController createTaskbarUIControllerForNonDefaultDisplay(int displayId) {
debugPrimaryTaskbar("createTaskbarUIControllerForNonDefaultDisplay");
if (RecentsWindowFlags.Companion.getEnableOverviewInWindow()) {
RecentsViewContainer rvc = mRecentsWindowManagerRepository.get(displayId);
if (rvc != null) {
return createTaskbarUIControllerForRecentsViewContainer(rvc);
}
}
return new TaskbarUIController();
}
/**
* Creates a {@link TaskbarUIController} to use while the given StatefulActivity is active.
*/
private TaskbarUIController createTaskbarUIControllerForRecentsViewContainer(
RecentsViewContainer container) {
debugPrimaryTaskbar("createTaskbarUIControllerForRecentsViewContainer");
if (mActivity instanceof QuickstepLauncher quickstepLauncher) {
// If 1P Launcher is default, always use LauncherTaskbarUIController, regardless of
// whether the recents container is NexusLauncherActivity or RecentsWindowManager.
return new LauncherTaskbarUIController(quickstepLauncher);
}
// If a 3P Launcher is default, always use FallbackTaskbarUIController regardless of
// whether the recents container is RecentsActivity or RecentsWindowManager.
if (container instanceof RecentsActivity recentsActivity) {
return new FallbackTaskbarUIController<>(recentsActivity);
}
if (container instanceof RecentsWindowManager recentsWindowManager) {
return new FallbackTaskbarUIController<>(recentsWindowManager);
}
return TaskbarUIController.DEFAULT;
}
/**
* This method is called multiple times (ex. initial init, then when user unlocks) in which case
* we fully want to destroy existing taskbars and create all desired new ones.
* In other case (folding/unfolding) we don't need to remove and add window.
*/
public synchronized void recreateTaskbars() {
for (int i = 0; i < mWindowContexts.size(); i++) {
int displayId = mWindowContexts.keyAt(i);
debugTaskbarManager("recreateTaskbars", displayId);
recreateTaskbarForDisplay(displayId, 0);
}
}
/**
* This method is called multiple times (ex. initial init, then when user unlocks) in which case
* we fully want to destroy an existing taskbar for a specified display and create a new one.
* In other case (folding/unfolding) we don't need to remove and add window.
*/
private void recreateTaskbarForDisplay(int displayId, int duration) {
debugTaskbarManager("recreateTaskbarForDisplay: ", displayId);
Trace.beginSection("recreateTaskbarForDisplay");
try {
debugTaskbarManager("recreateTaskbarForDisplay: getting device profile", displayId);
// TODO (b/381113004): make this display-specific via getWindowContext()
DeviceProfile dp = getDeviceProfile(displayId);
// All Apps action is unrelated to navbar unification, so we only need to check DP.
final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
mAllAppsActionManager.setTaskbarPresent(isLargeScreenTaskbar);
debugTaskbarManager("recreateTaskbarForDisplay: destroying taskbar", displayId);
destroyTaskbarForDisplay(displayId);
boolean displayExists = getDisplay(displayId) != null;
boolean isTaskbarEnabled = dp != null && isTaskbarEnabled(dp);
debugTaskbarManager("recreateTaskbarForDisplay: isTaskbarEnabled=" + isTaskbarEnabled
+ " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
+ " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
+ " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent)
+ " displayExists=" + displayExists, displayId);
if (!isTaskbarEnabled || !isLargeScreenTaskbar || !displayExists) {
SystemUiProxy.INSTANCE.get(mBaseContext)
.notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
if (!isTaskbarEnabled || !displayExists) {
debugTaskbarManager(
"recreateTaskbarForDisplay: exiting bc (!isTaskbarEnabled || "
+ "!displayExists)",
displayId);
return;
}
}
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (enableTaskbarNoRecreate() || taskbar == null) {
debugTaskbarManager("recreateTaskbarForDisplay: creating taskbar", displayId);
taskbar = createTaskbarActivityContext(dp, displayId);
if (taskbar == null) {
debugTaskbarManager(
"recreateTaskbarForDisplay: new taskbar instance is null!", displayId);
return;
}
} else {
debugTaskbarManager("recreateTaskbarForDisplay: updating taskbar device profile",
displayId);
taskbar.updateDeviceProfile(dp);
}
mSharedState.startTaskbarVariantIsTransient = taskbar.isTransientTaskbar();
mSharedState.allAppsVisible = mSharedState.allAppsVisible && isLargeScreenTaskbar;
taskbar.init(mSharedState, duration);
// Non default displays should not use LauncherTaskbarUIController as they shouldn't
// have access to the Launcher activity.
if (isExternalDisplay(displayId)) {
taskbar.setUIController(createTaskbarUIControllerForNonDefaultDisplay(displayId));
} else if (mRecentsViewContainer != null) {
taskbar.setUIController(
createTaskbarUIControllerForRecentsViewContainer(mRecentsViewContainer));
}
if (enableTaskbarNoRecreate()) {
debugTaskbarManager("recreateTaskbarForDisplay: adding rootView", displayId);
addTaskbarRootViewToWindow(taskbar);
FrameLayout taskbarRootLayout = getTaskbarRootLayoutForDisplay(displayId);
if (taskbarRootLayout != null) {
debugTaskbarManager("recreateTaskbarForDisplay: adding root layout", displayId);
taskbarRootLayout.removeAllViews();
taskbarRootLayout.addView(taskbar.getDragLayer());
taskbar.notifyUpdateLayoutParams();
} else {
debugTaskbarManager("recreateTaskbarForDisplay: taskbarRootLayout is null!",
displayId);
}
}
} finally {
Trace.endSection();
}
}
/** Called when the SysUI flags for a given display change. */
public void onSystemUiFlagsChanged(@SystemUiStateFlags long systemUiStateFlags, int displayId) {
if (DEBUG) {
Log.d(TAG, "SysUI flags changed: " + formatFlagChange(systemUiStateFlags,
mSharedState.sysuiStateFlags, QuickStepContract::getSystemUiStateString));
}
mSharedState.sysuiStateFlags = systemUiStateFlags;
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
taskbar.updateSysuiStateFlags(systemUiStateFlags, false /* fromInit */);
}
}
public void onLongPressHomeEnabled(boolean assistantLongPressEnabled) {
if (mPrimaryNavButtonController != null) {
mPrimaryNavButtonController.setAssistantLongPressEnabled(assistantLongPressEnabled);
}
}
/**
* Sets the flag indicating setup UI is visible
*/
public void setSetupUIVisible(boolean isVisible) {
mSharedState.setupUIVisible = isVisible;
mAllAppsActionManager.setSetupUiVisible(isVisible);
TaskbarActivityContext taskbar = getTaskbarForDisplay(mPrimaryDisplayId);
if (taskbar != null) {
taskbar.setSetupUIVisible(isVisible);
}
}
/**
* Sets wallpaper visibility for specific display.
*/
public void setWallpaperVisible(int displayId, boolean isVisible) {
mSharedState.wallpaperVisible = isVisible;
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
taskbar.setWallpaperVisible(isVisible);
}
}
public void checkNavBarModes(int displayId) {
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
taskbar.checkNavBarModes();
}
}
public void finishBarAnimations(int displayId) {
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
taskbar.finishBarAnimations();
}
}
public void touchAutoDim(int displayId, boolean reset) {
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
taskbar.touchAutoDim(reset);
}
}
public void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode,
boolean animate) {
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
taskbar.transitionTo(barMode, animate);
}
}
public void appTransitionPending(boolean pending) {
TaskbarActivityContext taskbar = getTaskbarForDisplay(mPrimaryDisplayId);
if (taskbar != null) {
taskbar.appTransitionPending(pending);
}
}
private boolean isTaskbarEnabled(DeviceProfile deviceProfile) {
return ENABLE_TASKBAR_NAVBAR_UNIFICATION || deviceProfile.isTaskbarPresent;
}
public void onRotationProposal(int rotation, boolean isValid) {
TaskbarActivityContext taskbar = getTaskbarForDisplay(mPrimaryDisplayId);
if (taskbar != null) {
taskbar.onRotationProposal(rotation, isValid);
}
}
public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) {
mSharedState.disableNavBarDisplayId = displayId;
mSharedState.disableNavBarState1 = state1;
mSharedState.disableNavBarState2 = state2;
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
taskbar.disableNavBarElements(displayId, state1, state2, animate);
}
}
public void onSystemBarAttributesChanged(int displayId, int behavior) {
mSharedState.systemBarAttrsDisplayId = displayId;
mSharedState.systemBarAttrsBehavior = behavior;
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
taskbar.onSystemBarAttributesChanged(displayId, behavior);
}
}
public void onTransitionModeUpdated(int barMode, boolean checkBarModes) {
mSharedState.barMode = barMode;
TaskbarActivityContext taskbar = getTaskbarForDisplay(mPrimaryDisplayId);
if (taskbar != null) {
taskbar.onTransitionModeUpdated(barMode, checkBarModes);
}
}
public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
mSharedState.navButtonsDarkIntensity = darkIntensity;
TaskbarActivityContext taskbar = getTaskbarForDisplay(mPrimaryDisplayId);
if (taskbar != null) {
taskbar.onNavButtonsDarkIntensityChanged(darkIntensity);
}
}
public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
mSharedState.mLumaSamplingDisplayId = displayId;
mSharedState.mIsLumaSamplingEnabled = enable;
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
taskbar.onNavigationBarLumaSamplingEnabled(displayId, enable);
}
}
/**
* Signal from SysUI indicating that a non-mirroring display was just connected to the
* primary device or a previously mirroring display is switched to extended mode.
*/
@Override
public void onDisplayAddSystemDecorations(int displayId) {
debugTaskbarManager("onDisplayAddSystemDecorations: ", displayId);
Display display = getDisplay(displayId);
if (display == null) {
debugTaskbarManager("onDisplayAddSystemDecorations: can't find display!", displayId);
return;
}
if (!isExternalDisplay(displayId)) {
debugTaskbarManager(
"onDisplayAddSystemDecorations: not an external display! | "
+ "isExternalDisplay=" + isExternalDisplay(displayId), displayId);
return;
}
debugTaskbarManager("onDisplayAddSystemDecorations: creating new windowContext!",
displayId);
Context newWindowContext = createWindowContext(displayId);
if (newWindowContext != null) {
debugTaskbarManager("onDisplayAddSystemDecorations: add new windowContext to map!",
displayId);
WindowManager wm = newWindowContext.getSystemService(WindowManager.class);
if (wm == null || !wm.shouldShowSystemDecors(displayId)) {
String wmStatus = wm == null ? "WindowManager is null!" : "WindowManager exists";
boolean showDecor = wm != null && wm.shouldShowSystemDecors(displayId);
debugTaskbarManager(
"onDisplayAddSystemDecorations:\n\t" + wmStatus + "\n\tshowSystemDecors="
+ showDecor, displayId);
return;
}
addWindowContextToMap(displayId, newWindowContext);
debugTaskbarManager("onDisplayAddSystemDecorations: creating RootLayout!", displayId);
createExternalDeviceProfile(displayId);
debugTaskbarManager("onDisplayAddSystemDecorations: creating RootLayout!", displayId);
createTaskbarRootLayout(displayId);
debugTaskbarManager("onDisplayAddSystemDecorations: creating NavButtonController!",
displayId);
createNavButtonController(displayId);
debugTaskbarManager(
"onDisplayAddSystemDecorations: createAndRegisterComponentCallbacks!",
displayId);
createAndRegisterComponentCallbacks(displayId);
debugTaskbarManager("onDisplayAddSystemDecorations: recreateTaskbarForDisplay!",
displayId);
recreateTaskbarForDisplay(displayId, 0);
} else {
debugTaskbarManager("onDisplayAddSystemDecorations: newWindowContext is NULL!",
displayId);
}
debugTaskbarManager("onDisplayAddSystemDecorations: finished!", displayId);
}
/**
* Signal from SysUI indicating that a previously connected non-mirroring display was just
* removed from the primary device.
*/
@Override
public void onDisplayRemoved(int displayId) {
debugTaskbarManager("onDisplayRemoved: ", displayId);
if (!isExternalDisplay(displayId)) {
debugTaskbarManager(
"onDisplayRemoved: not an external display! | "
+ "isExternalDisplay=" + isExternalDisplay(displayId), displayId);
return;
}
Context windowContext = getWindowContext(displayId);
if (windowContext != null) {
debugTaskbarManager("onDisplayRemoved: removing NavButtonController!", displayId);
removeNavButtonController(displayId);
debugTaskbarManager("onDisplayRemoved: removeAndUnregisterComponentCallbacks!",
displayId);
removeAndUnregisterComponentCallbacks(displayId);
debugTaskbarManager("onDisplayRemoved: removing DeviceProfile from map!", displayId);
removeDeviceProfileFromMap(displayId);
debugTaskbarManager("onDisplayRemoved: destroying Taskbar!", displayId);
destroyTaskbarForDisplay(displayId);
debugTaskbarManager("onDisplayRemoved: removing WindowContext from map!", displayId);
removeWindowContextFromMap(displayId);
debugTaskbarManager("onDisplayRemoved: finished!", displayId);
} else {
debugTaskbarManager("onDisplayRemoved: windowContext is null!", displayId);
}
}
/**
* Signal from SysUI indicating that system decorations should be removed from the display.
*/
@Override
public void onDisplayRemoveSystemDecorations(int displayId) {
// The display mirroring starts. The handling logic is the same as when removing a
// display.
onDisplayRemoved(displayId);
}
private void removeActivityCallbacksAndListeners() {
if (mActivity != null) {
mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
debugPrimaryTaskbar("unregistering activity lifecycle callbacks");
mActivity.removeEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
getUnfoldTransitionProgressProviderForActivity(mActivity);
if (unfoldTransitionProgressProvider != null) {
unfoldTransitionProgressProvider.removeCallback(mUnfoldTransitionProgressListener);
}
}
}
/**
* Called when the manager is no longer needed
*/
public void destroy() {
debugPrimaryTaskbar("TaskbarManager#destroy()");
mRecentsViewContainer = null;
debugPrimaryTaskbar("destroy: removing activity callbacks");
DesktopVisibilityController.INSTANCE.get(
mPrimaryWindowContext).unregisterTaskbarDesktopModeListener(
mTaskbarDesktopModeListener);
removeActivityCallbacksAndListeners();
mTaskbarBroadcastReceiver.unregisterReceiverSafely();
if (mGrowthBroadcastReceiver != null) {
mGrowthBroadcastReceiver.unregisterReceiverSafely();
}
if (mUserUnlocked) {
DisplayController.INSTANCE.get(mPrimaryWindowContext).removeChangeListener(
mRecreationListener);
}
SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
SystemDecorationChangeObserver.getINSTANCE().get(mPrimaryWindowContext)
.unregisterDisplayDecorationListener(this);
debugPrimaryTaskbar("destroy: unregistering component callbacks");
removeAndUnregisterComponentCallbacks(mPrimaryDisplayId);
mShutdownReceiver.unregisterReceiverSafely();
if (mTaskStackListener != null) {
mTaskStackListener.unregisterListener();
}
debugPrimaryTaskbar("destroy: destroying all taskbars!");
removeWindowContextFromMap(mPrimaryDisplayId);
destroyAllTaskbars();
debugPrimaryTaskbar("destroy: finished!");
}
private boolean eligibleForPerceptibleTasks() {
// Perceptible tasks feature (oom boosting) is eligible for android PC devices, and
// other android devices that supports free form windows
//
// - isAndroidPC is set per device (in this case, desktop devices)
// - supportsFreeformWindowsManagement is dynamic, and is to be used for the use-case where
// user plugs in their device to external displays
return Flags.perceptibleTasks()
&& (mIsAndroidPC || mSupportsFreeformWindowsManagement);
}
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
return getTaskbarForDisplay(mPrimaryDisplayId);
}
public void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "TaskbarManager:");
// iterate through taskbars and do the dump for each
for (Entry<Integer, TaskbarActivityContext> entry : mTaskbars.entrySet()) {
int displayId = entry.getKey();
TaskbarActivityContext taskbar = entry.getValue();
pw.println(prefix + "\tTaskbar at display " + displayId + ":");
if (taskbar == null) {
pw.println(prefix + "\t\tTaskbarActivityContext: null");
} else {
taskbar.dumpLogs(prefix + "\t\t", pw);
}
}
}
private void addTaskbarRootViewToWindow(@NonNull TaskbarActivityContext taskbar) {
int displayId = taskbar.getDisplayId();
debugTaskbarManager("addTaskbarRootViewToWindow:", displayId);
if (!enableTaskbarNoRecreate()) {
debugTaskbarManager("addTaskbarRootViewToWindow: taskbar null", displayId);
return;
}
if (getDisplay(displayId) == null) {
debugTaskbarManager("addTaskbarRootViewToWindow: display null", displayId);
return;
}
if (!isTaskbarRootLayoutAddedForDisplay(displayId)) {
FrameLayout rootLayout = getTaskbarRootLayoutForDisplay(displayId);
WindowManager windowManager = getWindowManager(displayId);
if (rootLayout != null && windowManager != null) {
windowManager.addView(rootLayout, taskbar.getWindowLayoutParams());
mAddedRootLayouts.put(displayId, true);
} else {
String rootLayoutStatus =
(rootLayout == null) ? "rootLayout is NULL!" : "rootLayout exists!";
String wmStatus = (windowManager == null) ? "windowManager is NULL!"
: "windowManager exists!";
debugTaskbarManager(
"addTaskbarRootViewToWindow: \n\t" + rootLayoutStatus + "\n\t" + wmStatus,
displayId);
}
} else {
debugTaskbarManager("addTaskbarRootViewToWindow: rootLayout already added!", displayId);
}
}
private void removeTaskbarRootViewFromWindow(int displayId) {
debugTaskbarManager("removeTaskbarRootViewFromWindow", displayId);
FrameLayout rootLayout = getTaskbarRootLayoutForDisplay(displayId);
if (!enableTaskbarNoRecreate() || rootLayout == null) {
return;
}
WindowManager windowManager = getWindowManager(displayId);
if (isTaskbarRootLayoutAddedForDisplay(displayId) && windowManager != null) {
windowManager.removeViewImmediate(rootLayout);
mAddedRootLayouts.put(displayId, false);
removeTaskbarRootLayoutFromMap(displayId);
} else {
debugTaskbarManager("removeTaskbarRootViewFromWindow: WindowManager is null",
displayId);
}
}
/**
* Returns the {@link TaskbarUIController} associated with the given display ID.
* TODO(b/395061396): Remove this method when overview in widow is enabled.
*
* @param displayId The ID of the display to retrieve the taskbar for.
* @return The {@link TaskbarUIController} for the specified display, or
* {@code null} if no taskbar is associated with that display.
*/
@Nullable
public TaskbarUIController getUIControllerForDisplay(int displayId) {
TaskbarActivityContext taskbarActivityContext = getTaskbarForDisplay(displayId);
if (taskbarActivityContext == null) {
return null;
}
return taskbarActivityContext.getControllers().uiController;
}
/**
* Retrieves whether RootLayout was added to window for specific display, or false if no
* such mapping has been made.
*
* @param displayId The ID of the display for which to retrieve the taskbar root layout.
* @return if RootLayout was added to window {@link Boolean} for a display or {@code false}.
*/
private boolean isTaskbarRootLayoutAddedForDisplay(int displayId) {
return mAddedRootLayouts.get(displayId);
}
/**
* Returns the {@link TaskbarActivityContext} associated with the given display ID.
*
* @param displayId The ID of the display to retrieve the taskbar for.
* @return The {@link TaskbarActivityContext} for the specified display, or
* {@code null} if no taskbar is associated with that display.
*/
public TaskbarActivityContext getTaskbarForDisplay(int displayId) {
return mTaskbars.get(displayId);
}
/**
* Creates a {@link TaskbarActivityContext} for the given display and adds it to the map.
*
* @param dp The {@link DeviceProfile} for the display.
* @param displayId The ID of the display.
*/
private @Nullable TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp,
int displayId) {
Display display = getDisplay(displayId);
if (display == null) {
debugTaskbarManager("createTaskbarActivityContext: display null", displayId);
return null;
}
Context navigationBarPanelContext = null;
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
navigationBarPanelContext = mBaseContext.createWindowContext(display,
TYPE_NAVIGATION_BAR_PANEL, null);
}
TaskbarActivityContext newTaskbar = new TaskbarActivityContext(displayId,
getWindowContext(displayId), navigationBarPanelContext, dp,
getNavButtonController(displayId), mUnfoldProgressProvider,
!isExternalDisplay(displayId), SystemUiProxy.INSTANCE.get(mBaseContext));
addTaskbarToMap(displayId, newTaskbar);
return newTaskbar;
}
/**
* Creates a {@link DeviceProfile} for the given display and adds it to the map.
* @param displayId The ID of the display.
*/
private void createExternalDeviceProfile(int displayId) {
if (!mUserUnlocked) {
return;
}
InvariantDeviceProfile idp = LauncherAppState.getIDP(mPrimaryWindowContext);
if (idp == null) {
return;
}
Context displayContext = getWindowContext(displayId);
if (displayContext == null) {
return;
}
DeviceProfile externalDeviceProfile = idp.createDeviceProfileForSecondaryDisplay(
displayContext);
mExternalDeviceProfiles.put(displayId, externalDeviceProfile);
}
/**
* Gets a {@link DeviceProfile} for the given displayId.
* @param displayId The ID of the display.
*/
private @Nullable DeviceProfile getDeviceProfile(int displayId) {
if (!mUserUnlocked) {
return null;
}
InvariantDeviceProfile idp = LauncherAppState.getIDP(mPrimaryWindowContext);
if (idp == null) {
return null;
}
if (!isExternalDisplay(displayId)) {
return idp.getDeviceProfile(mPrimaryWindowContext);
}
return mExternalDeviceProfiles.get(displayId);
}
/**
* Removes the {@link DeviceProfile} associated with the given display ID from the map.
* @param displayId The ID of the display for which to remove the taskbar.
*/
private void removeDeviceProfileFromMap(int displayId) {
mExternalDeviceProfiles.delete(displayId);
}
/**
* Create {@link ComponentCallbacks} for the given display and register it to the relevant
* WindowContext. For external displays, populate maps.
*
* @param displayId The ID of the display.
*/
private void createAndRegisterComponentCallbacks(int displayId) {
debugTaskbarManager("createAndRegisterComponentCallbacks", displayId);
ComponentCallbacks callbacks = new ComponentCallbacks() {
private Configuration mOldConfig =
getWindowContext(displayId).getResources().getConfiguration();
@Override
public void onConfigurationChanged(Configuration newConfig) {
Trace.instantForTrack(Trace.TRACE_TAG_APP, "TaskbarManager",
"onConfigurationChanged: " + newConfig);
debugTaskbarManager("onConfigurationChanged: " + newConfig, displayId);
DeviceProfile dp = getDeviceProfile(displayId);
int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
debugTaskbarManager("onConfigurationChanged: theme changed", displayId);
// Only recreate for theme changes, not other UI mode changes such as docking.
int oldUiNightMode = (mOldConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
int newUiNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
if (oldUiNightMode == newUiNightMode) {
configDiff &= ~ActivityInfo.CONFIG_UI_MODE;
}
}
debugTaskbarManager("onConfigurationChanged: | configDiff="
+ Configuration.configurationDiffToString(configDiff), displayId);
if (configDiff != 0 || getCurrentActivityContext() == null) {
debugTaskbarManager("onConfigurationChanged: call recreateTaskbars", displayId);
recreateTaskbars();
} else if (dp != null) {
// Config change might be handled without re-creating the taskbar
if (!isTaskbarEnabled(dp)) {
debugPrimaryTaskbar(
"onConfigurationChanged: isTaskbarEnabled(dp)=False | "
+ "destroyTaskbarForDisplay");
destroyTaskbarForDisplay(mPrimaryDisplayId);
} else {
debugPrimaryTaskbar("onConfigurationChanged: isTaskbarEnabled(dp)=True");
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
// Re-initialize for screen size change? Should this be done
// by looking at screen-size change flag in configDiff in the
// block above?
debugPrimaryTaskbar("onConfigurationChanged: call recreateTaskbars");
recreateTaskbars();
} else {
debugPrimaryTaskbar(
"onConfigurationChanged: updateDeviceProfile for current "
+ "taskbar.");
getCurrentActivityContext().updateDeviceProfile(dp);
}
}
} else {
getCurrentActivityContext().onConfigurationChanged(configDiff);
}
mOldConfig = new Configuration(newConfig);
// reset taskbar was pinned value, so we don't automatically unstash taskbar upon
// user unfolding the device.
mSharedState.setTaskbarWasPinned(false);
}
@Override
public void onLowMemory() {
}
};
if (!isExternalDisplay(displayId)) {
mPrimaryComponentCallbacks = callbacks;
mPrimaryWindowContext.registerComponentCallbacks(callbacks);
} else {
mComponentCallbacks.put(displayId, callbacks);
getWindowContext(displayId).registerComponentCallbacks(callbacks);
}
}
/**
* Unregister {@link ComponentCallbacks} for the given display from its WindowContext. For
* external displays, remove from the map.
*
* @param displayId The ID of the display.
*/
private void removeAndUnregisterComponentCallbacks(int displayId) {
if (!isExternalDisplay(displayId)) {
mPrimaryWindowContext.unregisterComponentCallbacks(mPrimaryComponentCallbacks);
} else {
ComponentCallbacks callbacks = mComponentCallbacks.get(displayId);
getWindowContext(displayId).unregisterComponentCallbacks(callbacks);
mComponentCallbacks.delete(displayId);
}
}
/**
* Creates a {@link TaskbarNavButtonController} for the given display and adds it to the map
* if it doesn't already exist.
*
* @param displayId The ID of the display
*/
private void createNavButtonController(int displayId) {
if (!isExternalDisplay(displayId)) {
mPrimaryNavButtonController = new TaskbarNavButtonController(
mPrimaryWindowContext,
mNavCallbacks,
SystemUiProxy.INSTANCE.get(mBaseContext),
new Handler(),
new ContextualSearchInvoker(mBaseContext));
} else {
TaskbarNavButtonController navButtonController = new TaskbarNavButtonController(
getWindowContext(displayId),
mNavCallbacks,
SystemUiProxy.INSTANCE.get(mBaseContext),
new Handler(),
new ContextualSearchInvoker(mBaseContext));
mNavButtonControllers.put(displayId, navButtonController);
}
}
private TaskbarNavButtonController getNavButtonController(int displayId) {
return (!isExternalDisplay(displayId)) ? mPrimaryNavButtonController
: mNavButtonControllers.get(displayId);
}
private void removeNavButtonController(int displayId) {
if (!isExternalDisplay(displayId)) {
mPrimaryNavButtonController = null;
} else {
mNavButtonControllers.delete(displayId);
}
}
/**
* Adds the {@link TaskbarActivityContext} associated with the given display ID to taskbar
* map if there is not already a taskbar mapped to that displayId.
*
* @param displayId The ID of the display to retrieve the taskbar for.
* @param newTaskbar The new {@link TaskbarActivityContext} to add to the map.
*/
private void addTaskbarToMap(int displayId, TaskbarActivityContext newTaskbar) {
mTaskbars.putIfAbsent(displayId, newTaskbar);
}
/**
* Removes the taskbar associated with the given display ID from the taskbar map.
*
* @param displayId The ID of the display for which to remove the taskbar.
*/
private void removeTaskbarFromMap(int displayId) {
mTaskbars.remove(displayId);
}
/**
* Creates {@link FrameLayout} for the taskbar on the specified display and adds it to map.
*
* @param displayId The ID of the display for which to create the taskbar root layout.
*/
private void createTaskbarRootLayout(int displayId) {
debugTaskbarManager("createTaskbarRootLayout: ", displayId);
if (!enableTaskbarNoRecreate()) {
return;
}
FrameLayout newTaskbarRootLayout = new FrameLayout(getWindowContext(displayId)) {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
debugTaskbarManager("dispatchTouchEvent: ", displayId);
// The motion events can be outside the view bounds of task bar, and hence
// manually dispatching them to the drag layer here.
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null && taskbar.getDragLayer().isAttachedToWindow()) {
return taskbar.getDragLayer().dispatchTouchEvent(ev);
}
return super.dispatchTouchEvent(ev);
}
};
debugTaskbarManager("createTaskbarRootLayout: adding to map", displayId);
addTaskbarRootLayoutToMap(displayId, newTaskbarRootLayout);
}
private boolean isDefaultDisplay(int displayId) {
return displayId == mPrimaryDisplayId;
}
/**
* Retrieves the root layout of the taskbar for the specified display.
*
* @param displayId The ID of the display for which to retrieve the taskbar root layout.
* @return The taskbar root layout {@link FrameLayout} for a given display or {@code null}.
*/
private FrameLayout getTaskbarRootLayoutForDisplay(int displayId) {
debugTaskbarManager("getTaskbarRootLayoutForDisplay:", displayId);
FrameLayout frameLayout = mRootLayouts.get(displayId);
if (frameLayout != null) {
return frameLayout;
} else {
debugTaskbarManager("getTaskbarRootLayoutForDisplay: rootLayout is null!", displayId);
return null;
}
}
/**
* Adds the taskbar root layout {@link FrameLayout} to taskbar map, mapped to display ID.
*
* @param displayId The ID of the display to associate with the taskbar root layout.
* @param rootLayout The taskbar root layout {@link FrameLayout} to add to the map.
*/
private void addTaskbarRootLayoutToMap(int displayId, FrameLayout rootLayout) {
debugTaskbarManager("addTaskbarRootLayoutToMap: ", displayId);
if (!mRootLayouts.contains(displayId) && rootLayout != null) {
mRootLayouts.put(displayId, rootLayout);
}
debugTaskbarManager(
"addTaskbarRootLayoutToMap: finished! mRootLayouts.size()=" + mRootLayouts.size(),
displayId);
}
/**
* Removes taskbar root layout {@link FrameLayout} for given display ID from the taskbar map.
*
* @param displayId The ID of the display for which to remove the taskbar root layout.
*/
private void removeTaskbarRootLayoutFromMap(int displayId) {
debugTaskbarManager("removeTaskbarRootLayoutFromMap:", displayId);
if (mRootLayouts.contains(displayId)) {
mAddedRootLayouts.delete(displayId);
mRootLayouts.delete(displayId);
}
debugTaskbarManager("removeTaskbarRootLayoutFromMap: finished! mRootLayouts.size="
+ mRootLayouts.size(), displayId);
}
/**
* Creates {@link Context} for the taskbar on the specified display.
*
* @param displayId The ID of the display for which to create the window context.
*/
private @Nullable Context createWindowContext(int displayId) {
debugTaskbarManager("createWindowContext: ", displayId);
Display display = getDisplay(displayId);
if (display == null) {
debugTaskbarManager("createWindowContext: display null!", displayId);
return null;
}
int windowType = TYPE_NAVIGATION_BAR_PANEL;
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && !isExternalDisplay(displayId)) {
windowType = TYPE_NAVIGATION_BAR;
}
debugTaskbarManager(
"createWindowContext: windowType=" + ((windowType == TYPE_NAVIGATION_BAR)
? "TYPE_NAVIGATION_BAR" : "TYPE_NAVIGATION_BAR_PANEL"), displayId);
return mBaseContext.createWindowContext(display, windowType, null);
}
private @Nullable Display getDisplay(int displayId) {
if (mDisplayManager == null) {
debugTaskbarManager("cannot get DisplayManager", displayId);
return null;
}
Display display = mDisplayManager.getDisplay(displayId);
if (display == null) {
debugTaskbarManager("Cannot get display!", displayId);
return null;
}
return mDisplayManager.getDisplay(displayId);
}
/**
* Retrieves the window context of the taskbar for the specified display.
*
* @param displayId The ID of the display for which to retrieve the window context.
* @return The Window Context {@link Context} for a given display or {@code null}.
*/
private Context getWindowContext(int displayId) {
return (!isExternalDisplay(displayId))
? mPrimaryWindowContext : mWindowContexts.get(displayId);
}
@VisibleForTesting
public Context getPrimaryWindowContext() {
return mPrimaryWindowContext;
}
/**
* Retrieves the window manager {@link WindowManager} of the taskbar for the specified display.
*
* @param displayId The ID of the display for which to retrieve the window manager.
* @return The window manager {@link WindowManager} for a given display or {@code null}.
*/
private @Nullable WindowManager getWindowManager(int displayId) {
if (!isExternalDisplay(displayId)) {
debugTaskbarManager("cannot get mPrimaryWindowManager", displayId);
return mPrimaryWindowManager;
}
Context externalDisplayContext = getWindowContext(displayId);
if (externalDisplayContext == null) {
debugTaskbarManager("cannot get externalDisplayContext", displayId);
return null;
}
return externalDisplayContext.getSystemService(WindowManager.class);
}
/**
* Adds the window context {@link Context} to taskbar map, mapped to display ID.
*
* @param displayId The ID of the display to associate with the taskbar root layout.
* @param windowContext The window context {@link Context} to add to the map.
*/
private void addWindowContextToMap(int displayId, @NonNull Context windowContext) {
if (!mWindowContexts.contains(displayId)) {
mWindowContexts.put(displayId, windowContext);
}
}
/**
* Removes the window context {@link Context} for given display ID from the taskbar map.
*
* @param displayId The ID of the display for which to remove the taskbar root layout.
*/
private void removeWindowContextFromMap(int displayId) {
if (mWindowContexts.contains(displayId)) {
mWindowContexts.delete(displayId);
}
}
private boolean isExternalDisplay(int displayId) {
return DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue() && (
mPrimaryDisplayId != displayId);
}
private int getFocusedDisplayId() {
return SystemUiProxy.INSTANCE.get(mBaseContext).getFocusState().getFocusedDisplayId();
}
/**
* Logs debug information about the TaskbarManager for primary display.
* @param debugReason A string describing the reason for the debug log.
* @param displayId The ID of the display for which to log debug information.
*/
public void debugTaskbarManager(String debugReason, int displayId) {
StringJoiner log = new StringJoiner("\n");
log.add(debugReason + " displayId=" + displayId + " isDefaultDisplay=" + isDefaultDisplay(
displayId));
Log.d(TAG, log.toString());
}
/**
* Logs verbose debug information about the TaskbarManager for primary display.
* @param debugReason A string describing the reason for the debug log.
* @param displayId The ID of the display for which to log debug information.
* @param verbose Indicates whether or not to debug with detail.
*/
private void debugTaskbarManager(String debugReason, int displayId, boolean verbose) {
StringJoiner log = new StringJoiner("\n");
log.add(debugReason + " displayId=" + displayId + " isDefaultDisplay=" + isDefaultDisplay(
displayId));
if (verbose) {
generateVerboseLogs(log, displayId);
}
Log.d(TAG, log.toString());
}
/**
* Logs debug information about the TaskbarManager for primary display.
* @param debugReason A string describing the reason for the debug log.
*
*/
private void debugPrimaryTaskbar(String debugReason) {
debugTaskbarManager(debugReason, mPrimaryDisplayId, false);
}
/**
* Logs debug information about the TaskbarManager for primary display.
* @param debugReason A string describing the reason for the debug log.
*
*/
public void debugPrimaryTaskbar(String debugReason, boolean verbose) {
debugTaskbarManager(debugReason, mPrimaryDisplayId, verbose);
}
/** Creates a {@link PendingIntent} for showing / hiding the all apps UI. */
public PendingIntent createAllAppsPendingIntent(Executor uiExecutor) {
return new PendingIntent(new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType,
IBinder allowlistToken, IIntentReceiver finishedReceiver,
String requiredPermission, Bundle options) {
uiExecutor.execute(TaskbarManagerImpl.this::toggleAllAppsSearch);
}
});
}
/**
* Logs verbose debug information about the TaskbarManager for a specific display.
*/
private void generateVerboseLogs(StringJoiner log, int displayId) {
boolean activityTaskbarPresent = mActivity != null
&& mActivity.getDeviceProfile().isTaskbarPresent;
// TODO (b/381113004): make this display-specific via getWindowContext()
Context windowContext = mPrimaryWindowContext;
if (windowContext == null) {
log.add("windowContext is null!");
return;
}
boolean contextTaskbarPresent = false;
if (mUserUnlocked) {
DeviceProfile dp = getDeviceProfile(displayId);
contextTaskbarPresent = dp != null && dp.isTaskbarPresent;
}
if (activityTaskbarPresent == contextTaskbarPresent) {
log.add("mActivity and mWindowContext agree taskbarIsPresent=" + contextTaskbarPresent);
Log.d(TAG, log.toString());
return;
}
log.add("mActivity & mWindowContext device profiles have different values, add more logs.");
log.add("\tmActivity logs:");
log.add("\t\tmActivity=" + mActivity);
if (mActivity != null) {
log.add("\t\tmActivity.getResources().getConfiguration()="
+ mActivity.getResources().getConfiguration());
log.add("\t\tmActivity.getDeviceProfile().isTaskbarPresent="
+ activityTaskbarPresent);
}
log.add("\tWindowContext logs:");
log.add("\t\tWindowContext=" + windowContext);
log.add("\t\tWindowContext.getResources().getConfiguration()="
+ windowContext.getResources().getConfiguration());
if (mUserUnlocked) {
log.add("\t\tgetDeviceProfile(mPrimaryWindowContext).isTaskbarPresent="
+ contextTaskbarPresent);
} else {
log.add("\t\tCouldn't get DeviceProfile because !mUserUnlocked");
}
}
private final DeviceProfile.OnDeviceProfileChangeListener mDebugActivityDeviceProfileChanged =
dp -> debugPrimaryTaskbar("mActivity onDeviceProfileChanged", true);
}