diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index cc83431bf4..55e988796f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -56,11 +56,14 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.R; +import com.android.launcher3.dot.DotInfo; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.logger.LauncherAtom; 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.popup.PopupDataProvider; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.SettingsCache; @@ -168,7 +171,7 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ new TaskbarStashController(this), new TaskbarEduController(this), new TaskbarAutohideSuspendController(this), - new TaskbarPopupController()); + new TaskbarPopupController(this)); } public void init(TaskbarSharedState sharedState) { @@ -316,6 +319,17 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ } } + @Override + public DotInfo getDotInfoForItem(ItemInfo info) { + return getPopupDataProvider().getDotInfoForItem(info); + } + + @NonNull + @Override + public PopupDataProvider getPopupDataProvider() { + return mControllers.taskbarPopupController.getPopupDataProvider(); + } + /** * Sets a new data-source for this taskbar instance */ diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java index 56730dbd70..a7c5d4dd0e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java @@ -92,6 +92,7 @@ public class TaskbarControllers { stashedHandleViewController.init(this); taskbarStashController.init(this, sharedState); taskbarEduController.init(this); + taskbarPopupController.init(this); } /** @@ -107,5 +108,6 @@ public class TaskbarControllers { taskbarViewController.onDestroy(); stashedHandleViewController.onDestroy(); taskbarAutohideSuspendController.onDestroy(); + taskbarPopupController.onDestroy(); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java index 952f597f72..06c75fc68d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java @@ -19,15 +19,25 @@ 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.util.ComponentKey; +import com.android.launcher3.util.LauncherBindableItemsContainer; +import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.views.ActivityContext; import java.util.HashMap; import java.util.Objects; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -39,11 +49,25 @@ public class TaskbarPopupController { private static final SystemShortcut.Factory APP_INFO = SystemShortcut.AppInfo::new; + private final TaskbarActivityContext mContext; private final PopupDataProvider mPopupDataProvider; - public TaskbarPopupController() { - // TODO (b/198438631): add notifications dots change listener - mPopupDataProvider = new PopupDataProvider(packageUserKey -> {}); + // 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 @@ -55,6 +79,38 @@ public class TaskbarPopupController { 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. @@ -74,6 +130,8 @@ public class TaskbarPopupController { final PopupContainerWithArrow container = (PopupContainerWithArrow) context.getLayoutInflater().inflate( R.layout.popup_container, context.getDragLayer(), false); + container.addOnAttachStateChangeListener( + new PopupLiveUpdateHandler<>(mContext, container)); // TODO (b/198438631): configure for taskbar/context container.populateAndShow(icon, diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index 59393d7b5b..c8d9fca947 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -36,6 +36,7 @@ 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.uioverrides.ApiWrapper; +import com.android.launcher3.util.LauncherBindableItemsContainer; import com.android.launcher3.views.ActivityContext; /** @@ -330,4 +331,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar // Consider the overall visibility return getVisibility() == VISIBLE; } + + protected void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) { + // map over all the shortcuts on the taskbar + for (int i = 0; i < getChildCount(); i++) { + View item = getChildAt(i); + if (op.evaluate((ItemInfo) item.getTag(), item)) { + return; + } + } + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index c47bde9dcd..96eac51410 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -35,6 +35,7 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.util.LauncherBindableItemsContainer; import com.android.launcher3.util.MultiValueAlpha; import com.android.quickstep.AnimatedFloat; @@ -243,6 +244,10 @@ public class TaskbarViewController { mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY()); } + public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) { + mTaskbarView.mapOverItems(op); + } + /** * Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's * touch bounds. diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 8154168e4f..0656125c90 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -669,6 +669,8 @@ public class Launcher extends StatefulActivity implements Launche return !isWorkspaceLoading(); } + @NonNull + @Override public PopupDataProvider getPopupDataProvider() { return mPopupDataProvider; } @@ -948,7 +950,7 @@ public class Launcher extends StatefulActivity implements Launche hideKeyboard(); logStopAndResume(false /* isResume */); mAppWidgetHost.setActivityStarted(false); - NotificationListener.removeNotificationsChangedListener(); + NotificationListener.removeNotificationsChangedListener(getPopupDataProvider()); } @Override @@ -977,7 +979,7 @@ public class Launcher extends StatefulActivity implements Launche mModel.validateModelDataOnResume(); // Set the notification listener and fetch updated notifications when we resume - NotificationListener.setNotificationsChangedListener(mPopupDataProvider); + NotificationListener.addNotificationsChangedListener(mPopupDataProvider); DiscoveryBounce.showForHomeIfNeeded(this); mAppWidgetHost.setActivityResumed(true); diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java index d27d8c7778..ebe45a55f0 100644 --- a/src/com/android/launcher3/notification/NotificationInfo.java +++ b/src/com/android/launcher3/notification/NotificationInfo.java @@ -29,12 +29,13 @@ import android.service.notification.StatusBarNotification; import android.view.View; import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.graphics.IconPalette; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.views.ActivityContext; /** * An object that contains relevant information from a {@link StatusBarNotification}. This should @@ -99,21 +100,23 @@ public class NotificationInfo implements View.OnClickListener { if (intent == null) { return; } - final Launcher launcher = Launcher.getLauncher(view.getContext()); + final ActivityContext context = ActivityContext.lookupContext(view.getContext()); Bundle activityOptions = ActivityOptions.makeClipRevealAnimation( view, 0, 0, view.getWidth(), view.getHeight()).toBundle(); try { intent.send(null, 0, null, null, null, null, activityOptions); - launcher.getStatsLogManager().logger().withItemInfo(mItemInfo) + context.getStatsLogManager().logger().withItemInfo(mItemInfo) .log(LAUNCHER_NOTIFICATION_LAUNCH_TAP); } catch (PendingIntent.CanceledException e) { e.printStackTrace(); } if (autoCancel) { - launcher.getPopupDataProvider().cancelNotification(notificationKey); + PopupDataProvider popupDataProvider = context.getPopupDataProvider(); + if (popupDataProvider != null) { + popupDataProvider.cancelNotification(notificationKey); + } } - AbstractFloatingView.closeOpenContainer(launcher, AbstractFloatingView - .TYPE_ACTION_POPUP); + AbstractFloatingView.closeOpenContainer(context, AbstractFloatingView.TYPE_ACTION_POPUP); } public Drawable getIconForBackground(Context context, int background) { diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java index e58f5fa2d5..bbeb886f3c 100644 --- a/src/com/android/launcher3/notification/NotificationListener.java +++ b/src/com/android/launcher3/notification/NotificationListener.java @@ -30,6 +30,7 @@ import android.os.Message; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Log; import android.util.Pair; @@ -66,7 +67,8 @@ public class NotificationListener extends NotificationListenerService { private static final int MSG_RANKING_UPDATE = 5; private static NotificationListener sNotificationListenerInstance = null; - private static NotificationsChangedListener sNotificationsChangedListener; + private static final ArraySet sNotificationsChangedListeners = + new ArraySet<>(); private static boolean sIsConnected; private final Handler mWorkerHandler; @@ -94,8 +96,11 @@ public class NotificationListener extends NotificationListenerService { return sIsConnected ? sNotificationListenerInstance : null; } - public static void setNotificationsChangedListener(NotificationsChangedListener listener) { - sNotificationsChangedListener = listener; + public static void addNotificationsChangedListener(NotificationsChangedListener listener) { + if (listener == null) { + return; + } + sNotificationsChangedListeners.add(listener); NotificationListener notificationListener = getInstanceIfConnected(); if (notificationListener != null) { @@ -108,8 +113,10 @@ public class NotificationListener extends NotificationListenerService { } } - public static void removeNotificationsChangedListener() { - sNotificationsChangedListener = null; + public static void removeNotificationsChangedListener(NotificationsChangedListener listener) { + if (listener != null) { + sNotificationsChangedListeners.remove(listener); + } } private boolean handleWorkerMessage(Message message) { @@ -180,23 +187,27 @@ public class NotificationListener extends NotificationListenerService { private boolean handleUiMessage(Message message) { switch (message.what) { case MSG_NOTIFICATION_POSTED: - if (sNotificationsChangedListener != null) { + if (sNotificationsChangedListeners.size() > 0) { Pair msg = (Pair) message.obj; - sNotificationsChangedListener.onNotificationPosted( - msg.first, msg.second); + for (NotificationsChangedListener listener : sNotificationsChangedListeners) { + listener.onNotificationPosted(msg.first, msg.second); + } } break; case MSG_NOTIFICATION_REMOVED: - if (sNotificationsChangedListener != null) { + if (sNotificationsChangedListeners.size() > 0) { Pair msg = (Pair) message.obj; - sNotificationsChangedListener.onNotificationRemoved( - msg.first, msg.second); + for (NotificationsChangedListener listener : sNotificationsChangedListeners) { + listener.onNotificationRemoved(msg.first, msg.second); + } } break; case MSG_NOTIFICATION_FULL_REFRESH: - if (sNotificationsChangedListener != null) { - sNotificationsChangedListener.onNotificationFullRefresh( - (List) message.obj); + if (sNotificationsChangedListeners.size() > 0) { + for (NotificationsChangedListener listener : sNotificationsChangedListeners) { + listener.onNotificationFullRefresh( + (List) message.obj); + } } break; } diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java index f9ff8a6e56..16a40576b8 100644 --- a/src/com/android/launcher3/notification/NotificationMainView.java +++ b/src/com/android/launcher3/notification/NotificationMainView.java @@ -38,11 +38,12 @@ import android.widget.TextView; import androidx.annotation.Nullable; -import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.util.Themes; +import com.android.launcher3.views.ActivityContext; /** * A {@link android.widget.FrameLayout} that contains a single notification, @@ -320,9 +321,12 @@ public class NotificationMainView extends LinearLayout { } public void onChildDismissed() { - Launcher launcher = Launcher.getLauncher(getContext()); - launcher.getPopupDataProvider().cancelNotification( - mNotificationInfo.notificationKey); - launcher.getStatsLogManager().logger().log(LAUNCHER_NOTIFICATION_DISMISSED); + ActivityContext activityContext = ActivityContext.lookupContext(getContext()); + PopupDataProvider popupDataProvider = activityContext.getPopupDataProvider(); + if (popupDataProvider == null) { + return; + } + popupDataProvider.cancelNotification(mNotificationInfo.notificationKey); + activityContext.getStatsLogManager().logger().log(LAUNCHER_NOTIFICATION_DISMISSED); } } diff --git a/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java b/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java new file mode 100644 index 0000000000..731f439d6c --- /dev/null +++ b/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java @@ -0,0 +1,89 @@ +/* + * 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.popup; + +import android.view.View; +import android.view.ViewGroup; + +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.model.data.ItemInfo; + +/** + * Utility class to handle updates while the popup is visible on the Launcher + */ +public class LauncherPopupLiveUpdateHandler extends PopupLiveUpdateHandler { + + public LauncherPopupLiveUpdateHandler( + Launcher launcher, PopupContainerWithArrow popupContainerWithArrow) { + super(launcher, popupContainerWithArrow); + } + + private View getWidgetsView(ViewGroup container) { + for (int i = container.getChildCount() - 1; i >= 0; --i) { + View systemShortcutView = container.getChildAt(i); + if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) { + return systemShortcutView; + } + } + return null; + } + + @Override + public void onWidgetsBound() { + ItemInfo itemInfo = (ItemInfo) mPopupContainerWithArrow.getOriginalIcon().getTag(); + SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mContext, itemInfo); + View widgetsView = getWidgetsView(mPopupContainerWithArrow); + if (widgetsView == null && mPopupContainerWithArrow.getWidgetContainer() != null) { + widgetsView = getWidgetsView(mPopupContainerWithArrow.getWidgetContainer()); + } + + if (widgetInfo != null && widgetsView == null) { + // We didn't have any widgets cached but now there are some, so enable the shortcut. + if (mPopupContainerWithArrow.getSystemShortcutContainer() + != mPopupContainerWithArrow) { + if (mPopupContainerWithArrow.getWidgetContainer() == null) { + mPopupContainerWithArrow.setWidgetContainer( + mPopupContainerWithArrow.inflateAndAdd( + R.layout.widget_shortcut_container, + mPopupContainerWithArrow)); + } + mPopupContainerWithArrow.initializeSystemShortcut( + R.layout.system_shortcut, + mPopupContainerWithArrow.getWidgetContainer(), + widgetInfo); + } else { + // If using the expanded system shortcut (as opposed to just the icon), we need + // to reopen the container to ensure measurements etc. all work out. While this + // could be quite janky, in practice the user would typically see a small + // flicker as the animation restarts partway through, and this is a very rare + // edge case anyway. + mPopupContainerWithArrow.close(false); + PopupContainerWithArrow.showForIcon(mPopupContainerWithArrow.getOriginalIcon()); + } + } else if (widgetInfo == null && widgetsView != null) { + // No widgets exist, but we previously added the shortcut so remove it. + if (mPopupContainerWithArrow.getSystemShortcutContainer() + != mPopupContainerWithArrow + && mPopupContainerWithArrow.getWidgetContainer() != null) { + mPopupContainerWithArrow.getWidgetContainer().removeView(widgetsView); + } else { + mPopupContainerWithArrow.close(false); + PopupContainerWithArrow.showForIcon(mPopupContainerWithArrow.getOriginalIcon()); + } + } + } +} diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 278f57ddec..aa8c70bbe8 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -60,7 +60,6 @@ import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.notification.NotificationContainer; import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.notification.NotificationKeyData; -import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; import com.android.launcher3.touch.ItemLongClickListener; @@ -72,9 +71,7 @@ import com.android.launcher3.views.BaseDragLayer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -225,7 +222,8 @@ public class PopupContainerWithArrow } private void configureForLauncher(Launcher launcher) { - addOnAttachStateChangeListener(new LiveUpdateHandler(launcher)); + addOnAttachStateChangeListener(new LauncherPopupLiveUpdateHandler( + launcher, (PopupContainerWithArrow) this)); mPopupItemDragHandler = new LauncherPopupItemDragHandler(launcher, this); mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(launcher); launcher.getDragController().addDragListener(this); @@ -329,6 +327,26 @@ public class PopupContainerWithArrow this, mShortcuts, notificationKeys)); } + protected NotificationContainer getNotificationContainer() { + return mNotificationContainer; + } + + protected BubbleTextView getOriginalIcon() { + return mOriginalIcon; + } + + protected ViewGroup getSystemShortcutContainer() { + return mSystemShortcutContainer; + } + + protected ViewGroup getWidgetContainer() { + return mWidgetContainer; + } + + protected void setWidgetContainer(ViewGroup widgetContainer) { + mWidgetContainer = widgetContainer; + } + private String getTitleForAccessibility() { return getContext().getString(mNumNotifications == 0 ? R.string.action_deep_shortcut : @@ -352,7 +370,7 @@ public class PopupContainerWithArrow } } - private void updateHiddenShortcuts() { + protected void updateHiddenShortcuts() { int allowedCount = mNotificationContainer != null ? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS; @@ -363,7 +381,7 @@ public class PopupContainerWithArrow } } - private void initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info) { + protected void initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info) { View view = inflateAndAdd( resId, container, getInsertIndexForSystemShortcut(container, info)); if (view instanceof DeepShortcutView) { @@ -436,7 +454,7 @@ public class PopupContainerWithArrow }; } - private void updateNotificationHeader() { + protected void updateNotificationHeader() { ItemInfoWithIcon itemInfo = (ItemInfoWithIcon) mOriginalIcon.getTag(); DotInfo dotInfo = mActivityContext.getDotInfoForItem(itemInfo); if (mNotificationContainer != null && dotInfo != null) { @@ -494,118 +512,6 @@ public class PopupContainerWithArrow return getOpenView(context, TYPE_ACTION_POPUP); } - /** - * Utility class to handle updates while the popup is visible (like widgets and - * notification changes) - */ - private class LiveUpdateHandler implements - PopupDataChangeListener, View.OnAttachStateChangeListener { - - private final Launcher mLauncher; - - LiveUpdateHandler(Launcher launcher) { - mLauncher = launcher; - } - - @Override - public void onViewAttachedToWindow(View view) { - mLauncher.getPopupDataProvider().setChangeListener(this); - } - - @Override - public void onViewDetachedFromWindow(View view) { - mLauncher.getPopupDataProvider().setChangeListener(null); - } - - private View getWidgetsView(ViewGroup container) { - for (int i = container.getChildCount() - 1; i >= 0; --i) { - View systemShortcutView = container.getChildAt(i); - if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) { - return systemShortcutView; - } - } - return null; - } - - @Override - public void onWidgetsBound() { - ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag(); - SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mLauncher, itemInfo); - View widgetsView = getWidgetsView(PopupContainerWithArrow.this); - if (widgetsView == null && mWidgetContainer != null) { - widgetsView = getWidgetsView(mWidgetContainer); - } - - if (widgetInfo != null && widgetsView == null) { - // We didn't have any widgets cached but now there are some, so enable the shortcut. - if (mSystemShortcutContainer != PopupContainerWithArrow.this) { - if (mWidgetContainer == null) { - mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container, - PopupContainerWithArrow.this); - } - initializeSystemShortcut(R.layout.system_shortcut, mWidgetContainer, - widgetInfo); - } else { - // If using the expanded system shortcut (as opposed to just the icon), we need - // to reopen the container to ensure measurements etc. all work out. While this - // could be quite janky, in practice the user would typically see a small - // flicker as the animation restarts partway through, and this is a very rare - // edge case anyway. - close(false); - PopupContainerWithArrow.showForIcon(mOriginalIcon); - } - } else if (widgetInfo == null && widgetsView != null) { - // No widgets exist, but we previously added the shortcut so remove it. - if (mSystemShortcutContainer - != PopupContainerWithArrow.this - && mWidgetContainer != null) { - mWidgetContainer.removeView(widgetsView); - } else { - close(false); - PopupContainerWithArrow.showForIcon(mOriginalIcon); - } - } - } - - /** - * Updates the notification header if the original icon's dot updated. - */ - @Override - public void onNotificationDotsUpdated(Predicate updatedDots) { - ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag(); - PackageUserKey packageUser = PackageUserKey.fromItemInfo(itemInfo); - if (updatedDots.test(packageUser)) { - updateNotificationHeader(); - } - } - - - @Override - public void trimNotifications(Map updatedDots) { - if (mNotificationContainer == null) { - return; - } - ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag(); - DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo)); - if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) { - // No more notifications, remove the notification views and expand all shortcuts. - mNotificationContainer.setVisibility(GONE); - updateHiddenShortcuts(); - assignMarginsAndBackgrounds(PopupContainerWithArrow.this); - updateArrowColor(); - } else { - mNotificationContainer.trimNotifications( - NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys())); - } - } - - @Override - public void onSystemShortcutsUpdated() { - close(true); - PopupContainerWithArrow.showForIcon(mOriginalIcon); - } - } - /** * Dismisses the popup if it is no longer valid */ diff --git a/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java b/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java new file mode 100644 index 0000000000..194c22faf0 --- /dev/null +++ b/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java @@ -0,0 +1,108 @@ +/* + * 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.popup; + +import static android.view.View.GONE; + +import android.content.Context; +import android.view.View; + +import com.android.launcher3.dot.DotInfo; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.notification.NotificationContainer; +import com.android.launcher3.notification.NotificationKeyData; +import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.views.ActivityContext; + +import java.util.Map; +import java.util.function.Predicate; + +/** + * Utility class to handle updates while the popup is visible (like widgets and + * notification changes) + * + * @param The activity on which the popup shows + */ +public class PopupLiveUpdateHandler implements + PopupDataProvider.PopupDataChangeListener, View.OnAttachStateChangeListener { + + protected final T mContext; + protected final PopupContainerWithArrow mPopupContainerWithArrow; + + public PopupLiveUpdateHandler( + T context, PopupContainerWithArrow popupContainerWithArrow) { + mContext = context; + mPopupContainerWithArrow = popupContainerWithArrow; + } + + @Override + public void onViewAttachedToWindow(View view) { + PopupDataProvider popupDataProvider = mContext.getPopupDataProvider(); + + if (popupDataProvider != null) { + popupDataProvider.setChangeListener(this); + } + } + + @Override + public void onViewDetachedFromWindow(View view) { + PopupDataProvider popupDataProvider = mContext.getPopupDataProvider(); + + if (popupDataProvider != null) { + popupDataProvider.setChangeListener(null); + } + } + + /** + * Updates the notification header if the original icon's dot updated. + */ + @Override + public void onNotificationDotsUpdated(Predicate updatedDots) { + ItemInfo itemInfo = (ItemInfo) mPopupContainerWithArrow.getOriginalIcon().getTag(); + PackageUserKey packageUser = PackageUserKey.fromItemInfo(itemInfo); + if (updatedDots.test(packageUser)) { + mPopupContainerWithArrow.updateNotificationHeader(); + } + } + + + @Override + public void trimNotifications(Map updatedDots) { + NotificationContainer notificationContainer = + mPopupContainerWithArrow.getNotificationContainer(); + if (notificationContainer == null) { + return; + } + ItemInfo originalInfo = (ItemInfo) mPopupContainerWithArrow.getOriginalIcon().getTag(); + DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo)); + if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) { + // No more notifications, remove the notification views and expand all shortcuts. + notificationContainer.setVisibility(GONE); + mPopupContainerWithArrow.updateHiddenShortcuts(); + mPopupContainerWithArrow.assignMarginsAndBackgrounds(mPopupContainerWithArrow); + mPopupContainerWithArrow.updateArrowColor(); + } else { + notificationContainer.trimNotifications( + NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys())); + } + } + + @Override + public void onSystemShortcutsUpdated() { + mPopupContainerWithArrow.close(true); + PopupContainerWithArrow.showForIcon(mPopupContainerWithArrow.getOriginalIcon()); + } +} diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java index 1820933509..0d8602fa3e 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java @@ -33,6 +33,7 @@ import com.android.launcher3.R; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.popup.PopupContainerWithArrow; +import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.util.ShortcutUtil; import com.android.launcher3.util.TouchController; import com.android.launcher3.views.BaseDragLayer; @@ -177,12 +178,16 @@ public class SecondaryDragLayer extends BaseDragLayer if (!ShortcutUtil.supportsShortcuts(item)) { return false; } + PopupDataProvider popupDataProvider = mActivity.getPopupDataProvider(); + if (popupDataProvider == null) { + return false; + } final PopupContainerWithArrow container = (PopupContainerWithArrow) mActivity.getLayoutInflater().inflate( R.layout.popup_container, mActivity.getDragLayer(), false); container.populateAndShow((BubbleTextView) v, - mActivity.getPopupDataProvider().getShortcutCountForItem(item), + popupDataProvider.getShortcutCountForItem(item), Collections.emptyList(), Arrays.asList(mPinnedAppsAdapter.getSystemShortcut(item), APP_INFO.getShortcut(mActivity, item))); diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java index a2e4ad6809..e09eff6f94 100644 --- a/src/com/android/launcher3/views/ActivityContext.java +++ b/src/com/android/launcher3/views/ActivityContext.java @@ -31,6 +31,7 @@ import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.util.ViewCache; /** @@ -166,4 +167,9 @@ public interface ActivityContext { // No op. }; } + + @Nullable + default PopupDataProvider getPopupDataProvider() { + return null; + } }