f4835a8884
This patch makes it so that the transient Taskbar cannot unstash when in 3P launcher. Previously, the user was able to unstash Taskbar when in 3P launcher, causing a janky-looking UI (3P launchers may implement their own version of Taskbar on the home screen. This also caused problems with certain Taskbar commands like split screen, which provide an entry portal to Pixel-specific implementations. Fixed by forcing the Taskbar to stay stashed when a 3P Launcher is displayed. The Taskbar is still usable inside of other non-launcher apps. This was done by using TopTaskTracker to check for ACTIVITY_TYPE_HOME or ACTIVITY_TYPE_RECENTS, and disabling Taskbar when these activities are running. Fixes: 277963491 Test: Manual Change-Id: Ifc0f3c07e3b76eb610f93205978fbc596bab6253
337 lines
14 KiB
Java
337 lines
14 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 com.android.launcher3.config.FeatureFlags.ENABLE_MATERIAL_U_POPUP;
|
|
import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
|
|
|
|
import android.content.Intent;
|
|
import android.content.pm.LauncherApps;
|
|
import android.graphics.Point;
|
|
import android.util.Pair;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import com.android.internal.logging.InstanceId;
|
|
import com.android.launcher3.AbstractFloatingView;
|
|
import com.android.launcher3.BubbleTextView;
|
|
import com.android.launcher3.LauncherSettings;
|
|
import com.android.launcher3.R;
|
|
import com.android.launcher3.dot.FolderDotInfo;
|
|
import com.android.launcher3.folder.Folder;
|
|
import com.android.launcher3.folder.FolderIcon;
|
|
import com.android.launcher3.model.data.FolderInfo;
|
|
import com.android.launcher3.model.data.ItemInfo;
|
|
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
|
import com.android.launcher3.notification.NotificationListener;
|
|
import com.android.launcher3.popup.PopupContainerWithArrow;
|
|
import com.android.launcher3.popup.PopupDataProvider;
|
|
import com.android.launcher3.popup.PopupLiveUpdateHandler;
|
|
import com.android.launcher3.popup.SystemShortcut;
|
|
import com.android.launcher3.shortcuts.DeepShortcutView;
|
|
import com.android.launcher3.splitscreen.SplitShortcut;
|
|
import com.android.launcher3.util.ComponentKey;
|
|
import com.android.launcher3.util.LauncherBindableItemsContainer;
|
|
import com.android.launcher3.util.PackageUserKey;
|
|
import com.android.launcher3.util.ShortcutUtil;
|
|
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
|
|
import com.android.launcher3.views.ActivityContext;
|
|
import com.android.quickstep.SystemUiProxy;
|
|
import com.android.quickstep.util.LogUtils;
|
|
|
|
import java.io.PrintWriter;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.function.Predicate;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
|
|
/**
|
|
* Implements interfaces required to show and allow interacting with a PopupContainerWithArrow.
|
|
* Controls the long-press menu on Taskbar and AllApps icons.
|
|
*/
|
|
public class TaskbarPopupController implements TaskbarControllers.LoggableTaskbarController {
|
|
|
|
private static final SystemShortcut.Factory<BaseTaskbarContext>
|
|
APP_INFO = SystemShortcut.AppInfo::new;
|
|
|
|
private final TaskbarActivityContext mContext;
|
|
private final PopupDataProvider mPopupDataProvider;
|
|
|
|
// Initialized in init.
|
|
private TaskbarControllers mControllers;
|
|
private boolean mAllowInitialSplitSelection;
|
|
|
|
public TaskbarPopupController(TaskbarActivityContext context) {
|
|
mContext = context;
|
|
mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
|
|
}
|
|
|
|
public void init(TaskbarControllers controllers) {
|
|
mControllers = controllers;
|
|
|
|
NotificationListener.addNotificationsChangedListener(mPopupDataProvider);
|
|
}
|
|
|
|
public void onDestroy() {
|
|
NotificationListener.removeNotificationsChangedListener(mPopupDataProvider);
|
|
}
|
|
|
|
@NonNull
|
|
public PopupDataProvider getPopupDataProvider() {
|
|
return mPopupDataProvider;
|
|
}
|
|
|
|
public void setDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
|
|
mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
|
|
}
|
|
|
|
public void setAllowInitialSplitSelection(boolean allowInitialSplitSelection) {
|
|
mAllowInitialSplitSelection = allowInitialSplitSelection;
|
|
}
|
|
|
|
private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
|
|
final PackageUserKey packageUserKey = new PackageUserKey(null, null);
|
|
Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
|
|
|| updatedDots.test(packageUserKey);
|
|
|
|
LauncherBindableItemsContainer.ItemOperator op = (info, v) -> {
|
|
if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
|
|
if (matcher.test(info)) {
|
|
((BubbleTextView) v).applyDotState(info, true /* animate */);
|
|
}
|
|
} else if (info instanceof FolderInfo && v instanceof FolderIcon) {
|
|
FolderInfo fi = (FolderInfo) info;
|
|
if (fi.contents.stream().anyMatch(matcher)) {
|
|
FolderDotInfo folderDotInfo = new FolderDotInfo();
|
|
for (WorkspaceItemInfo si : fi.contents) {
|
|
folderDotInfo.addDotInfo(mPopupDataProvider.getDotInfoForItem(si));
|
|
}
|
|
((FolderIcon) v).setDotInfo(folderDotInfo);
|
|
}
|
|
}
|
|
|
|
// process all the shortcuts
|
|
return false;
|
|
};
|
|
|
|
mControllers.taskbarViewController.mapOverItems(op);
|
|
Folder folder = Folder.getOpen(mContext);
|
|
if (folder != null) {
|
|
folder.iterateOverItems(op);
|
|
}
|
|
mControllers.taskbarAllAppsController.updateNotificationDots(updatedDots);
|
|
}
|
|
|
|
/**
|
|
* Shows the notifications and deep shortcuts associated with a Taskbar {@param icon}.
|
|
* @return the container if shown or null.
|
|
*/
|
|
public PopupContainerWithArrow<BaseTaskbarContext> showForIcon(BubbleTextView icon) {
|
|
BaseTaskbarContext context = ActivityContext.lookupContext(icon.getContext());
|
|
if (PopupContainerWithArrow.getOpen(context) != null) {
|
|
// There is already an items container open, so don't open this one.
|
|
icon.clearFocus();
|
|
return null;
|
|
}
|
|
ItemInfo item = (ItemInfo) icon.getTag();
|
|
if (!ShortcutUtil.supportsShortcuts(item)) {
|
|
return null;
|
|
}
|
|
|
|
PopupContainerWithArrow<BaseTaskbarContext> container;
|
|
int deepShortcutCount = mPopupDataProvider.getShortcutCountForItem(item);
|
|
// TODO(b/198438631): add support for INSTALL shortcut factory
|
|
List<SystemShortcut> systemShortcuts = getSystemShortcuts()
|
|
.map(s -> s.getShortcut(context, item, icon))
|
|
.filter(Objects::nonNull)
|
|
.collect(Collectors.toList());
|
|
|
|
if (ENABLE_MATERIAL_U_POPUP.get()) {
|
|
container = (PopupContainerWithArrow) context.getLayoutInflater().inflate(
|
|
R.layout.popup_container_material_u, context.getDragLayer(), false);
|
|
container.populateAndShowRowsMaterialU(icon, deepShortcutCount, systemShortcuts);
|
|
} else {
|
|
container = (PopupContainerWithArrow) context.getLayoutInflater().inflate(
|
|
R.layout.popup_container, context.getDragLayer(), false);
|
|
container.populateAndShow(
|
|
icon,
|
|
deepShortcutCount,
|
|
mPopupDataProvider.getNotificationKeysForItem(item),
|
|
systemShortcuts);
|
|
}
|
|
|
|
icon.clearAccessibilityFocus();
|
|
container.addOnAttachStateChangeListener(
|
|
new PopupLiveUpdateHandler<BaseTaskbarContext>(context, container) {
|
|
@Override
|
|
protected void showPopupContainerForIcon(BubbleTextView originalIcon) {
|
|
showForIcon(originalIcon);
|
|
}
|
|
});
|
|
// TODO (b/198438631): configure for taskbar/context
|
|
container.setPopupItemDragHandler(new TaskbarPopupItemDragHandler());
|
|
mControllers.taskbarDragController.addDragListener(container);
|
|
container.requestFocus();
|
|
|
|
// Make focusable to receive back events
|
|
context.onPopupVisibilityChanged(true);
|
|
container.addOnCloseCallback(() -> {
|
|
context.getDragLayer().post(() -> context.onPopupVisibilityChanged(false));
|
|
});
|
|
|
|
return container;
|
|
}
|
|
|
|
// Create a Stream of all applicable system shortcuts
|
|
private Stream<SystemShortcut.Factory> getSystemShortcuts() {
|
|
// append split options to APP_INFO shortcut, the order here will reflect in the popup
|
|
return Stream.concat(
|
|
Stream.of(APP_INFO),
|
|
mControllers.uiController.getSplitMenuOptions()
|
|
);
|
|
}
|
|
|
|
@Override
|
|
public void dumpLogs(String prefix, PrintWriter pw) {
|
|
pw.println(prefix + "TaskbarPopupController:");
|
|
|
|
mPopupDataProvider.dump(prefix + "\t", pw);
|
|
}
|
|
|
|
private class TaskbarPopupItemDragHandler implements
|
|
PopupContainerWithArrow.PopupItemDragHandler {
|
|
|
|
protected final Point mIconLastTouchPos = new Point();
|
|
|
|
TaskbarPopupItemDragHandler() {}
|
|
|
|
@Override
|
|
public boolean onTouch(View view, MotionEvent ev) {
|
|
// Touched a shortcut, update where it was touched so we can drag from there on
|
|
// long click.
|
|
switch (ev.getAction()) {
|
|
case MotionEvent.ACTION_DOWN:
|
|
case MotionEvent.ACTION_MOVE:
|
|
mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onLongClick(View v) {
|
|
// Return early if not the correct view
|
|
if (!(v.getParent() instanceof DeepShortcutView)) return false;
|
|
|
|
DeepShortcutView sv = (DeepShortcutView) v.getParent();
|
|
sv.setWillDrawIcon(false);
|
|
|
|
// Move the icon to align with the center-top of the touch point
|
|
Point iconShift = new Point();
|
|
iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
|
|
iconShift.y = mIconLastTouchPos.y - mContext.getDeviceProfile().taskbarIconSize;
|
|
|
|
((TaskbarDragController) ActivityContext.lookupContext(
|
|
v.getContext()).getDragController()).startDragOnLongClick(sv, iconShift);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a factory function representing a single "split position" menu item ("Split left,"
|
|
* "Split right," or "Split top").
|
|
* @param position A SplitPositionOption representing whether we are splitting top, left, or
|
|
* right.
|
|
* @return A factory function to be used in populating the long-press menu.
|
|
*/
|
|
SystemShortcut.Factory<BaseTaskbarContext> createSplitShortcutFactory(
|
|
SplitPositionOption position) {
|
|
return (context, itemInfo, originalView) -> new TaskbarSplitShortcut(context, itemInfo,
|
|
originalView, position, mAllowInitialSplitSelection);
|
|
}
|
|
|
|
/**
|
|
* A single menu item ("Split left," "Split right," or "Split top") that executes a split
|
|
* from the taskbar, as if the user performed a drag and drop split.
|
|
* Includes an onClick method that initiates the actual split.
|
|
*/
|
|
private static class TaskbarSplitShortcut extends
|
|
SplitShortcut<BaseTaskbarContext> {
|
|
/**
|
|
* If {@code true}, clicking this shortcut will not attempt to start a split app directly,
|
|
* but be the first app in split selection mode
|
|
*/
|
|
private final boolean mAllowInitialSplitSelection;
|
|
|
|
TaskbarSplitShortcut(BaseTaskbarContext context, ItemInfo itemInfo, View originalView,
|
|
SplitPositionOption position, boolean allowInitialSplitSelection) {
|
|
super(position.iconResId, position.textResId, context, itemInfo, originalView,
|
|
position);
|
|
mAllowInitialSplitSelection = allowInitialSplitSelection;
|
|
}
|
|
|
|
@Override
|
|
public void onClick(View view) {
|
|
// Add callbacks depending on what type of Taskbar context we're in (Taskbar or AllApps)
|
|
mTarget.onSplitScreenMenuButtonClicked();
|
|
AbstractFloatingView.closeAllOpenViews(mTarget);
|
|
|
|
// Depending on what app state we're in, we either want to initiate the split screen
|
|
// staging process or immediately launch a split with an existing app.
|
|
// - Initiate the split screen staging process
|
|
if (mAllowInitialSplitSelection) {
|
|
super.onClick(view);
|
|
return;
|
|
}
|
|
|
|
// - Immediately launch split with the running app
|
|
Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
|
|
LogUtils.getShellShareableInstanceId();
|
|
mTarget.getStatsLogManager().logger()
|
|
.withItemInfo(mItemInfo)
|
|
.withInstanceId(instanceIds.second)
|
|
.log(getLogEventForPosition(getPosition().stagePosition));
|
|
|
|
if (mItemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
|
|
WorkspaceItemInfo workspaceItemInfo = (WorkspaceItemInfo) mItemInfo;
|
|
SystemUiProxy.INSTANCE.get(mTarget).startShortcut(
|
|
workspaceItemInfo.getIntent().getPackage(),
|
|
workspaceItemInfo.getDeepShortcutId(),
|
|
getPosition().stagePosition,
|
|
null,
|
|
workspaceItemInfo.user,
|
|
instanceIds.first);
|
|
} else {
|
|
SystemUiProxy.INSTANCE.get(mTarget).startIntent(
|
|
mTarget.getSystemService(LauncherApps.class).getMainActivityLaunchIntent(
|
|
mItemInfo.getIntent().getComponent(),
|
|
null,
|
|
mItemInfo.user),
|
|
new Intent(),
|
|
getPosition().stagePosition,
|
|
null,
|
|
instanceIds.first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|