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