blob: 7d6d154bde715b9a87989b2f295889c61704614f [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
fbaron9d08e0f2024-04-24 11:26:01 -07007import com.android.launcher3.Flags.enableSmartspaceRemovalToggle
Sebastian Franco0b461ef2023-12-08 12:20:07 -06008import com.android.launcher3.LauncherConstants.TraceEvents
fbaron9d08e0f2024-04-24 11:26:01 -07009import com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET
Sebastian Franco6dda7c72023-11-17 18:03:00 -060010import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID
Sebastian Francoe4965122023-12-01 13:27:42 -060011import com.android.launcher3.allapps.AllAppsStore
Sebastian Franco6dda7c72023-11-17 18:03:00 -060012import com.android.launcher3.config.FeatureFlags
Sebastian Francobd7919c2023-09-19 10:55:37 -070013import com.android.launcher3.model.BgDataModel
Sebastian Franco6dda7c72023-11-17 18:03:00 -060014import com.android.launcher3.model.StringCache
Sebastian Francobd7919c2023-09-19 10:55:37 -070015import com.android.launcher3.model.data.AppInfo
16import com.android.launcher3.model.data.ItemInfo
17import com.android.launcher3.model.data.LauncherAppWidgetInfo
18import com.android.launcher3.model.data.WorkspaceItemInfo
19import com.android.launcher3.popup.PopupContainerWithArrow
20import com.android.launcher3.util.ComponentKey
Sebastian Franco69fd7422023-10-06 16:48:58 -070021import com.android.launcher3.util.IntArray as LIntArray
22import com.android.launcher3.util.IntSet as LIntSet
Sebastian Francobd7919c2023-09-19 10:55:37 -070023import com.android.launcher3.util.PackageUserKey
24import com.android.launcher3.util.Preconditions
Sebastian Franco0b461ef2023-12-08 12:20:07 -060025import com.android.launcher3.util.RunnableList
Sebastian Francoe4965122023-12-01 13:27:42 -060026import com.android.launcher3.util.TraceHelper
27import com.android.launcher3.util.ViewOnDrawExecutor
Sebastian Franco50b74c32023-11-16 11:36:49 -060028import com.android.launcher3.widget.PendingAddWidgetInfo
Sebastian Francobd7919c2023-09-19 10:55:37 -070029import com.android.launcher3.widget.model.WidgetsListBaseEntry
30import java.util.function.Predicate
31
32class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks {
Sebastian Franco69fd7422023-10-06 16:48:58 -070033
34 var synchronouslyBoundPages = LIntSet()
35 var pagesToBindSynchronously = LIntSet()
36
Sebastian Francoe4965122023-12-01 13:27:42 -060037 private var isFirstPagePinnedItemEnabled =
fbaron9d08e0f2024-04-24 11:26:01 -070038 (BuildConfig.QSB_ON_FIRST_SCREEN && !enableSmartspaceRemovalToggle())
Sebastian Franco6dda7c72023-11-17 18:03:00 -060039
40 var stringCache: StringCache? = null
41
Sebastian Francoe4965122023-12-01 13:27:42 -060042 var pendingExecutor: ViewOnDrawExecutor? = null
43
44 var workspaceLoading = true
45
46 /**
47 * Refreshes the shortcuts shown on the workspace.
48 *
49 * Implementation of the method from LauncherModel.Callbacks.
50 */
51 override fun startBinding() {
52 TraceHelper.INSTANCE.beginSection("startBinding")
53 // Floating panels (except the full widget sheet) are associated with individual icons. If
54 // we are starting a fresh bind, close all such panels as all the icons are about
55 // to go away.
56 AbstractFloatingView.closeOpenViews(
57 launcher,
58 true,
59 AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv()
60 )
61 workspaceLoading = true
62
63 // Clear the workspace because it's going to be rebound
64 launcher.dragController.cancelDrag()
65 launcher.workspace.clearDropTargets()
66 launcher.workspace.removeAllWorkspaceScreens()
Sihua Maf418b2e2024-03-13 12:17:27 -070067 // Avoid clearing the widget update listeners for staying up-to-date with widget info
68 launcher.appWidgetHolder.clearWidgetViews()
Sebastian Francoe4965122023-12-01 13:27:42 -060069 launcher.hotseat?.resetLayout(launcher.deviceProfile.isVerticalBarLayout)
70 TraceHelper.INSTANCE.endSection()
71 }
72
Sebastian Franco0b461ef2023-12-08 12:20:07 -060073 @TargetApi(Build.VERSION_CODES.S)
74 override fun onInitialBindComplete(
75 boundPages: LIntSet,
76 pendingTasks: RunnableList,
Sunny Goyal72a74662024-02-14 12:32:59 -080077 onCompleteSignal: RunnableList,
Sebastian Franco0b461ef2023-12-08 12:20:07 -060078 workspaceItemCount: Int,
79 isBindSync: Boolean
80 ) {
Sunny Goyale337a802024-02-14 15:07:26 -080081 if (Utilities.ATLEAST_S) {
82 Trace.endAsyncSection(
83 TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
84 TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE
85 )
86 }
Sebastian Franco0b461ef2023-12-08 12:20:07 -060087 synchronouslyBoundPages = boundPages
88 pagesToBindSynchronously = LIntSet()
89 clearPendingBinds()
Sebastian Franco0b461ef2023-12-08 12:20:07 -060090 if (!launcher.isInState(LauncherState.ALL_APPS)) {
91 launcher.appsView.appsStore.enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW)
92 pendingTasks.add {
93 launcher.appsView.appsStore.disableDeferUpdates(
94 AllAppsStore.DEFER_UPDATES_NEXT_DRAW
95 )
96 }
97 }
Sunny Goyale337a802024-02-14 15:07:26 -080098 val executor =
99 ViewOnDrawExecutor(pendingTasks) {
100 if (pendingExecutor == it) {
101 pendingExecutor = null
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600102 }
103 }
Sunny Goyale337a802024-02-14 15:07:26 -0800104 pendingExecutor = executor
Sunny Goyal72a74662024-02-14 12:32:59 -0800105
106 if (Flags.enableWorkspaceInflation()) {
107 // Finish the executor as soon as the pending inflation is completed
108 onCompleteSignal.add(executor::markCompleted)
109 } else {
110 // Pending executor is already completed, wait until first draw to run the tasks
111 executor.attachTo(launcher)
112 }
Sunny Goyale337a802024-02-14 15:07:26 -0800113 launcher.bindComplete(workspaceItemCount, isBindSync)
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600114 }
115
Sebastian Francoe4965122023-12-01 13:27:42 -0600116 /**
Sebastian Franco6196b902023-12-06 11:39:52 -0600117 * Callback saying that there aren't any more items to bind.
118 *
119 * Implementation of the method from LauncherModel.Callbacks.
120 */
121 override fun finishBindingItems(pagesBoundFirst: LIntSet?) {
122 TraceHelper.INSTANCE.beginSection("finishBindingItems")
123 val deviceProfile = launcher.deviceProfile
124 launcher.workspace.restoreInstanceStateForRemainingPages()
125 workspaceLoading = false
126 launcher.processActivityResult()
127 val currentPage =
128 if (pagesBoundFirst != null && !pagesBoundFirst.isEmpty)
129 launcher.workspace.getPageIndexForScreenId(pagesBoundFirst.array[0])
130 else PagedView.INVALID_PAGE
131 // When undoing the removal of the last item on a page, return to that page.
132 // Since we are just resetting the current page without user interaction,
133 // override the previous page so we don't log the page switch.
134 launcher.workspace.setCurrentPage(currentPage, currentPage /* overridePrevPage */)
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600135 pagesToBindSynchronously = LIntSet()
Sebastian Franco6196b902023-12-06 11:39:52 -0600136
137 // Cache one page worth of icons
138 launcher.viewCache.setCacheSize(
139 R.layout.folder_application,
Thales Lima1faa4ed2023-12-01 16:48:19 +0000140 deviceProfile.numFolderColumns * deviceProfile.numFolderRows
Sebastian Franco6196b902023-12-06 11:39:52 -0600141 )
142 launcher.viewCache.setCacheSize(R.layout.folder_page, 2)
143 TraceHelper.INSTANCE.endSection()
144 launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true)
Shamali Pef4b1022024-02-19 18:05:27 +0000145 launcher.workspace.pageIndicator.setPauseScroll(/*pause=*/ false, deviceProfile.isTwoPanels)
Sebastian Franco6196b902023-12-06 11:39:52 -0600146 }
147
148 /**
Sebastian Francoe4965122023-12-01 13:27:42 -0600149 * Clear any pending bind callbacks. This is called when is loader is planning to perform a full
150 * rebind from scratch.
151 */
152 override fun clearPendingBinds() {
153 pendingExecutor?.cancel() ?: return
154 pendingExecutor = null
155
156 // We might have set this flag previously and forgot to clear it.
157 launcher.appsView.appsStore.disableDeferUpdatesSilently(
158 AllAppsStore.DEFER_UPDATES_NEXT_DRAW
159 )
160 }
161
Sebastian Francobd7919c2023-09-19 10:55:37 -0700162 override fun preAddApps() {
163 // If there's an undo snackbar, force it to complete to ensure empty screens are removed
164 // before trying to add new items.
165 launcher.modelWriter.commitDelete()
166 val snackbar =
167 AbstractFloatingView.getOpenView<AbstractFloatingView>(
168 launcher,
169 AbstractFloatingView.TYPE_SNACKBAR
170 )
171 snackbar?.post { snackbar.close(true) }
172 }
173
174 @UiThread
175 override fun bindAllApplications(
176 apps: Array<AppInfo?>?,
177 flags: Int,
178 packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?
179 ) {
180 Preconditions.assertUIThread()
181 val hadWorkApps = launcher.appsView.shouldShowTabs()
182 launcher.appsView.appsStore.setApps(apps, flags, packageUserKeytoUidMap)
183 PopupContainerWithArrow.dismissInvalidPopup(launcher)
Alex Chau61d19382024-02-12 17:36:57 +0000184 if (
185 hadWorkApps != launcher.appsView.shouldShowTabs() &&
186 launcher.stateManager.state == LauncherState.ALL_APPS
187 ) {
Sebastian Francobd7919c2023-09-19 10:55:37 -0700188 launcher.stateManager.goToState(LauncherState.NORMAL)
189 }
190 }
191
192 /**
193 * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
194 * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
195 */
196 override fun bindDeepShortcutMap(deepShortcutMapCopy: HashMap<ComponentKey?, Int?>?) {
197 launcher.popupDataProvider.setDeepShortcutMap(deepShortcutMapCopy)
198 }
199
200 override fun bindIncrementalDownloadProgressUpdated(app: AppInfo?) {
201 launcher.appsView.appsStore.updateProgressBar(app)
202 }
203
204 override fun bindWidgetsRestored(widgets: ArrayList<LauncherAppWidgetInfo?>?) {
205 launcher.workspace.widgetsRestored(widgets)
206 }
207
208 /**
209 * Some shortcuts were updated in the background. Implementation of the method from
210 * LauncherModel.Callbacks.
211 *
212 * @param updated list of shortcuts which have changed.
213 */
214 override fun bindWorkspaceItemsChanged(updated: List<WorkspaceItemInfo?>) {
215 if (updated.isNotEmpty()) {
216 launcher.workspace.updateWorkspaceItems(updated, launcher)
217 PopupContainerWithArrow.dismissInvalidPopup(launcher)
218 }
219 }
220
221 /**
222 * Update the state of a package, typically related to install state. Implementation of the
223 * method from LauncherModel.Callbacks.
224 */
225 override fun bindRestoreItemsChange(updates: HashSet<ItemInfo?>?) {
226 launcher.workspace.updateRestoreItems(updates, launcher)
227 }
228
229 /**
230 * A package was uninstalled/updated. We take both the super set of packageNames in addition to
231 * specific applications to remove, the reason being that this can be called when a package is
232 * updated as well. In that scenario, we only remove specific components from the workspace and
233 * hotseat, where as package-removal should clear all items by package name.
234 */
235 override fun bindWorkspaceComponentsRemoved(matcher: Predicate<ItemInfo?>?) {
236 launcher.workspace.removeItemsByMatcher(matcher)
237 launcher.dragController.onAppsRemoved(matcher)
238 PopupContainerWithArrow.dismissInvalidPopup(launcher)
239 }
240
241 override fun bindAllWidgets(allWidgets: List<WidgetsListBaseEntry?>?) {
242 launcher.popupDataProvider.allWidgets = allWidgets
243 }
Sebastian Franco69fd7422023-10-06 16:48:58 -0700244
245 /** Returns the ids of the workspaces to bind. */
246 override fun getPagesToBindSynchronously(orderedScreenIds: LIntArray): LIntSet {
247 // If workspace binding is still in progress, getCurrentPageScreenIds won't be
248 // accurate, and we should use mSynchronouslyBoundPages that's set during initial binding.
249 val visibleIds =
250 when {
251 !pagesToBindSynchronously.isEmpty -> pagesToBindSynchronously
Sebastian Francoe4965122023-12-01 13:27:42 -0600252 !workspaceLoading -> launcher.workspace.currentPageScreenIds
Sebastian Franco69fd7422023-10-06 16:48:58 -0700253 else -> synchronouslyBoundPages
254 }
255 // Launcher IntArray has the same name as Kotlin IntArray
256 val result = LIntSet()
257 if (visibleIds.isEmpty) {
258 return result
259 }
260 val actualIds = orderedScreenIds.clone()
261 val firstId = visibleIds.first()
262 val pairId = launcher.workspace.getScreenPair(firstId)
263 // Double check that actual screenIds contains the visibleId, as empty screens are hidden
264 // in single panel.
265 if (actualIds.contains(firstId)) {
266 result.add(firstId)
267 if (launcher.deviceProfile.isTwoPanels && actualIds.contains(pairId)) {
268 result.add(pairId)
269 }
270 } else if (
271 LauncherAppState.getIDP(launcher).supportedProfiles.any(DeviceProfile::isTwoPanels) &&
272 actualIds.contains(pairId)
273 ) {
274 // Add the right panel if left panel is hidden when switching display, due to empty
275 // pages being hidden in single panel.
276 result.add(pairId)
277 }
278 return result
279 }
Sebastian Franco50b74c32023-11-16 11:36:49 -0600280
281 override fun bindSmartspaceWidget() {
282 val cl: CellLayout? =
283 launcher.workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID)
284 val spanX = InvariantDeviceProfile.INSTANCE.get(launcher).numSearchContainerColumns
285
286 if (cl?.isRegionVacant(0, 0, spanX, 1) != true) {
287 return
288 }
289
290 val widgetsListBaseEntry: WidgetsListBaseEntry =
291 launcher.popupDataProvider.allWidgets.firstOrNull { item: WidgetsListBaseEntry ->
292 item.mPkgItem.packageName == BuildConfig.APPLICATION_ID
293 }
294 ?: return
295
296 val info =
297 PendingAddWidgetInfo(
298 widgetsListBaseEntry.mWidgets[0].widgetInfo,
299 LauncherSettings.Favorites.CONTAINER_DESKTOP
300 )
301 launcher.addPendingItem(
302 info,
303 info.container,
304 WorkspaceLayoutManager.FIRST_SCREEN_ID,
305 intArrayOf(0, 0),
306 info.spanX,
307 info.spanY
308 )
309 }
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600310
Sebastian Franco6196b902023-12-06 11:39:52 -0600311 override fun bindScreens(orderedScreenIds: LIntArray) {
Shamali Pef4b1022024-02-19 18:05:27 +0000312 launcher.workspace.pageIndicator.setPauseScroll(
313 /*pause=*/ true,
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600314 launcher.deviceProfile.isTwoPanels
315 )
316 val firstScreenPosition = 0
317 if (
fbaron9d08e0f2024-04-24 11:26:01 -0700318 (isFirstPagePinnedItemEnabled &&
319 !SHOULD_SHOW_FIRST_PAGE_WIDGET) &&
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600320 orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition
321 ) {
322 orderedScreenIds.removeValue(FIRST_SCREEN_ID)
323 orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID)
324 } else if (
fbaron9d08e0f2024-04-24 11:26:01 -0700325 (!isFirstPagePinnedItemEnabled ||
326 SHOULD_SHOW_FIRST_PAGE_WIDGET)
327 && orderedScreenIds.isEmpty
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600328 ) {
329 // If there are no screens, we need to have an empty screen
330 launcher.workspace.addExtraEmptyScreens()
331 }
332 bindAddScreens(orderedScreenIds)
333
334 // After we have added all the screens, if the wallpaper was locked to the default state,
335 // then notify to indicate that it can be released and a proper wallpaper offset can be
336 // computed before the next layout
337 launcher.workspace.unlockWallpaperFromDefaultPageOnNextLayout()
338 }
339
340 override fun bindAppsAdded(
Sebastian Franco6196b902023-12-06 11:39:52 -0600341 newScreens: LIntArray?,
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600342 addNotAnimated: java.util.ArrayList<ItemInfo?>?,
343 addAnimated: java.util.ArrayList<ItemInfo?>?
344 ) {
345 // Add the new screens
346 if (newScreens != null) {
347 // newScreens can contain an empty right panel that is already bound, but not known
348 // by BgDataModel.
349 newScreens.removeAllValues(launcher.workspace.mScreenOrder)
350 bindAddScreens(newScreens)
351 }
352
353 // We add the items without animation on non-visible pages, and with
354 // animations on the new page (which we will try and snap to).
355 if (!addNotAnimated.isNullOrEmpty()) {
356 launcher.bindItems(addNotAnimated, false)
357 }
358 if (!addAnimated.isNullOrEmpty()) {
359 launcher.bindItems(addAnimated, true)
360 }
361
362 // Remove the extra empty screen
363 launcher.workspace.removeExtraEmptyScreen(false)
364 }
365
Sebastian Franco6196b902023-12-06 11:39:52 -0600366 private fun bindAddScreens(orderedScreenIdsArg: LIntArray) {
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600367 var orderedScreenIds = orderedScreenIdsArg
368 if (launcher.deviceProfile.isTwoPanels) {
369 if (FeatureFlags.FOLDABLE_SINGLE_PAGE.get()) {
370 orderedScreenIds = filterTwoPanelScreenIds(orderedScreenIds)
371 } else {
372 // Some empty pages might have been removed while the phone was in a single panel
373 // mode, so we want to add those empty pages back.
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600374 val screenIds = LIntSet.wrap(orderedScreenIds)
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600375 orderedScreenIds.forEach { screenId: Int ->
376 screenIds.add(launcher.workspace.getScreenPair(screenId))
377 }
378 orderedScreenIds = screenIds.array
379 }
380 }
381 orderedScreenIds
382 .filterNot { screenId ->
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600383 isFirstPagePinnedItemEnabled &&
fbaron9d08e0f2024-04-24 11:26:01 -0700384 !SHOULD_SHOW_FIRST_PAGE_WIDGET &&
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600385 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}