<lambda>null1 package com.android.launcher3
2 
3 import android.annotation.TargetApi
4 import android.os.Build
5 import android.os.Trace
6 import android.util.Log
7 import androidx.annotation.UiThread
8 import com.android.launcher3.Flags.enableSmartspaceRemovalToggle
9 import com.android.launcher3.LauncherConstants.TraceEvents
10 import com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET
11 import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID
12 import com.android.launcher3.allapps.AllAppsStore
13 import com.android.launcher3.config.FeatureFlags
14 import com.android.launcher3.debug.TestEvent
15 import com.android.launcher3.debug.TestEventEmitter
16 import com.android.launcher3.model.BgDataModel
17 import com.android.launcher3.model.StringCache
18 import com.android.launcher3.model.data.AppInfo
19 import com.android.launcher3.model.data.ItemInfo
20 import com.android.launcher3.model.data.LauncherAppWidgetInfo
21 import com.android.launcher3.model.data.WorkspaceItemInfo
22 import com.android.launcher3.popup.PopupContainerWithArrow
23 import com.android.launcher3.util.ComponentKey
24 import com.android.launcher3.util.IntArray as LIntArray
25 import com.android.launcher3.util.IntSet as LIntSet
26 import com.android.launcher3.util.PackageUserKey
27 import com.android.launcher3.util.Preconditions
28 import com.android.launcher3.util.RunnableList
29 import com.android.launcher3.util.TraceHelper
30 import com.android.launcher3.util.ViewOnDrawExecutor
31 import com.android.launcher3.widget.PendingAddWidgetInfo
32 import com.android.launcher3.widget.model.WidgetsListBaseEntry
33 import java.util.function.Predicate
34 
35 private const val TAG = "ModelCallbacks"
36 
37 class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks {
38 
39     var synchronouslyBoundPages = LIntSet()
40     var pagesToBindSynchronously = LIntSet()
41 
42     private var isFirstPagePinnedItemEnabled =
43         (BuildConfig.QSB_ON_FIRST_SCREEN && !enableSmartspaceRemovalToggle())
44 
45     var stringCache: StringCache? = null
46 
47     var pendingExecutor: ViewOnDrawExecutor? = null
48 
49     var workspaceLoading = true
50 
51     /**
52      * Refreshes the shortcuts shown on the workspace.
53      *
54      * Implementation of the method from LauncherModel.Callbacks.
55      */
56     override fun startBinding() {
57         TraceHelper.INSTANCE.beginSection("startBinding")
58         // Floating panels (except the full widget sheet) are associated with individual icons. If
59         // we are starting a fresh bind, close all such panels as all the icons are about
60         // to go away.
61         AbstractFloatingView.closeOpenViews(
62             launcher,
63             true,
64             AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv(),
65         )
66         workspaceLoading = true
67 
68         // Clear the workspace because it's going to be rebound
69         launcher.dragController.cancelDrag()
70         launcher.workspace.clearDropTargets()
71         launcher.workspace.removeAllWorkspaceScreens()
72         // Avoid clearing the widget update listeners for staying up-to-date with widget info
73         launcher.appWidgetHolder.clearWidgetViews()
74         // TODO(b/335141365): Remove this log after the bug is fixed.
75         Log.d(
76             TAG,
77             "startBinding: " +
78                 "hotseat layout was vertical: ${launcher.hotseat?.isHasVerticalHotseat}" +
79                 " and is setting to ${launcher.deviceProfile.isVerticalBarLayout}",
80         )
81         launcher.hotseat?.resetLayout(launcher.deviceProfile.isVerticalBarLayout)
82         TraceHelper.INSTANCE.endSection()
83     }
84 
85     @TargetApi(Build.VERSION_CODES.S)
86     override fun onInitialBindComplete(
87         boundPages: LIntSet,
88         pendingTasks: RunnableList,
89         onCompleteSignal: RunnableList,
90         workspaceItemCount: Int,
91         isBindSync: Boolean,
92     ) {
93         Trace.endAsyncSection(
94             TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
95             TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE,
96         )
97         synchronouslyBoundPages = boundPages
98         pagesToBindSynchronously = LIntSet()
99         clearPendingBinds()
100         if (!launcher.isInState(LauncherState.ALL_APPS)) {
101             launcher.appsView.appsStore.enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW)
102             pendingTasks.add {
103                 launcher.appsView.appsStore.disableDeferUpdates(
104                     AllAppsStore.DEFER_UPDATES_NEXT_DRAW
105                 )
106             }
107         }
108         val executor =
109             ViewOnDrawExecutor(pendingTasks) {
110                 if (pendingExecutor == it) {
111                     pendingExecutor = null
112                 }
113             }
114         pendingExecutor = executor
115 
116         if (Flags.enableWorkspaceInflation()) {
117             // Finish the executor as soon as the pending inflation is completed
118             onCompleteSignal.add(executor::markCompleted)
119         } else {
120             // Pending executor is already completed, wait until first draw to run the tasks
121             executor.attachTo(launcher)
122         }
123         launcher.bindComplete(workspaceItemCount, isBindSync)
124     }
125 
126     /**
127      * Callback saying that there aren't any more items to bind.
128      *
129      * Implementation of the method from LauncherModel.Callbacks.
130      */
131     override fun finishBindingItems(pagesBoundFirst: LIntSet?) {
132         TraceHelper.INSTANCE.beginSection("finishBindingItems")
133         val deviceProfile = launcher.deviceProfile
134         launcher.workspace.restoreInstanceStateForRemainingPages()
135         workspaceLoading = false
136         launcher.processActivityResult()
137         val currentPage =
138             if (pagesBoundFirst != null && !pagesBoundFirst.isEmpty)
139                 launcher.workspace.getPageIndexForScreenId(pagesBoundFirst.array[0])
140             else PagedView.INVALID_PAGE
141         // When undoing the removal of the last item on a page, return to that page.
142         // Since we are just resetting the current page without user interaction,
143         // override the previous page so we don't log the page switch.
144         launcher.workspace.setCurrentPage(currentPage, currentPage /* overridePrevPage */)
145         pagesToBindSynchronously = LIntSet()
146 
147         // Cache one page worth of icons
148         launcher.viewCache.setCacheSize(
149             R.layout.folder_application,
150             deviceProfile.numFolderColumns * deviceProfile.numFolderRows,
151         )
152         launcher.viewCache.setCacheSize(R.layout.folder_page, 2)
153         TraceHelper.INSTANCE.endSection()
154         launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true)
155         launcher.workspace.pageIndicator.setPauseScroll(
156             /*pause=*/ false,
157             deviceProfile.isTwoPanels,
158         )
159         TestEventEmitter.INSTANCE.get(launcher).sendEvent(TestEvent.WORKSPACE_FINISH_LOADING)
160     }
161 
162     /**
163      * Clear any pending bind callbacks. This is called when is loader is planning to perform a full
164      * rebind from scratch.
165      */
166     override fun clearPendingBinds() {
167         pendingExecutor?.cancel() ?: return
168         pendingExecutor = null
169 
170         // We might have set this flag previously and forgot to clear it.
171         launcher.appsView.appsStore.disableDeferUpdatesSilently(
172             AllAppsStore.DEFER_UPDATES_NEXT_DRAW
173         )
174     }
175 
176     override fun preAddApps() {
177         // If there's an undo snackbar, force it to complete to ensure empty screens are removed
178         // before trying to add new items.
179         launcher.modelWriter.commitDelete()
180         val snackbar =
181             AbstractFloatingView.getOpenView<AbstractFloatingView>(
182                 launcher,
183                 AbstractFloatingView.TYPE_SNACKBAR,
184             )
185         snackbar?.post { snackbar.close(true) }
186     }
187 
188     @UiThread
189     override fun bindAllApplications(
190         apps: Array<AppInfo?>?,
191         flags: Int,
192         packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?,
193     ) {
194         Preconditions.assertUIThread()
195         val hadWorkApps = launcher.appsView.shouldShowTabs()
196         launcher.appsView.appsStore.setApps(apps, flags, packageUserKeytoUidMap)
197         PopupContainerWithArrow.dismissInvalidPopup(launcher)
198         if (
199             hadWorkApps != launcher.appsView.shouldShowTabs() &&
200                 launcher.stateManager.state == LauncherState.ALL_APPS
201         ) {
202             launcher.stateManager.goToState(LauncherState.NORMAL)
203         }
204     }
205 
206     /**
207      * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
208      * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
209      */
210     override fun bindDeepShortcutMap(deepShortcutMapCopy: HashMap<ComponentKey?, Int?>?) {
211         launcher.popupDataProvider.setDeepShortcutMap(deepShortcutMapCopy)
212     }
213 
214     override fun bindIncrementalDownloadProgressUpdated(app: AppInfo?) {
215         launcher.appsView.appsStore.updateProgressBar(app)
216     }
217 
218     override fun bindWidgetsRestored(widgets: ArrayList<LauncherAppWidgetInfo?>?) {
219         launcher.workspace.widgetsRestored(widgets)
220     }
221 
222     /**
223      * Some shortcuts were updated in the background. Implementation of the method from
224      * LauncherModel.Callbacks.
225      *
226      * @param updated list of shortcuts which have changed.
227      */
228     override fun bindWorkspaceItemsChanged(updated: List<WorkspaceItemInfo?>) {
229         if (updated.isNotEmpty()) {
230             launcher.workspace.updateWorkspaceItems(updated, launcher)
231             PopupContainerWithArrow.dismissInvalidPopup(launcher)
232         }
233     }
234 
235     /**
236      * Update the state of a package, typically related to install state. Implementation of the
237      * method from LauncherModel.Callbacks.
238      */
239     override fun bindRestoreItemsChange(updates: HashSet<ItemInfo?>?) {
240         launcher.workspace.updateRestoreItems(updates, launcher)
241     }
242 
243     /**
244      * A package was uninstalled/updated. We take both the super set of packageNames in addition to
245      * specific applications to remove, the reason being that this can be called when a package is
246      * updated as well. In that scenario, we only remove specific components from the workspace and
247      * hotseat, where as package-removal should clear all items by package name.
248      */
249     override fun bindWorkspaceComponentsRemoved(matcher: Predicate<ItemInfo?>?) {
250         launcher.workspace.removeItemsByMatcher(matcher)
251         launcher.dragController.onAppsRemoved(matcher)
252         PopupContainerWithArrow.dismissInvalidPopup(launcher)
253     }
254 
255     override fun bindAllWidgets(
256         allWidgets: List<WidgetsListBaseEntry>,
257         defaultWidgets: List<WidgetsListBaseEntry>,
258     ) {
259         launcher.widgetPickerDataProvider.setWidgets(allWidgets, defaultWidgets)
260     }
261 
262     /** Returns the ids of the workspaces to bind. */
263     override fun getPagesToBindSynchronously(orderedScreenIds: LIntArray): LIntSet {
264         // If workspace binding is still in progress, getCurrentPageScreenIds won't be
265         // accurate, and we should use mSynchronouslyBoundPages that's set during initial binding.
266         val visibleIds =
267             when {
268                 !pagesToBindSynchronously.isEmpty -> pagesToBindSynchronously
269                 !workspaceLoading -> launcher.workspace.currentPageScreenIds
270                 else -> synchronouslyBoundPages
271             }
272         // Launcher IntArray has the same name as Kotlin IntArray
273         val result = LIntSet()
274         if (visibleIds.isEmpty) {
275             return result
276         }
277         val actualIds = orderedScreenIds.clone()
278         val firstId = visibleIds.first()
279         val pairId = launcher.workspace.getScreenPair(firstId)
280         // Double check that actual screenIds contains the visibleId, as empty screens are hidden
281         // in single panel.
282         if (actualIds.contains(firstId)) {
283             result.add(firstId)
284             if (launcher.deviceProfile.isTwoPanels && actualIds.contains(pairId)) {
285                 result.add(pairId)
286             }
287         } else if (
288             LauncherAppState.getIDP(launcher).supportedProfiles.any(DeviceProfile::isTwoPanels) &&
289                 actualIds.contains(pairId)
290         ) {
291             // Add the right panel if left panel is hidden when switching display, due to empty
292             // pages being hidden in single panel.
293             result.add(pairId)
294         }
295         return result
296     }
297 
298     override fun bindSmartspaceWidget() {
299         val cl: CellLayout? =
300             launcher.workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID)
301         val spanX = InvariantDeviceProfile.INSTANCE.get(launcher).numSearchContainerColumns
302 
303         if (cl?.isRegionVacant(0, 0, spanX, 1) != true) {
304             return
305         }
306 
307         val widgetsListBaseEntry: WidgetsListBaseEntry =
308             launcher.widgetPickerDataProvider.get().allWidgets.firstOrNull {
309                 item: WidgetsListBaseEntry ->
310                 item.mPkgItem.packageName == BuildConfig.APPLICATION_ID
311             } ?: return
312 
313         val info =
314             PendingAddWidgetInfo(
315                 widgetsListBaseEntry.mWidgets[0].widgetInfo,
316                 LauncherSettings.Favorites.CONTAINER_DESKTOP,
317             )
318         launcher.addPendingItem(
319             info,
320             info.container,
321             WorkspaceLayoutManager.FIRST_SCREEN_ID,
322             intArrayOf(0, 0),
323             info.spanX,
324             info.spanY,
325         )
326     }
327 
328     override fun bindScreens(orderedScreenIds: LIntArray) {
329         launcher.workspace.pageIndicator.setPauseScroll(
330             /*pause=*/ true,
331             launcher.deviceProfile.isTwoPanels,
332         )
333         val firstScreenPosition = 0
334         if (
335             (isFirstPagePinnedItemEnabled && !SHOULD_SHOW_FIRST_PAGE_WIDGET) &&
336                 orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition
337         ) {
338             orderedScreenIds.removeValue(FIRST_SCREEN_ID)
339             orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID)
340         } else if (
341             (!isFirstPagePinnedItemEnabled || SHOULD_SHOW_FIRST_PAGE_WIDGET) &&
342                 orderedScreenIds.isEmpty
343         ) {
344             // If there are no screens, we need to have an empty screen
345             launcher.workspace.addExtraEmptyScreens()
346         }
347         bindAddScreens(orderedScreenIds)
348 
349         // After we have added all the screens, if the wallpaper was locked to the default state,
350         // then notify to indicate that it can be released and a proper wallpaper offset can be
351         // computed before the next layout
352         launcher.workspace.unlockWallpaperFromDefaultPageOnNextLayout()
353     }
354 
355     override fun bindAppsAdded(
356         newScreens: LIntArray?,
357         addNotAnimated: java.util.ArrayList<ItemInfo?>?,
358         addAnimated: java.util.ArrayList<ItemInfo?>?,
359     ) {
360         // Add the new screens
361         if (newScreens != null) {
362             // newScreens can contain an empty right panel that is already bound, but not known
363             // by BgDataModel.
364             newScreens.removeAllValues(launcher.workspace.mScreenOrder)
365             bindAddScreens(newScreens)
366         }
367 
368         // We add the items without animation on non-visible pages, and with
369         // animations on the new page (which we will try and snap to).
370         if (!addNotAnimated.isNullOrEmpty()) {
371             launcher.bindItems(addNotAnimated, false)
372         }
373         if (!addAnimated.isNullOrEmpty()) {
374             launcher.bindItems(addAnimated, true)
375         }
376 
377         // Remove the extra empty screen
378         launcher.workspace.removeExtraEmptyScreen(false)
379     }
380 
381     private fun bindAddScreens(orderedScreenIdsArg: LIntArray) {
382         var orderedScreenIds = orderedScreenIdsArg
383         if (launcher.deviceProfile.isTwoPanels) {
384             if (FeatureFlags.FOLDABLE_SINGLE_PAGE.get()) {
385                 orderedScreenIds = filterTwoPanelScreenIds(orderedScreenIds)
386             } else {
387                 // Some empty pages might have been removed while the phone was in a single panel
388                 // mode, so we want to add those empty pages back.
389                 val screenIds = LIntSet.wrap(orderedScreenIds)
390                 orderedScreenIds.forEach { screenId: Int ->
391                     screenIds.add(launcher.workspace.getScreenPair(screenId))
392                 }
393                 orderedScreenIds = screenIds.array
394             }
395         }
396         orderedScreenIds
397             .filterNot { screenId ->
398                 isFirstPagePinnedItemEnabled &&
399                     !SHOULD_SHOW_FIRST_PAGE_WIDGET &&
400                     screenId == WorkspaceLayoutManager.FIRST_SCREEN_ID
401             }
402             .forEach { screenId ->
403                 launcher.workspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId)
404             }
405     }
406 
407     /**
408      * Remove odd number because they are already included when isTwoPanels and add the pair screen
409      * if not present.
410      */
411     private fun filterTwoPanelScreenIds(orderedScreenIds: LIntArray): LIntArray {
412         val screenIds = LIntSet.wrap(orderedScreenIds)
413         orderedScreenIds
414             .filter { screenId -> screenId % 2 == 1 }
415             .forEach { screenId ->
416                 screenIds.remove(screenId)
417                 // In case the pair is not added, add it
418                 if (!launcher.workspace.containsScreenId(screenId - 1)) {
419                     screenIds.add(screenId - 1)
420                 }
421             }
422         return screenIds.array
423     }
424 
425     override fun setIsFirstPagePinnedItemEnabled(isFirstPagePinnedItemEnabled: Boolean) {
426         this.isFirstPagePinnedItemEnabled = isFirstPagePinnedItemEnabled
427         launcher.workspace.bindAndInitFirstWorkspaceScreen()
428     }
429 
430     override fun bindStringCache(cache: StringCache) {
431         stringCache = cache
432         launcher.appsView.updateWorkUI()
433     }
434 
435     fun getIsFirstPagePinnedItemEnabled(): Boolean = isFirstPagePinnedItemEnabled
436 
437     override fun getItemInflater() = launcher.itemInflater
438 }
439