• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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.TestEventEmitter
15 import com.android.launcher3.debug.TestEventEmitter.TestEvent
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.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         Trace.endAsyncSection(
92             TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
93             TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE,
94         )
95         synchronouslyBoundPages = boundPages
96         pagesToBindSynchronously = LIntSet()
97         clearPendingBinds()
98         if (!launcher.isInState(LauncherState.ALL_APPS) && !Flags.enableWorkspaceInflation()) {
99             launcher.appsView.appsStore.enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW)
100             pendingTasks.add {
101                 launcher.appsView.appsStore.disableDeferUpdates(
102                     AllAppsStore.DEFER_UPDATES_NEXT_DRAW
103                 )
104             }
105         }
106         val executor =
107             ViewOnDrawExecutor(pendingTasks) {
108                 if (pendingExecutor == it) {
109                     pendingExecutor = null
110                 }
111             }
112         pendingExecutor = executor
113 
114         if (Flags.enableWorkspaceInflation()) {
115             // Finish the executor as soon as the pending inflation is completed
116             onCompleteSignal.add(executor::markCompleted)
117         } else {
118             // Pending executor is already completed, wait until first draw to run the tasks
119             executor.attachTo(launcher)
120         }
121         launcher.bindComplete(workspaceItemCount, isBindSync)
122     }
123 
124     /**
125      * Callback saying that there aren't any more items to bind.
126      *
127      * Implementation of the method from LauncherModel.Callbacks.
128      */
129     override fun finishBindingItems(pagesBoundFirst: LIntSet?) {
130         TraceHelper.INSTANCE.beginSection("finishBindingItems")
131         val deviceProfile = launcher.deviceProfile
132         launcher.workspace.restoreInstanceStateForRemainingPages()
133         workspaceLoading = false
134         launcher.processActivityResult()
135         val currentPage =
136             if (pagesBoundFirst != null && !pagesBoundFirst.isEmpty)
137                 launcher.workspace.getPageIndexForScreenId(pagesBoundFirst.array[0])
138             else PagedView.INVALID_PAGE
139         // When undoing the removal of the last item on a page, return to that page.
140         // Since we are just resetting the current page without user interaction,
141         // override the previous page so we don't log the page switch.
142         launcher.workspace.setCurrentPage(currentPage, currentPage /* overridePrevPage */)
143         pagesToBindSynchronously = LIntSet()
144 
145         // Cache one page worth of icons
146         launcher.viewCache.setCacheSize(
147             R.layout.folder_application,
148             deviceProfile.numFolderColumns * deviceProfile.numFolderRows,
149         )
150         launcher.viewCache.setCacheSize(R.layout.folder_page, 2)
151         TraceHelper.INSTANCE.endSection()
152         launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true)
153         launcher.workspace.pageIndicator.setPauseScroll(
154             /*pause=*/ false,
155             deviceProfile.isTwoPanels,
156         )
157         TestEventEmitter.sendEvent(TestEvent.WORKSPACE_FINISH_LOADING)
158     }
159 
160     /**
161      * Clear any pending bind callbacks. This is called when is loader is planning to perform a full
162      * rebind from scratch.
163      */
164     override fun clearPendingBinds() {
165         pendingExecutor?.cancel() ?: return
166         pendingExecutor = null
167 
168         // We might have set this flag previously and forgot to clear it.
169         launcher.appsView.appsStore.disableDeferUpdatesSilently(
170             AllAppsStore.DEFER_UPDATES_NEXT_DRAW
171         )
172     }
173 
174     override fun preAddApps() {
175         // If there's an undo snackbar, force it to complete to ensure empty screens are removed
176         // before trying to add new items.
177         launcher.modelWriter.commitDelete()
178         val snackbar =
179             AbstractFloatingView.getOpenView<AbstractFloatingView>(
180                 launcher,
181                 AbstractFloatingView.TYPE_SNACKBAR,
182             )
183         snackbar?.post { snackbar.close(true) }
184     }
185 
186     @UiThread
187     override fun bindAllApplications(
188         apps: Array<AppInfo?>?,
189         flags: Int,
190         packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?,
191     ) {
192         Preconditions.assertUIThread()
193         val hadWorkApps = launcher.appsView.shouldShowTabs()
194         launcher.appsView.appsStore.setApps(apps, flags, packageUserKeytoUidMap)
195         PopupContainerWithArrow.dismissInvalidPopup(launcher)
196         if (
197             hadWorkApps != launcher.appsView.shouldShowTabs() &&
198                 launcher.stateManager.state == LauncherState.ALL_APPS
199         ) {
200             launcher.stateManager.goToState(LauncherState.NORMAL)
201         }
202     }
203 
204     /**
205      * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
206      * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
207      */
208     override fun bindDeepShortcutMap(deepShortcutMapCopy: HashMap<ComponentKey?, Int?>?) {
209         launcher.popupDataProvider.setDeepShortcutMap(deepShortcutMapCopy)
210     }
211 
212     override fun bindIncrementalDownloadProgressUpdated(app: AppInfo?) {
213         launcher.appsView.appsStore.updateProgressBar(app)
214     }
215 
216     /**
217      * Update the state of a package, typically related to install state. Implementation of the
218      * method from LauncherModel.Callbacks.
219      */
220     override fun bindItemsUpdated(updates: Set<ItemInfo>) {
221         launcher.workspace.updateContainerItems(updates, launcher)
222         PopupContainerWithArrow.dismissInvalidPopup(launcher)
223     }
224 
225     /**
226      * A package was uninstalled/updated. We take both the super set of packageNames in addition to
227      * specific applications to remove, the reason being that this can be called when a package is
228      * updated as well. In that scenario, we only remove specific components from the workspace and
229      * hotseat, where as package-removal should clear all items by package name.
230      */
231     override fun bindWorkspaceComponentsRemoved(matcher: Predicate<ItemInfo?>?) {
232         launcher.workspace.removeItemsByMatcher(matcher)
233         launcher.dragController.onAppsRemoved(matcher)
234         PopupContainerWithArrow.dismissInvalidPopup(launcher)
235     }
236 
237     override fun bindAllWidgets(allWidgets: List<WidgetsListBaseEntry>) {
238         launcher.widgetPickerDataProvider.setWidgets(allWidgets)
239     }
240 
241     /** Returns the ids of the workspaces to bind. */
242     override fun getPagesToBindSynchronously(orderedScreenIds: LIntArray): LIntSet {
243         // If workspace binding is still in progress, getCurrentPageScreenIds won't be
244         // accurate, and we should use mSynchronouslyBoundPages that's set during initial binding.
245         val visibleIds =
246             when {
247                 !pagesToBindSynchronously.isEmpty -> pagesToBindSynchronously
248                 !workspaceLoading -> launcher.workspace.currentPageScreenIds
249                 else -> synchronouslyBoundPages
250             }
251         // Launcher IntArray has the same name as Kotlin IntArray
252         val result = LIntSet()
253         if (visibleIds.isEmpty) {
254             return result
255         }
256         val actualIds = orderedScreenIds.clone()
257         val firstId = visibleIds.first()
258         val pairId = launcher.workspace.getScreenPair(firstId)
259         // Double check that actual screenIds contains the visibleId, as empty screens are hidden
260         // in single panel.
261         if (actualIds.contains(firstId)) {
262             result.add(firstId)
263             if (launcher.deviceProfile.isTwoPanels && actualIds.contains(pairId)) {
264                 result.add(pairId)
265             }
266         } else if (
267             LauncherAppState.getIDP(launcher).supportedProfiles.any(DeviceProfile::isTwoPanels) &&
268                 actualIds.contains(pairId)
269         ) {
270             // Add the right panel if left panel is hidden when switching display, due to empty
271             // pages being hidden in single panel.
272             result.add(pairId)
273         }
274         return result
275     }
276 
277     override fun bindSmartspaceWidget() {
278         val cl: CellLayout? =
279             launcher.workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID)
280         val spanX = InvariantDeviceProfile.INSTANCE.get(launcher).numSearchContainerColumns
281 
282         if (cl?.isRegionVacant(0, 0, spanX, 1) != true) {
283             return
284         }
285 
286         val widgetsListBaseEntry: WidgetsListBaseEntry =
287             launcher.widgetPickerDataProvider.get().allWidgets.firstOrNull {
288                 item: WidgetsListBaseEntry ->
289                 item.mPkgItem.packageName == BuildConfig.APPLICATION_ID
290             } ?: return
291 
292         val info =
293             PendingAddWidgetInfo(
294                 widgetsListBaseEntry.mWidgets[0].widgetInfo,
295                 LauncherSettings.Favorites.CONTAINER_DESKTOP,
296             )
297         launcher.addPendingItem(
298             info,
299             info.container,
300             WorkspaceLayoutManager.FIRST_SCREEN_ID,
301             intArrayOf(0, 0),
302             info.spanX,
303             info.spanY,
304         )
305     }
306 
307     override fun bindScreens(orderedScreenIds: LIntArray) {
308         launcher.workspace.pageIndicator.setPauseScroll(
309             /*pause=*/ true,
310             launcher.deviceProfile.isTwoPanels,
311         )
312         val firstScreenPosition = 0
313         if (
314             (isFirstPagePinnedItemEnabled && !SHOULD_SHOW_FIRST_PAGE_WIDGET) &&
315                 orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition
316         ) {
317             orderedScreenIds.removeValue(FIRST_SCREEN_ID)
318             orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID)
319         } else if (
320             (!isFirstPagePinnedItemEnabled || SHOULD_SHOW_FIRST_PAGE_WIDGET) &&
321                 orderedScreenIds.isEmpty
322         ) {
323             // If there are no screens, we need to have an empty screen
324             launcher.workspace.addExtraEmptyScreens()
325         }
326         bindAddScreens(orderedScreenIds)
327 
328         // After we have added all the screens, if the wallpaper was locked to the default state,
329         // then notify to indicate that it can be released and a proper wallpaper offset can be
330         // computed before the next layout
331         launcher.workspace.unlockWallpaperFromDefaultPageOnNextLayout()
332     }
333 
334     override fun bindAppsAdded(
335         newScreens: LIntArray?,
336         addNotAnimated: java.util.ArrayList<ItemInfo?>?,
337         addAnimated: java.util.ArrayList<ItemInfo?>?,
338     ) {
339         // Add the new screens
340         if (newScreens != null) {
341             // newScreens can contain an empty right panel that is already bound, but not known
342             // by BgDataModel.
343             newScreens.removeAllValues(launcher.workspace.mScreenOrder)
344             bindAddScreens(newScreens)
345         }
346 
347         // We add the items without animation on non-visible pages, and with
348         // animations on the new page (which we will try and snap to).
349         if (!addNotAnimated.isNullOrEmpty()) {
350             launcher.bindItems(addNotAnimated, false)
351         }
352         if (!addAnimated.isNullOrEmpty()) {
353             launcher.bindItems(addAnimated, true)
354         }
355 
356         // Remove the extra empty screen
357         launcher.workspace.removeExtraEmptyScreen(false)
358     }
359 
360     private fun bindAddScreens(orderedScreenIdsArg: LIntArray) {
361         var orderedScreenIds = orderedScreenIdsArg
362         if (launcher.deviceProfile.isTwoPanels) {
363             if (FeatureFlags.FOLDABLE_SINGLE_PAGE.get()) {
364                 orderedScreenIds = filterTwoPanelScreenIds(orderedScreenIds)
365             } else {
366                 // Some empty pages might have been removed while the phone was in a single panel
367                 // mode, so we want to add those empty pages back.
368                 val screenIds = LIntSet.wrap(orderedScreenIds)
369                 orderedScreenIds.forEach { screenId: Int ->
370                     screenIds.add(launcher.workspace.getScreenPair(screenId))
371                 }
372                 orderedScreenIds = screenIds.array
373             }
374         }
375         orderedScreenIds
376             .filterNot { screenId ->
377                 isFirstPagePinnedItemEnabled &&
378                     !SHOULD_SHOW_FIRST_PAGE_WIDGET &&
379                     screenId == WorkspaceLayoutManager.FIRST_SCREEN_ID
380             }
381             .forEach { screenId ->
382                 launcher.workspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId)
383             }
384     }
385 
386     /**
387      * Remove odd number because they are already included when isTwoPanels and add the pair screen
388      * if not present.
389      */
390     private fun filterTwoPanelScreenIds(orderedScreenIds: LIntArray): LIntArray {
391         val screenIds = LIntSet.wrap(orderedScreenIds)
392         orderedScreenIds
393             .filter { screenId -> screenId % 2 == 1 }
394             .forEach { screenId ->
395                 screenIds.remove(screenId)
396                 // In case the pair is not added, add it
397                 if (!launcher.workspace.containsScreenId(screenId - 1)) {
398                     screenIds.add(screenId - 1)
399                 }
400             }
401         return screenIds.array
402     }
403 
404     override fun setIsFirstPagePinnedItemEnabled(isFirstPagePinnedItemEnabled: Boolean) {
405         this.isFirstPagePinnedItemEnabled = isFirstPagePinnedItemEnabled
406         launcher.workspace.bindAndInitFirstWorkspaceScreen()
407     }
408 
409     override fun bindStringCache(cache: StringCache) {
410         stringCache = cache
411         launcher.appsView.updateWorkUI()
412     }
413 
414     fun getIsFirstPagePinnedItemEnabled(): Boolean = isFirstPagePinnedItemEnabled
415 
416     override fun getItemInflater() = launcher.itemInflater
417 }
418