1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.taskbar.allapps;
17 
18 import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
19 
20 import android.view.View;
21 
22 import androidx.annotation.NonNull;
23 import androidx.annotation.Nullable;
24 import androidx.annotation.VisibleForTesting;
25 
26 import com.android.launcher3.R;
27 import com.android.launcher3.appprediction.PredictionRowView;
28 import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
29 import com.android.launcher3.model.data.AppInfo;
30 import com.android.launcher3.model.data.ItemInfo;
31 import com.android.launcher3.taskbar.TaskbarControllers;
32 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
33 import com.android.launcher3.util.PackageUserKey;
34 
35 import java.util.Collections;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.function.Predicate;
39 /**
40  * Handles the all apps overlay window initialization, updates, and its data.
41  * <p>
42  * All apps is in an application overlay window instead of taskbar's navigation bar panel window,
43  * because a navigation bar panel is higher than UI components that all apps should be below such as
44  * the notification tray.
45  * <p>
46  * The all apps window is created and destroyed upon opening and closing all apps, respectively.
47  * Application data may be bound while the window does not exist, so this controller will store
48  * the models for the next all apps session.
49  */
50 public final class TaskbarAllAppsController {
51 
52     private TaskbarControllers mControllers;
53     private @Nullable TaskbarOverlayContext mOverlayContext;
54     private @Nullable TaskbarAllAppsSlideInView mSlideInView;
55     private @Nullable TaskbarAllAppsContainerView mAppsView;
56     private @Nullable TaskbarSearchSessionController mSearchSessionController;
57 
58     // Application data models.
59     private @NonNull AppInfo[] mApps = EMPTY_ARRAY;
60     private int mAppsModelFlags;
61     private @NonNull List<ItemInfo> mPredictedApps = Collections.emptyList();
62     private @Nullable List<ItemInfo> mZeroStateSearchSuggestions;
63     private boolean mDisallowGlobalDrag;
64     private boolean mDisallowLongClick;
65 
66     private Map<PackageUserKey, Integer> mPackageUserKeytoUidMap = Collections.emptyMap();
67 
68     /** Initialize the controller. */
init(TaskbarControllers controllers, boolean allAppsVisible)69     public void init(TaskbarControllers controllers, boolean allAppsVisible) {
70         mControllers = controllers;
71 
72         /*
73          * Recreate All Apps if it was open in the previous Taskbar instance (e.g. the configuration
74          * changed).
75          */
76         if (allAppsVisible) {
77             show(false);
78         }
79     }
80 
81     /** Clean up the controller. */
onDestroy()82     public void onDestroy() {
83         cleanUpOverlay();
84     }
85 
86     /** Updates the current {@link AppInfo} instances. */
setApps(@ullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map)87     public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
88         mApps = apps == null ? EMPTY_ARRAY : apps;
89         mAppsModelFlags = flags;
90         mPackageUserKeytoUidMap = map;
91         if (mAppsView != null) {
92             mAppsView.getAppsStore().setApps(
93                     mApps, mAppsModelFlags, mPackageUserKeytoUidMap, false);
94         }
95     }
96 
setDisallowGlobalDrag(boolean disableDragForOverviewState)97     public void setDisallowGlobalDrag(boolean disableDragForOverviewState) {
98         mDisallowGlobalDrag = disableDragForOverviewState;
99     }
100 
setDisallowLongClick(boolean disallowLongClick)101     public void setDisallowLongClick(boolean disallowLongClick) {
102         mDisallowLongClick = disallowLongClick;
103     }
104 
105     /** Updates the current predictions. */
setPredictedApps(List<ItemInfo> predictedApps)106     public void setPredictedApps(List<ItemInfo> predictedApps) {
107         mPredictedApps = predictedApps;
108         if (mAppsView != null) {
109             mAppsView.getFloatingHeaderView()
110                     .findFixedRowByType(PredictionRowView.class)
111                     .setPredictedApps(mPredictedApps);
112         }
113         if (mSearchSessionController != null) {
114             mSearchSessionController.setZeroStatePredictedItems(predictedApps);
115         }
116     }
117 
118     /** Updates the current search suggestions. */
setZeroStateSearchSuggestions(List<ItemInfo> zeroStateSearchSuggestions)119     public void setZeroStateSearchSuggestions(List<ItemInfo> zeroStateSearchSuggestions) {
120         mZeroStateSearchSuggestions = zeroStateSearchSuggestions;
121     }
122 
123     /** Updates the current notification dots. */
updateNotificationDots(Predicate<PackageUserKey> updatedDots)124     public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
125         if (mAppsView != null) {
126             mAppsView.getAppsStore().updateNotificationDots(updatedDots);
127         }
128     }
129 
130     /** Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay window. */
toggle()131     public void toggle() {
132         toggle(false);
133     }
134 
135     /** Toggles visibility of {@link TaskbarAllAppsContainerView} with the keyboard for search. */
toggleSearch()136     public void toggleSearch() {
137         toggle(true);
138     }
139 
toggle(boolean showKeyboard)140     private void toggle(boolean showKeyboard) {
141         if (isOpen()) {
142             mSlideInView.close(true);
143         } else {
144             show(true, showKeyboard);
145         }
146     }
147 
148     /** Returns {@code true} if All Apps is open. */
isOpen()149     public boolean isOpen() {
150         return mSlideInView != null && mSlideInView.isOpen();
151     }
152 
show(boolean animate)153     private void show(boolean animate) {
154         show(animate, false);
155     }
156 
show(boolean animate, boolean showKeyboard)157     private void show(boolean animate, boolean showKeyboard) {
158         if (mAppsView != null) {
159             return;
160         }
161         mOverlayContext = mControllers.taskbarOverlayController.requestWindow();
162 
163         // Initialize search session for All Apps.
164         mSearchSessionController = TaskbarSearchSessionController.newInstance(mOverlayContext);
165         mOverlayContext.setSearchSessionController(mSearchSessionController);
166         mSearchSessionController.setZeroStatePredictedItems(mPredictedApps);
167         if (mZeroStateSearchSuggestions != null) {
168             mSearchSessionController.setZeroStateSearchSuggestions(mZeroStateSearchSuggestions);
169         }
170         mSearchSessionController.startLifecycle();
171 
172         mSlideInView = (TaskbarAllAppsSlideInView) mOverlayContext.getLayoutInflater().inflate(
173                 R.layout.taskbar_all_apps_sheet, mOverlayContext.getDragLayer(), false);
174         // Ensures All Apps gets touch events in case it is not the top floating view. Floating
175         // views above it may not be able to intercept the touch, so All Apps should try to.
176         mOverlayContext.getDragLayer().addTouchController(mSlideInView);
177         mSlideInView.addOnCloseListener(this::cleanUpOverlay);
178         TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
179                 mOverlayContext,
180                 mSlideInView,
181                 mControllers,
182                 mSearchSessionController,
183                 showKeyboard);
184 
185         viewController.show(animate);
186         mAppsView = mOverlayContext.getAppsView();
187         mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap, false);
188         mAppsView.getFloatingHeaderView()
189                 .findFixedRowByType(PredictionRowView.class)
190                 .setPredictedApps(mPredictedApps);
191         // 1 alternative that would be more work:
192         // Create a shared drag layer between taskbar and taskbarAllApps so that when dragging
193         // starts and taskbarAllApps can close, but the drag layer that the view is being dragged in
194         // doesn't also close
195         mOverlayContext.getDragController().setDisallowGlobalDrag(mDisallowGlobalDrag);
196         mOverlayContext.getDragController().setDisallowLongClick(mDisallowLongClick);
197     }
198 
cleanUpOverlay()199     private void cleanUpOverlay() {
200         // Floating search bar is added to the drag layer in ActivityAllAppsContainerView onAttach;
201         // removed here as this is a special case that we remove the all apps panel.
202         if (mAppsView != null && mOverlayContext != null
203                 && mAppsView.getSearchUiDelegate().isSearchBarFloating()) {
204             mOverlayContext.getDragLayer().removeView(mAppsView.getSearchView());
205             mAppsView.getSearchUiDelegate().onDestroySearchBar();
206         }
207         if (mSearchSessionController != null) {
208             mSearchSessionController.onDestroy();
209             mSearchSessionController = null;
210         }
211         if (mOverlayContext != null) {
212             mOverlayContext.getDragLayer().removeTouchController(mSlideInView);
213             mOverlayContext.setSearchSessionController(null);
214             mOverlayContext = null;
215         }
216         mSlideInView = null;
217         mAppsView = null;
218     }
219 
220     @VisibleForTesting
getTaskbarAllAppsTopPadding()221     public int getTaskbarAllAppsTopPadding() {
222         // Allow null-pointer since this should only be null if the apps view is not showing.
223         return mAppsView.getActiveRecyclerView().getClipBounds().top;
224     }
225 
226     @VisibleForTesting
getTaskbarAllAppsScroll()227     public int getTaskbarAllAppsScroll() {
228         // Allow null-pointer since this should only be null if the apps view is not showing.
229         return mAppsView.getActiveRecyclerView().computeVerticalScrollOffset();
230     }
231 
232     /** @see TaskbarSearchSessionController#createPreDragConditionForSearch(View) */
233     @Nullable
createPreDragConditionForSearch(View view)234     public PreDragCondition createPreDragConditionForSearch(View view) {
235         return mSearchSessionController != null
236                 ? mSearchSessionController.createPreDragConditionForSearch(view)
237                 : null;
238     }
239 }
240