/* * Copyright (C) 2022 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.allapps; import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.launcher3.R; import com.android.launcher3.appprediction.PredictionRowView; import com.android.launcher3.dragndrop.DragOptions.PreDragCondition; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.taskbar.TaskbarControllers; import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext; import com.android.launcher3.util.PackageUserKey; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Predicate; /** * Handles the all apps overlay window initialization, updates, and its data. *

* All apps is in an application overlay window instead of taskbar's navigation bar panel window, * because a navigation bar panel is higher than UI components that all apps should be below such as * the notification tray. *

* The all apps window is created and destroyed upon opening and closing all apps, respectively. * Application data may be bound while the window does not exist, so this controller will store * the models for the next all apps session. */ public final class TaskbarAllAppsController { private TaskbarControllers mControllers; private @Nullable TaskbarOverlayContext mOverlayContext; private @Nullable TaskbarAllAppsSlideInView mSlideInView; private @Nullable TaskbarAllAppsContainerView mAppsView; private @Nullable TaskbarSearchSessionController mSearchSessionController; // Application data models. private @NonNull AppInfo[] mApps = EMPTY_ARRAY; private int mAppsModelFlags; private @NonNull List mPredictedApps = Collections.emptyList(); private @Nullable List mZeroStateSearchSuggestions; private boolean mDisallowGlobalDrag; private boolean mDisallowLongClick; private Map mPackageUserKeytoUidMap = Collections.emptyMap(); /** Initialize the controller. */ public void init(TaskbarControllers controllers, boolean allAppsVisible) { mControllers = controllers; /* * Recreate All Apps if it was open in the previous Taskbar instance (e.g. the configuration * changed). */ if (allAppsVisible) { show(false); } } /** Clean up the controller. */ public void onDestroy() { cleanUpOverlay(); } /** Updates the current {@link AppInfo} instances. */ public void setApps(@Nullable AppInfo[] apps, int flags, Map map) { mApps = apps == null ? EMPTY_ARRAY : apps; mAppsModelFlags = flags; mPackageUserKeytoUidMap = map; if (mAppsView != null) { mAppsView.getAppsStore().setApps( mApps, mAppsModelFlags, mPackageUserKeytoUidMap, false); } } public void setDisallowGlobalDrag(boolean disableDragForOverviewState) { mDisallowGlobalDrag = disableDragForOverviewState; } public void setDisallowLongClick(boolean disallowLongClick) { mDisallowLongClick = disallowLongClick; } /** Updates the current predictions. */ public void setPredictedApps(List predictedApps) { mPredictedApps = predictedApps; if (mAppsView != null) { mAppsView.getFloatingHeaderView() .findFixedRowByType(PredictionRowView.class) .setPredictedApps(mPredictedApps); } if (mSearchSessionController != null) { mSearchSessionController.setZeroStatePredictedItems(predictedApps); } } /** Updates the current search suggestions. */ public void setZeroStateSearchSuggestions(List zeroStateSearchSuggestions) { mZeroStateSearchSuggestions = zeroStateSearchSuggestions; } /** Updates the current notification dots. */ public void updateNotificationDots(Predicate updatedDots) { if (mAppsView != null) { mAppsView.getAppsStore().updateNotificationDots(updatedDots); } } /** Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay window. */ public void toggle() { toggle(false); } /** Toggles visibility of {@link TaskbarAllAppsContainerView} with the keyboard for search. */ public void toggleSearch() { toggle(true); } private void toggle(boolean showKeyboard) { if (isOpen()) { mSlideInView.close(true); } else { show(true, showKeyboard); } } /** Returns {@code true} if All Apps is open. */ public boolean isOpen() { return mSlideInView != null && mSlideInView.isOpen(); } private void show(boolean animate) { show(animate, false); } private void show(boolean animate, boolean showKeyboard) { if (mAppsView != null) { return; } mOverlayContext = mControllers.taskbarOverlayController.requestWindow(); // Initialize search session for All Apps. mSearchSessionController = TaskbarSearchSessionController.newInstance(mOverlayContext); mOverlayContext.setSearchSessionController(mSearchSessionController); mSearchSessionController.setZeroStatePredictedItems(mPredictedApps); if (mZeroStateSearchSuggestions != null) { mSearchSessionController.setZeroStateSearchSuggestions(mZeroStateSearchSuggestions); } mSearchSessionController.startLifecycle(); mSlideInView = (TaskbarAllAppsSlideInView) mOverlayContext.getLayoutInflater().inflate( R.layout.taskbar_all_apps_sheet, mOverlayContext.getDragLayer(), false); // Ensures All Apps gets touch events in case it is not the top floating view. Floating // views above it may not be able to intercept the touch, so All Apps should try to. mOverlayContext.getDragLayer().addTouchController(mSlideInView); mSlideInView.addOnCloseListener(this::cleanUpOverlay); TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController( mOverlayContext, mSlideInView, mControllers, mSearchSessionController, showKeyboard); viewController.show(animate); mAppsView = mOverlayContext.getAppsView(); mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap, false); mAppsView.getFloatingHeaderView() .findFixedRowByType(PredictionRowView.class) .setPredictedApps(mPredictedApps); // 1 alternative that would be more work: // Create a shared drag layer between taskbar and taskbarAllApps so that when dragging // starts and taskbarAllApps can close, but the drag layer that the view is being dragged in // doesn't also close mOverlayContext.getDragController().setDisallowGlobalDrag(mDisallowGlobalDrag); mOverlayContext.getDragController().setDisallowLongClick(mDisallowLongClick); } private void cleanUpOverlay() { // Floating search bar is added to the drag layer in ActivityAllAppsContainerView onAttach; // removed here as this is a special case that we remove the all apps panel. if (mAppsView != null && mOverlayContext != null && mAppsView.getSearchUiDelegate().isSearchBarFloating()) { mOverlayContext.getDragLayer().removeView(mAppsView.getSearchView()); mAppsView.getSearchUiDelegate().onDestroySearchBar(); } if (mSearchSessionController != null) { mSearchSessionController.onDestroy(); mSearchSessionController = null; } if (mOverlayContext != null) { mOverlayContext.getDragLayer().removeTouchController(mSlideInView); mOverlayContext.setSearchSessionController(null); mOverlayContext = null; } mSlideInView = null; mAppsView = null; } @VisibleForTesting public int getTaskbarAllAppsTopPadding() { // Allow null-pointer since this should only be null if the apps view is not showing. return mAppsView.getActiveRecyclerView().getClipBounds().top; } @VisibleForTesting public int getTaskbarAllAppsScroll() { // Allow null-pointer since this should only be null if the apps view is not showing. return mAppsView.getActiveRecyclerView().computeVerticalScrollOffset(); } /** @see TaskbarSearchSessionController#createPreDragConditionForSearch(View) */ @Nullable public PreDragCondition createPreDragConditionForSearch(View view) { return mSearchSessionController != null ? mSearchSessionController.createPreDragConditionForSearch(view) : null; } }