/* * 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 android.graphics.Point; import android.view.MotionEvent; import android.view.View; import androidx.annotation.NonNull; import com.android.launcher3.BubbleTextView; 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.util.ComponentKey; import com.android.launcher3.util.LauncherBindableItemsContainer; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.views.ActivityContext; import java.io.PrintWriter; import java.util.HashMap; 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. */ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskbarController { private static final SystemShortcut.Factory APP_INFO = SystemShortcut.AppInfo::new; private final TaskbarActivityContext mContext; private final PopupDataProvider mPopupDataProvider; // Initialized in init. private TaskbarControllers mControllers; 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 deepShortcutMapCopy) { mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy); } private void updateNotificationDots(Predicate updatedDots) { final PackageUserKey packageUserKey = new PackageUserKey(null, null); Predicate 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); } } /** * Shows the notifications and deep shortcuts associated with a Taskbar {@param icon}. * @return the container if shown or null. */ public PopupContainerWithArrow showForIcon(BubbleTextView icon) { TaskbarActivityContext 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 (!PopupContainerWithArrow.canShow(icon, item)) { return null; } final PopupContainerWithArrow container = (PopupContainerWithArrow) context.getLayoutInflater().inflate( R.layout.popup_container, context.getDragLayer(), false); container.addOnAttachStateChangeListener( new PopupLiveUpdateHandler(mContext, container) { @Override protected void showPopupContainerForIcon(BubbleTextView originalIcon) { showForIcon(originalIcon); } }); // TODO (b/198438631): configure for taskbar/context container.setPopupItemDragHandler(new TaskbarPopupItemDragHandler()); container.populateAndShow(icon, mPopupDataProvider.getShortcutCountForItem(item), mPopupDataProvider.getNotificationKeysForItem(item), // TODO (b/198438631): add support for INSTALL shortcut factory Stream.of(APP_INFO) .map(s -> s.getShortcut(context, item)) .filter(Objects::nonNull) .collect(Collectors.toList())); container.requestFocus(); // Make focusable to receive back events mControllers.taskbarActivityContext.setTaskbarWindowFocusable(true); container.setOnCloseCallback(() -> { mControllers.taskbarActivityContext.getDragLayer().post( () -> mControllers.taskbarActivityContext.setTaskbarWindowFocusable(false)); container.setOnCloseCallback(null); }); return container; } @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().iconSizePx; mControllers.taskbarDragController.startDragOnLongClick(sv, iconShift); return false; } } }