blob: 13062b6b1bbd1087207faa0d64b829bf7389d706 [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
Sihua Maa44f4ac2024-06-03 18:35:39 +00006import android.util.Log
Sebastian Francobd7919c2023-09-19 10:55:37 -07007import androidx.annotation.UiThread
fbaron9d08e0f2024-04-24 11:26:01 -07008import com.android.launcher3.Flags.enableSmartspaceRemovalToggle
Sebastian Franco0b461ef2023-12-08 12:20:07 -06009import com.android.launcher3.LauncherConstants.TraceEvents
fbaron9d08e0f2024-04-24 11:26:01 -070010import com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET
Sebastian Franco6dda7c72023-11-17 18:03:00 -060011import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID
Sebastian Francoe4965122023-12-01 13:27:42 -060012import com.android.launcher3.allapps.AllAppsStore
Sebastian Franco6dda7c72023-11-17 18:03:00 -060013import com.android.launcher3.config.FeatureFlags
Sebastian Francobd7919c2023-09-19 10:55:37 -070014import com.android.launcher3.model.BgDataModel
Sebastian Franco6dda7c72023-11-17 18:03:00 -060015import com.android.launcher3.model.StringCache
Sebastian Francobd7919c2023-09-19 10:55:37 -070016import com.android.launcher3.model.data.AppInfo
17import com.android.launcher3.model.data.ItemInfo
18import com.android.launcher3.model.data.LauncherAppWidgetInfo
19import com.android.launcher3.model.data.WorkspaceItemInfo
20import com.android.launcher3.popup.PopupContainerWithArrow
21import com.android.launcher3.util.ComponentKey
Sebastian Franco69fd7422023-10-06 16:48:58 -070022import com.android.launcher3.util.IntArray as LIntArray
23import com.android.launcher3.util.IntSet as LIntSet
Sebastian Francobd7919c2023-09-19 10:55:37 -070024import com.android.launcher3.util.PackageUserKey
25import com.android.launcher3.util.Preconditions
Sebastian Franco0b461ef2023-12-08 12:20:07 -060026import com.android.launcher3.util.RunnableList
Sebastian Francoe4965122023-12-01 13:27:42 -060027import com.android.launcher3.util.TraceHelper
28import com.android.launcher3.util.ViewOnDrawExecutor
Sebastian Franco50b74c32023-11-16 11:36:49 -060029import com.android.launcher3.widget.PendingAddWidgetInfo
Sebastian Francobd7919c2023-09-19 10:55:37 -070030import com.android.launcher3.widget.model.WidgetsListBaseEntry
31import java.util.function.Predicate
32
Sihua Maa44f4ac2024-06-03 18:35:39 +000033private const val TAG = "ModelCallbacks"
34
Sebastian Francobd7919c2023-09-19 10:55:37 -070035class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks {
Sebastian Franco69fd7422023-10-06 16:48:58 -070036
37 var synchronouslyBoundPages = LIntSet()
38 var pagesToBindSynchronously = LIntSet()
39
Sebastian Francoe4965122023-12-01 13:27:42 -060040 private var isFirstPagePinnedItemEnabled =
fbaron9d08e0f2024-04-24 11:26:01 -070041 (BuildConfig.QSB_ON_FIRST_SCREEN && !enableSmartspaceRemovalToggle())
Sebastian Franco6dda7c72023-11-17 18:03:00 -060042
43 var stringCache: StringCache? = null
44
Sebastian Francoe4965122023-12-01 13:27:42 -060045 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()
Sihua Maf418b2e2024-03-13 12:17:27 -070070 // Avoid clearing the widget update listeners for staying up-to-date with widget info
71 launcher.appWidgetHolder.clearWidgetViews()
Sihua Maa44f4ac2024-06-03 18:35:39 +000072 // 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 )
Sebastian Francoe4965122023-12-01 13:27:42 -060079 launcher.hotseat?.resetLayout(launcher.deviceProfile.isVerticalBarLayout)
80 TraceHelper.INSTANCE.endSection()
81 }
82
Sebastian Franco0b461ef2023-12-08 12:20:07 -060083 @TargetApi(Build.VERSION_CODES.S)
84 override fun onInitialBindComplete(
85 boundPages: LIntSet,
86 pendingTasks: RunnableList,
Sunny Goyal72a74662024-02-14 12:32:59 -080087 onCompleteSignal: RunnableList,
Sebastian Franco0b461ef2023-12-08 12:20:07 -060088 workspaceItemCount: Int,
89 isBindSync: Boolean
90 ) {
Sunny Goyale337a802024-02-14 15:07:26 -080091 if (Utilities.ATLEAST_S) {
92 Trace.endAsyncSection(
93 TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
94 TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE
95 )
96 }
Sebastian Franco0b461ef2023-12-08 12:20:07 -060097 synchronouslyBoundPages = boundPages
98 pagesToBindSynchronously = LIntSet()
99 clearPendingBinds()
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600100 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 }
Sunny Goyale337a802024-02-14 15:07:26 -0800108 val executor =
109 ViewOnDrawExecutor(pendingTasks) {
110 if (pendingExecutor == it) {
111 pendingExecutor = null
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600112 }
113 }
Sunny Goyale337a802024-02-14 15:07:26 -0800114 pendingExecutor = executor
Sunny Goyal72a74662024-02-14 12:32:59 -0800115
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 }
Sunny Goyale337a802024-02-14 15:07:26 -0800123 launcher.bindComplete(workspaceItemCount, isBindSync)
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600124 }
125
Sebastian Francoe4965122023-12-01 13:27:42 -0600126 /**
Sebastian Franco6196b902023-12-06 11:39:52 -0600127 * 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 */)
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600145 pagesToBindSynchronously = LIntSet()
Sebastian Franco6196b902023-12-06 11:39:52 -0600146
147 // Cache one page worth of icons
148 launcher.viewCache.setCacheSize(
149 R.layout.folder_application,
Thales Lima1faa4ed2023-12-01 16:48:19 +0000150 deviceProfile.numFolderColumns * deviceProfile.numFolderRows
Sebastian Franco6196b902023-12-06 11:39:52 -0600151 )
152 launcher.viewCache.setCacheSize(R.layout.folder_page, 2)
153 TraceHelper.INSTANCE.endSection()
154 launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true)
Sihua Maa44f4ac2024-06-03 18:35:39 +0000155 launcher.workspace.pageIndicator.setPauseScroll(
156 /*pause=*/ false,
157 deviceProfile.isTwoPanels
158 )
Sebastian Franco6196b902023-12-06 11:39:52 -0600159 }
160
161 /**
Sebastian Francoe4965122023-12-01 13:27:42 -0600162 * 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
Sebastian Francobd7919c2023-09-19 10:55:37 -0700175 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)
Alex Chau61d19382024-02-12 17:36:57 +0000197 if (
198 hadWorkApps != launcher.appsView.shouldShowTabs() &&
199 launcher.stateManager.state == LauncherState.ALL_APPS
200 ) {
Sebastian Francobd7919c2023-09-19 10:55:37 -0700201 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 }
Sebastian Franco69fd7422023-10-06 16:48:58 -0700257
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
Sebastian Francoe4965122023-12-01 13:27:42 -0600265 !workspaceLoading -> launcher.workspace.currentPageScreenIds
Sebastian Franco69fd7422023-10-06 16:48:58 -0700266 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 }
Sebastian Franco50b74c32023-11-16 11:36:49 -0600293
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
Sihua Maa44f4ac2024-06-03 18:35:39 +0000306 } ?: return
Sebastian Franco50b74c32023-11-16 11:36:49 -0600307
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 }
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600322
Sebastian Franco6196b902023-12-06 11:39:52 -0600323 override fun bindScreens(orderedScreenIds: LIntArray) {
Shamali Pef4b1022024-02-19 18:05:27 +0000324 launcher.workspace.pageIndicator.setPauseScroll(
325 /*pause=*/ true,
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600326 launcher.deviceProfile.isTwoPanels
327 )
328 val firstScreenPosition = 0
329 if (
Sihua Maa44f4ac2024-06-03 18:35:39 +0000330 (isFirstPagePinnedItemEnabled && !SHOULD_SHOW_FIRST_PAGE_WIDGET) &&
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600331 orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition
332 ) {
333 orderedScreenIds.removeValue(FIRST_SCREEN_ID)
334 orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID)
335 } else if (
Sihua Maa44f4ac2024-06-03 18:35:39 +0000336 (!isFirstPagePinnedItemEnabled || SHOULD_SHOW_FIRST_PAGE_WIDGET) &&
337 orderedScreenIds.isEmpty
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600338 ) {
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(
Sebastian Franco6196b902023-12-06 11:39:52 -0600351 newScreens: LIntArray?,
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600352 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
Sebastian Franco6196b902023-12-06 11:39:52 -0600376 private fun bindAddScreens(orderedScreenIdsArg: LIntArray) {
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600377 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.
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600384 val screenIds = LIntSet.wrap(orderedScreenIds)
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600385 orderedScreenIds.forEach { screenId: Int ->
386 screenIds.add(launcher.workspace.getScreenPair(screenId))
387 }
388 orderedScreenIds = screenIds.array
389 }
390 }
391 orderedScreenIds
392 .filterNot { screenId ->
Sihua Maa44f4ac2024-06-03 18:35:39 +0000393 isFirstPagePinnedItemEnabled &&
fbaron9d08e0f2024-04-24 11:26:01 -0700394 !SHOULD_SHOW_FIRST_PAGE_WIDGET &&
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600395 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 */
Sebastian Franco6196b902023-12-06 11:39:52 -0600406 private fun filterTwoPanelScreenIds(orderedScreenIds: LIntArray): LIntArray {
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600407 val screenIds = LIntSet.wrap(orderedScreenIds)
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600408 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
Sunny Goyal72a74662024-02-14 12:32:59 -0800431
432 override fun getItemInflater() = launcher.itemInflater
Sebastian Francobd7919c2023-09-19 10:55:37 -0700433}