blob: 0e3800701a5378ab624601fd39b1d8ca1f02301f [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()
66 launcher.appWidgetHolder.clearViews()
67 launcher.hotseat?.resetLayout(launcher.deviceProfile.isVerticalBarLayout)
68 TraceHelper.INSTANCE.endSection()
69 }
70
Sebastian Franco0b461ef2023-12-08 12:20:07 -060071 @TargetApi(Build.VERSION_CODES.S)
72 override fun onInitialBindComplete(
73 boundPages: LIntSet,
74 pendingTasks: RunnableList,
75 workspaceItemCount: Int,
76 isBindSync: Boolean
77 ) {
Sunny Goyale337a802024-02-14 15:07:26 -080078 if (Utilities.ATLEAST_S) {
79 Trace.endAsyncSection(
80 TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
81 TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE
82 )
83 }
Sebastian Franco0b461ef2023-12-08 12:20:07 -060084 synchronouslyBoundPages = boundPages
85 pagesToBindSynchronously = LIntSet()
86 clearPendingBinds()
Sebastian Franco0b461ef2023-12-08 12:20:07 -060087 if (!launcher.isInState(LauncherState.ALL_APPS)) {
88 launcher.appsView.appsStore.enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW)
89 pendingTasks.add {
90 launcher.appsView.appsStore.disableDeferUpdates(
91 AllAppsStore.DEFER_UPDATES_NEXT_DRAW
92 )
93 }
94 }
Sunny Goyale337a802024-02-14 15:07:26 -080095 val executor =
96 ViewOnDrawExecutor(pendingTasks) {
97 if (pendingExecutor == it) {
98 pendingExecutor = null
Sebastian Franco0b461ef2023-12-08 12:20:07 -060099 }
100 }
Sunny Goyale337a802024-02-14 15:07:26 -0800101 pendingExecutor = executor
102 executor.attachTo(launcher)
103 launcher.bindComplete(workspaceItemCount, isBindSync)
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600104 }
105
Sebastian Francoe4965122023-12-01 13:27:42 -0600106 /**
Sebastian Franco6196b902023-12-06 11:39:52 -0600107 * Callback saying that there aren't any more items to bind.
108 *
109 * Implementation of the method from LauncherModel.Callbacks.
110 */
111 override fun finishBindingItems(pagesBoundFirst: LIntSet?) {
112 TraceHelper.INSTANCE.beginSection("finishBindingItems")
113 val deviceProfile = launcher.deviceProfile
114 launcher.workspace.restoreInstanceStateForRemainingPages()
115 workspaceLoading = false
116 launcher.processActivityResult()
117 val currentPage =
118 if (pagesBoundFirst != null && !pagesBoundFirst.isEmpty)
119 launcher.workspace.getPageIndexForScreenId(pagesBoundFirst.array[0])
120 else PagedView.INVALID_PAGE
121 // When undoing the removal of the last item on a page, return to that page.
122 // Since we are just resetting the current page without user interaction,
123 // override the previous page so we don't log the page switch.
124 launcher.workspace.setCurrentPage(currentPage, currentPage /* overridePrevPage */)
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600125 pagesToBindSynchronously = LIntSet()
Sebastian Franco6196b902023-12-06 11:39:52 -0600126
127 // Cache one page worth of icons
128 launcher.viewCache.setCacheSize(
129 R.layout.folder_application,
Thales Lima1faa4ed2023-12-01 16:48:19 +0000130 deviceProfile.numFolderColumns * deviceProfile.numFolderRows
Sebastian Franco6196b902023-12-06 11:39:52 -0600131 )
132 launcher.viewCache.setCacheSize(R.layout.folder_page, 2)
133 TraceHelper.INSTANCE.endSection()
134 launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true)
135 launcher.workspace.pageIndicator.setAreScreensBinding(false, deviceProfile.isTwoPanels)
136 }
137
138 /**
Sebastian Francoe4965122023-12-01 13:27:42 -0600139 * Clear any pending bind callbacks. This is called when is loader is planning to perform a full
140 * rebind from scratch.
141 */
142 override fun clearPendingBinds() {
143 pendingExecutor?.cancel() ?: return
144 pendingExecutor = null
145
146 // We might have set this flag previously and forgot to clear it.
147 launcher.appsView.appsStore.disableDeferUpdatesSilently(
148 AllAppsStore.DEFER_UPDATES_NEXT_DRAW
149 )
150 }
151
Sebastian Francobd7919c2023-09-19 10:55:37 -0700152 override fun preAddApps() {
153 // If there's an undo snackbar, force it to complete to ensure empty screens are removed
154 // before trying to add new items.
155 launcher.modelWriter.commitDelete()
156 val snackbar =
157 AbstractFloatingView.getOpenView<AbstractFloatingView>(
158 launcher,
159 AbstractFloatingView.TYPE_SNACKBAR
160 )
161 snackbar?.post { snackbar.close(true) }
162 }
163
164 @UiThread
165 override fun bindAllApplications(
166 apps: Array<AppInfo?>?,
167 flags: Int,
168 packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?
169 ) {
170 Preconditions.assertUIThread()
171 val hadWorkApps = launcher.appsView.shouldShowTabs()
172 launcher.appsView.appsStore.setApps(apps, flags, packageUserKeytoUidMap)
173 PopupContainerWithArrow.dismissInvalidPopup(launcher)
Alex Chau61d19382024-02-12 17:36:57 +0000174 if (
175 hadWorkApps != launcher.appsView.shouldShowTabs() &&
176 launcher.stateManager.state == LauncherState.ALL_APPS
177 ) {
Sebastian Francobd7919c2023-09-19 10:55:37 -0700178 launcher.stateManager.goToState(LauncherState.NORMAL)
179 }
180 }
181
182 /**
183 * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
184 * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
185 */
186 override fun bindDeepShortcutMap(deepShortcutMapCopy: HashMap<ComponentKey?, Int?>?) {
187 launcher.popupDataProvider.setDeepShortcutMap(deepShortcutMapCopy)
188 }
189
190 override fun bindIncrementalDownloadProgressUpdated(app: AppInfo?) {
191 launcher.appsView.appsStore.updateProgressBar(app)
192 }
193
194 override fun bindWidgetsRestored(widgets: ArrayList<LauncherAppWidgetInfo?>?) {
195 launcher.workspace.widgetsRestored(widgets)
196 }
197
198 /**
199 * Some shortcuts were updated in the background. Implementation of the method from
200 * LauncherModel.Callbacks.
201 *
202 * @param updated list of shortcuts which have changed.
203 */
204 override fun bindWorkspaceItemsChanged(updated: List<WorkspaceItemInfo?>) {
205 if (updated.isNotEmpty()) {
206 launcher.workspace.updateWorkspaceItems(updated, launcher)
207 PopupContainerWithArrow.dismissInvalidPopup(launcher)
208 }
209 }
210
211 /**
212 * Update the state of a package, typically related to install state. Implementation of the
213 * method from LauncherModel.Callbacks.
214 */
215 override fun bindRestoreItemsChange(updates: HashSet<ItemInfo?>?) {
216 launcher.workspace.updateRestoreItems(updates, launcher)
217 }
218
219 /**
220 * A package was uninstalled/updated. We take both the super set of packageNames in addition to
221 * specific applications to remove, the reason being that this can be called when a package is
222 * updated as well. In that scenario, we only remove specific components from the workspace and
223 * hotseat, where as package-removal should clear all items by package name.
224 */
225 override fun bindWorkspaceComponentsRemoved(matcher: Predicate<ItemInfo?>?) {
226 launcher.workspace.removeItemsByMatcher(matcher)
227 launcher.dragController.onAppsRemoved(matcher)
228 PopupContainerWithArrow.dismissInvalidPopup(launcher)
229 }
230
231 override fun bindAllWidgets(allWidgets: List<WidgetsListBaseEntry?>?) {
232 launcher.popupDataProvider.allWidgets = allWidgets
233 }
Sebastian Franco69fd7422023-10-06 16:48:58 -0700234
235 /** Returns the ids of the workspaces to bind. */
236 override fun getPagesToBindSynchronously(orderedScreenIds: LIntArray): LIntSet {
237 // If workspace binding is still in progress, getCurrentPageScreenIds won't be
238 // accurate, and we should use mSynchronouslyBoundPages that's set during initial binding.
239 val visibleIds =
240 when {
241 !pagesToBindSynchronously.isEmpty -> pagesToBindSynchronously
Sebastian Francoe4965122023-12-01 13:27:42 -0600242 !workspaceLoading -> launcher.workspace.currentPageScreenIds
Sebastian Franco69fd7422023-10-06 16:48:58 -0700243 else -> synchronouslyBoundPages
244 }
245 // Launcher IntArray has the same name as Kotlin IntArray
246 val result = LIntSet()
247 if (visibleIds.isEmpty) {
248 return result
249 }
250 val actualIds = orderedScreenIds.clone()
251 val firstId = visibleIds.first()
252 val pairId = launcher.workspace.getScreenPair(firstId)
253 // Double check that actual screenIds contains the visibleId, as empty screens are hidden
254 // in single panel.
255 if (actualIds.contains(firstId)) {
256 result.add(firstId)
257 if (launcher.deviceProfile.isTwoPanels && actualIds.contains(pairId)) {
258 result.add(pairId)
259 }
260 } else if (
261 LauncherAppState.getIDP(launcher).supportedProfiles.any(DeviceProfile::isTwoPanels) &&
262 actualIds.contains(pairId)
263 ) {
264 // Add the right panel if left panel is hidden when switching display, due to empty
265 // pages being hidden in single panel.
266 result.add(pairId)
267 }
268 return result
269 }
Sebastian Franco50b74c32023-11-16 11:36:49 -0600270
271 override fun bindSmartspaceWidget() {
272 val cl: CellLayout? =
273 launcher.workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID)
274 val spanX = InvariantDeviceProfile.INSTANCE.get(launcher).numSearchContainerColumns
275
276 if (cl?.isRegionVacant(0, 0, spanX, 1) != true) {
277 return
278 }
279
280 val widgetsListBaseEntry: WidgetsListBaseEntry =
281 launcher.popupDataProvider.allWidgets.firstOrNull { item: WidgetsListBaseEntry ->
282 item.mPkgItem.packageName == BuildConfig.APPLICATION_ID
283 }
284 ?: return
285
286 val info =
287 PendingAddWidgetInfo(
288 widgetsListBaseEntry.mWidgets[0].widgetInfo,
289 LauncherSettings.Favorites.CONTAINER_DESKTOP
290 )
291 launcher.addPendingItem(
292 info,
293 info.container,
294 WorkspaceLayoutManager.FIRST_SCREEN_ID,
295 intArrayOf(0, 0),
296 info.spanX,
297 info.spanY
298 )
299 }
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600300
Sebastian Franco6196b902023-12-06 11:39:52 -0600301 override fun bindScreens(orderedScreenIds: LIntArray) {
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600302 launcher.workspace.pageIndicator.setAreScreensBinding(
303 true,
304 launcher.deviceProfile.isTwoPanels
305 )
306 val firstScreenPosition = 0
307 if (
308 (FeatureFlags.QSB_ON_FIRST_SCREEN &&
309 isFirstPagePinnedItemEnabled &&
310 !shouldShowFirstPageWidget()) &&
311 orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition
312 ) {
313 orderedScreenIds.removeValue(FIRST_SCREEN_ID)
314 orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID)
315 } else if (
316 (!FeatureFlags.QSB_ON_FIRST_SCREEN && !isFirstPagePinnedItemEnabled ||
317 shouldShowFirstPageWidget()) && orderedScreenIds.isEmpty
318 ) {
319 // If there are no screens, we need to have an empty screen
320 launcher.workspace.addExtraEmptyScreens()
321 }
322 bindAddScreens(orderedScreenIds)
323
324 // After we have added all the screens, if the wallpaper was locked to the default state,
325 // then notify to indicate that it can be released and a proper wallpaper offset can be
326 // computed before the next layout
327 launcher.workspace.unlockWallpaperFromDefaultPageOnNextLayout()
328 }
329
330 override fun bindAppsAdded(
Sebastian Franco6196b902023-12-06 11:39:52 -0600331 newScreens: LIntArray?,
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600332 addNotAnimated: java.util.ArrayList<ItemInfo?>?,
333 addAnimated: java.util.ArrayList<ItemInfo?>?
334 ) {
335 // Add the new screens
336 if (newScreens != null) {
337 // newScreens can contain an empty right panel that is already bound, but not known
338 // by BgDataModel.
339 newScreens.removeAllValues(launcher.workspace.mScreenOrder)
340 bindAddScreens(newScreens)
341 }
342
343 // We add the items without animation on non-visible pages, and with
344 // animations on the new page (which we will try and snap to).
345 if (!addNotAnimated.isNullOrEmpty()) {
346 launcher.bindItems(addNotAnimated, false)
347 }
348 if (!addAnimated.isNullOrEmpty()) {
349 launcher.bindItems(addAnimated, true)
350 }
351
352 // Remove the extra empty screen
353 launcher.workspace.removeExtraEmptyScreen(false)
354 }
355
Sebastian Franco6196b902023-12-06 11:39:52 -0600356 private fun bindAddScreens(orderedScreenIdsArg: LIntArray) {
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600357 var orderedScreenIds = orderedScreenIdsArg
358 if (launcher.deviceProfile.isTwoPanels) {
359 if (FeatureFlags.FOLDABLE_SINGLE_PAGE.get()) {
360 orderedScreenIds = filterTwoPanelScreenIds(orderedScreenIds)
361 } else {
362 // Some empty pages might have been removed while the phone was in a single panel
363 // mode, so we want to add those empty pages back.
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600364 val screenIds = LIntSet.wrap(orderedScreenIds)
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600365 orderedScreenIds.forEach { screenId: Int ->
366 screenIds.add(launcher.workspace.getScreenPair(screenId))
367 }
368 orderedScreenIds = screenIds.array
369 }
370 }
371 orderedScreenIds
372 .filterNot { screenId ->
373 FeatureFlags.QSB_ON_FIRST_SCREEN &&
374 isFirstPagePinnedItemEnabled &&
375 !FeatureFlags.shouldShowFirstPageWidget() &&
376 screenId == WorkspaceLayoutManager.FIRST_SCREEN_ID
377 }
378 .forEach { screenId ->
379 launcher.workspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId)
380 }
381 }
382
383 /**
384 * Remove odd number because they are already included when isTwoPanels and add the pair screen
385 * if not present.
386 */
Sebastian Franco6196b902023-12-06 11:39:52 -0600387 private fun filterTwoPanelScreenIds(orderedScreenIds: LIntArray): LIntArray {
Sebastian Franco0b461ef2023-12-08 12:20:07 -0600388 val screenIds = LIntSet.wrap(orderedScreenIds)
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600389 orderedScreenIds
390 .filter { screenId -> screenId % 2 == 1 }
391 .forEach { screenId ->
392 screenIds.remove(screenId)
393 // In case the pair is not added, add it
394 if (!launcher.workspace.containsScreenId(screenId - 1)) {
395 screenIds.add(screenId - 1)
396 }
397 }
398 return screenIds.array
399 }
400
401 override fun setIsFirstPagePinnedItemEnabled(isFirstPagePinnedItemEnabled: Boolean) {
402 this.isFirstPagePinnedItemEnabled = isFirstPagePinnedItemEnabled
403 launcher.workspace.bindAndInitFirstWorkspaceScreen()
404 }
405
406 override fun bindStringCache(cache: StringCache) {
407 stringCache = cache
408 launcher.appsView.updateWorkUI()
409 }
410
411 fun getIsFirstPagePinnedItemEnabled(): Boolean = isFirstPagePinnedItemEnabled
Sebastian Francobd7919c2023-09-19 10:55:37 -0700412}