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