mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-03-02 17:06:49 +00:00
Before we were only applying the icon.clearAccessibilityFocus() only on normal popup and not material u popups, since the flag is turned on for udc, we can move it outside. There seems to be a bug associated with either nested abstract floating views or the accessibility service where upon opening a nested abstract floating view from another the icon get the focus. This change make the bug littl less visible to use but users will still see this bug if they open the shortcut pop agian withought closing taskbar all apps. The accessibility delegate was always null when using the TaskbarOverlayContext, now we use TaskbarActivityContext Delegate when in TaskbarOverlayContext. Test: Manual Bug: 280657266 Flag: not needed Change-Id: I631e3aa6e54748c4c8ea2ac7daf5473b198e47d6
340 lines
14 KiB
Java
340 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.Utilities;
|
|
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),
|
|
Utilities.getSplitPositionOptions(mContext.getDeviceProfile())
|
|
.stream()
|
|
.map(this::createSplitShortcutFactory)
|
|
);
|
|
}
|
|
|
|
@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.
|
|
*/
|
|
private 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|