blob: c05158b2f9f822eb24103d46237b7671d9cb9639 [file] [log] [blame]
Sebastian Francobd7919c2023-09-19 10:55:37 -07001package com.android.launcher3
2
3import androidx.annotation.UiThread
Sebastian Franco6dda7c72023-11-17 18:03:00 -06004import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID
Sebastian Francoe4965122023-12-01 13:27:42 -06005import com.android.launcher3.allapps.AllAppsStore
Sebastian Franco6dda7c72023-11-17 18:03:00 -06006import com.android.launcher3.config.FeatureFlags
7import com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget
Sebastian Francobd7919c2023-09-19 10:55:37 -07008import com.android.launcher3.model.BgDataModel
Sebastian Franco6dda7c72023-11-17 18:03:00 -06009import com.android.launcher3.model.StringCache
Sebastian Francobd7919c2023-09-19 10:55:37 -070010import com.android.launcher3.model.data.AppInfo
11import com.android.launcher3.model.data.ItemInfo
12import com.android.launcher3.model.data.LauncherAppWidgetInfo
13import com.android.launcher3.model.data.WorkspaceItemInfo
14import com.android.launcher3.popup.PopupContainerWithArrow
15import com.android.launcher3.util.ComponentKey
Sebastian Franco69fd7422023-10-06 16:48:58 -070016import com.android.launcher3.util.IntArray as LIntArray
17import com.android.launcher3.util.IntSet as LIntSet
Sebastian Franco6dda7c72023-11-17 18:03:00 -060018import com.android.launcher3.util.IntSet
Sebastian Francobd7919c2023-09-19 10:55:37 -070019import com.android.launcher3.util.PackageUserKey
20import com.android.launcher3.util.Preconditions
Sebastian Francoe4965122023-12-01 13:27:42 -060021import com.android.launcher3.util.TraceHelper
22import com.android.launcher3.util.ViewOnDrawExecutor
Sebastian Franco50b74c32023-11-16 11:36:49 -060023import com.android.launcher3.widget.PendingAddWidgetInfo
Sebastian Francobd7919c2023-09-19 10:55:37 -070024import com.android.launcher3.widget.model.WidgetsListBaseEntry
25import java.util.function.Predicate
26
27class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks {
Sebastian Franco69fd7422023-10-06 16:48:58 -070028
29 var synchronouslyBoundPages = LIntSet()
30 var pagesToBindSynchronously = LIntSet()
31
Sebastian Francoe4965122023-12-01 13:27:42 -060032 private var isFirstPagePinnedItemEnabled =
Sebastian Franco6dda7c72023-11-17 18:03:00 -060033 (BuildConfig.QSB_ON_FIRST_SCREEN && !FeatureFlags.ENABLE_SMARTSPACE_REMOVAL.get())
34
35 var stringCache: StringCache? = null
36
Sebastian Francoe4965122023-12-01 13:27:42 -060037 var pendingExecutor: ViewOnDrawExecutor? = null
38
39 var workspaceLoading = true
40
41 /**
42 * Refreshes the shortcuts shown on the workspace.
43 *
44 * Implementation of the method from LauncherModel.Callbacks.
45 */
46 override fun startBinding() {
47 TraceHelper.INSTANCE.beginSection("startBinding")
48 // Floating panels (except the full widget sheet) are associated with individual icons. If
49 // we are starting a fresh bind, close all such panels as all the icons are about
50 // to go away.
51 AbstractFloatingView.closeOpenViews(
52 launcher,
53 true,
54 AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv()
55 )
56 workspaceLoading = true
57
58 // Clear the workspace because it's going to be rebound
59 launcher.dragController.cancelDrag()
60 launcher.workspace.clearDropTargets()
61 launcher.workspace.removeAllWorkspaceScreens()
62 launcher.appWidgetHolder.clearViews()
63 launcher.hotseat?.resetLayout(launcher.deviceProfile.isVerticalBarLayout)
64 TraceHelper.INSTANCE.endSection()
65 }
66
67 /**
Sebastian Franco6196b902023-12-06 11:39:52 -060068 * Callback saying that there aren't any more items to bind.
69 *
70 * Implementation of the method from LauncherModel.Callbacks.
71 */
72 override fun finishBindingItems(pagesBoundFirst: LIntSet?) {
73 TraceHelper.INSTANCE.beginSection("finishBindingItems")
74 val deviceProfile = launcher.deviceProfile
75 launcher.workspace.restoreInstanceStateForRemainingPages()
76 workspaceLoading = false
77 launcher.processActivityResult()
78 val currentPage =
79 if (pagesBoundFirst != null && !pagesBoundFirst.isEmpty)
80 launcher.workspace.getPageIndexForScreenId(pagesBoundFirst.array[0])
81 else PagedView.INVALID_PAGE
82 // When undoing the removal of the last item on a page, return to that page.
83 // Since we are just resetting the current page without user interaction,
84 // override the previous page so we don't log the page switch.
85 launcher.workspace.setCurrentPage(currentPage, currentPage /* overridePrevPage */)
86 pagesToBindSynchronously = IntSet()
87
88 // Cache one page worth of icons
89 launcher.viewCache.setCacheSize(
90 R.layout.folder_application,
91 deviceProfile.inv.numFolderColumns * deviceProfile.inv.numFolderRows
92 )
93 launcher.viewCache.setCacheSize(R.layout.folder_page, 2)
94 TraceHelper.INSTANCE.endSection()
95 launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true)
96 launcher.workspace.pageIndicator.setAreScreensBinding(false, deviceProfile.isTwoPanels)
97 }
98
99 /**
Sebastian Francoe4965122023-12-01 13:27:42 -0600100 * Clear any pending bind callbacks. This is called when is loader is planning to perform a full
101 * rebind from scratch.
102 */
103 override fun clearPendingBinds() {
104 pendingExecutor?.cancel() ?: return
105 pendingExecutor = null
106
107 // We might have set this flag previously and forgot to clear it.
108 launcher.appsView.appsStore.disableDeferUpdatesSilently(
109 AllAppsStore.DEFER_UPDATES_NEXT_DRAW
110 )
111 }
112
Sebastian Francobd7919c2023-09-19 10:55:37 -0700113 override fun preAddApps() {
114 // If there's an undo snackbar, force it to complete to ensure empty screens are removed
115 // before trying to add new items.
116 launcher.modelWriter.commitDelete()
117 val snackbar =
118 AbstractFloatingView.getOpenView<AbstractFloatingView>(
119 launcher,
120 AbstractFloatingView.TYPE_SNACKBAR
121 )
122 snackbar?.post { snackbar.close(true) }
123 }
124
125 @UiThread
126 override fun bindAllApplications(
127 apps: Array<AppInfo?>?,
128 flags: Int,
129 packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?
130 ) {
131 Preconditions.assertUIThread()
132 val hadWorkApps = launcher.appsView.shouldShowTabs()
133 launcher.appsView.appsStore.setApps(apps, flags, packageUserKeytoUidMap)
134 PopupContainerWithArrow.dismissInvalidPopup(launcher)
135 if (hadWorkApps != launcher.appsView.shouldShowTabs()) {
136 launcher.stateManager.goToState(LauncherState.NORMAL)
137 }
138 }
139
140 /**
141 * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
142 * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
143 */
144 override fun bindDeepShortcutMap(deepShortcutMapCopy: HashMap<ComponentKey?, Int?>?) {
145 launcher.popupDataProvider.setDeepShortcutMap(deepShortcutMapCopy)
146 }
147
148 override fun bindIncrementalDownloadProgressUpdated(app: AppInfo?) {
149 launcher.appsView.appsStore.updateProgressBar(app)
150 }
151
152 override fun bindWidgetsRestored(widgets: ArrayList<LauncherAppWidgetInfo?>?) {
153 launcher.workspace.widgetsRestored(widgets)
154 }
155
156 /**
157 * Some shortcuts were updated in the background. Implementation of the method from
158 * LauncherModel.Callbacks.
159 *
160 * @param updated list of shortcuts which have changed.
161 */
162 override fun bindWorkspaceItemsChanged(updated: List<WorkspaceItemInfo?>) {
163 if (updated.isNotEmpty()) {
164 launcher.workspace.updateWorkspaceItems(updated, launcher)
165 PopupContainerWithArrow.dismissInvalidPopup(launcher)
166 }
167 }
168
169 /**
170 * Update the state of a package, typically related to install state. Implementation of the
171 * method from LauncherModel.Callbacks.
172 */
173 override fun bindRestoreItemsChange(updates: HashSet<ItemInfo?>?) {
174 launcher.workspace.updateRestoreItems(updates, launcher)
175 }
176
177 /**
178 * A package was uninstalled/updated. We take both the super set of packageNames in addition to
179 * specific applications to remove, the reason being that this can be called when a package is
180 * updated as well. In that scenario, we only remove specific components from the workspace and
181 * hotseat, where as package-removal should clear all items by package name.
182 */
183 override fun bindWorkspaceComponentsRemoved(matcher: Predicate<ItemInfo?>?) {
184 launcher.workspace.removeItemsByMatcher(matcher)
185 launcher.dragController.onAppsRemoved(matcher)
186 PopupContainerWithArrow.dismissInvalidPopup(launcher)
187 }
188
189 override fun bindAllWidgets(allWidgets: List<WidgetsListBaseEntry?>?) {
190 launcher.popupDataProvider.allWidgets = allWidgets
191 }
Sebastian Franco69fd7422023-10-06 16:48:58 -0700192
193 /** Returns the ids of the workspaces to bind. */
194 override fun getPagesToBindSynchronously(orderedScreenIds: LIntArray): LIntSet {
195 // If workspace binding is still in progress, getCurrentPageScreenIds won't be
196 // accurate, and we should use mSynchronouslyBoundPages that's set during initial binding.
197 val visibleIds =
198 when {
199 !pagesToBindSynchronously.isEmpty -> pagesToBindSynchronously
Sebastian Francoe4965122023-12-01 13:27:42 -0600200 !workspaceLoading -> launcher.workspace.currentPageScreenIds
Sebastian Franco69fd7422023-10-06 16:48:58 -0700201 else -> synchronouslyBoundPages
202 }
203 // Launcher IntArray has the same name as Kotlin IntArray
204 val result = LIntSet()
205 if (visibleIds.isEmpty) {
206 return result
207 }
208 val actualIds = orderedScreenIds.clone()
209 val firstId = visibleIds.first()
210 val pairId = launcher.workspace.getScreenPair(firstId)
211 // Double check that actual screenIds contains the visibleId, as empty screens are hidden
212 // in single panel.
213 if (actualIds.contains(firstId)) {
214 result.add(firstId)
215 if (launcher.deviceProfile.isTwoPanels && actualIds.contains(pairId)) {
216 result.add(pairId)
217 }
218 } else if (
219 LauncherAppState.getIDP(launcher).supportedProfiles.any(DeviceProfile::isTwoPanels) &&
220 actualIds.contains(pairId)
221 ) {
222 // Add the right panel if left panel is hidden when switching display, due to empty
223 // pages being hidden in single panel.
224 result.add(pairId)
225 }
226 return result
227 }
Sebastian Franco50b74c32023-11-16 11:36:49 -0600228
229 override fun bindSmartspaceWidget() {
230 val cl: CellLayout? =
231 launcher.workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID)
232 val spanX = InvariantDeviceProfile.INSTANCE.get(launcher).numSearchContainerColumns
233
234 if (cl?.isRegionVacant(0, 0, spanX, 1) != true) {
235 return
236 }
237
238 val widgetsListBaseEntry: WidgetsListBaseEntry =
239 launcher.popupDataProvider.allWidgets.firstOrNull { item: WidgetsListBaseEntry ->
240 item.mPkgItem.packageName == BuildConfig.APPLICATION_ID
241 }
242 ?: return
243
244 val info =
245 PendingAddWidgetInfo(
246 widgetsListBaseEntry.mWidgets[0].widgetInfo,
247 LauncherSettings.Favorites.CONTAINER_DESKTOP
248 )
249 launcher.addPendingItem(
250 info,
251 info.container,
252 WorkspaceLayoutManager.FIRST_SCREEN_ID,
253 intArrayOf(0, 0),
254 info.spanX,
255 info.spanY
256 )
257 }
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600258
Sebastian Franco6196b902023-12-06 11:39:52 -0600259 override fun bindScreens(orderedScreenIds: LIntArray) {
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600260 launcher.workspace.pageIndicator.setAreScreensBinding(
261 true,
262 launcher.deviceProfile.isTwoPanels
263 )
264 val firstScreenPosition = 0
265 if (
266 (FeatureFlags.QSB_ON_FIRST_SCREEN &&
267 isFirstPagePinnedItemEnabled &&
268 !shouldShowFirstPageWidget()) &&
269 orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition
270 ) {
271 orderedScreenIds.removeValue(FIRST_SCREEN_ID)
272 orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID)
273 } else if (
274 (!FeatureFlags.QSB_ON_FIRST_SCREEN && !isFirstPagePinnedItemEnabled ||
275 shouldShowFirstPageWidget()) && orderedScreenIds.isEmpty
276 ) {
277 // If there are no screens, we need to have an empty screen
278 launcher.workspace.addExtraEmptyScreens()
279 }
280 bindAddScreens(orderedScreenIds)
281
282 // After we have added all the screens, if the wallpaper was locked to the default state,
283 // then notify to indicate that it can be released and a proper wallpaper offset can be
284 // computed before the next layout
285 launcher.workspace.unlockWallpaperFromDefaultPageOnNextLayout()
286 }
287
288 override fun bindAppsAdded(
Sebastian Franco6196b902023-12-06 11:39:52 -0600289 newScreens: LIntArray?,
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600290 addNotAnimated: java.util.ArrayList<ItemInfo?>?,
291 addAnimated: java.util.ArrayList<ItemInfo?>?
292 ) {
293 // Add the new screens
294 if (newScreens != null) {
295 // newScreens can contain an empty right panel that is already bound, but not known
296 // by BgDataModel.
297 newScreens.removeAllValues(launcher.workspace.mScreenOrder)
298 bindAddScreens(newScreens)
299 }
300
301 // We add the items without animation on non-visible pages, and with
302 // animations on the new page (which we will try and snap to).
303 if (!addNotAnimated.isNullOrEmpty()) {
304 launcher.bindItems(addNotAnimated, false)
305 }
306 if (!addAnimated.isNullOrEmpty()) {
307 launcher.bindItems(addAnimated, true)
308 }
309
310 // Remove the extra empty screen
311 launcher.workspace.removeExtraEmptyScreen(false)
312 }
313
Sebastian Franco6196b902023-12-06 11:39:52 -0600314 private fun bindAddScreens(orderedScreenIdsArg: LIntArray) {
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600315 var orderedScreenIds = orderedScreenIdsArg
316 if (launcher.deviceProfile.isTwoPanels) {
317 if (FeatureFlags.FOLDABLE_SINGLE_PAGE.get()) {
318 orderedScreenIds = filterTwoPanelScreenIds(orderedScreenIds)
319 } else {
320 // Some empty pages might have been removed while the phone was in a single panel
321 // mode, so we want to add those empty pages back.
322 val screenIds = IntSet.wrap(orderedScreenIds)
323 orderedScreenIds.forEach { screenId: Int ->
324 screenIds.add(launcher.workspace.getScreenPair(screenId))
325 }
326 orderedScreenIds = screenIds.array
327 }
328 }
329 orderedScreenIds
330 .filterNot { screenId ->
331 FeatureFlags.QSB_ON_FIRST_SCREEN &&
332 isFirstPagePinnedItemEnabled &&
333 !FeatureFlags.shouldShowFirstPageWidget() &&
334 screenId == WorkspaceLayoutManager.FIRST_SCREEN_ID
335 }
336 .forEach { screenId ->
337 launcher.workspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId)
338 }
339 }
340
341 /**
342 * Remove odd number because they are already included when isTwoPanels and add the pair screen
343 * if not present.
344 */
Sebastian Franco6196b902023-12-06 11:39:52 -0600345 private fun filterTwoPanelScreenIds(orderedScreenIds: LIntArray): LIntArray {
Sebastian Franco6dda7c72023-11-17 18:03:00 -0600346 val screenIds = IntSet.wrap(orderedScreenIds)
347 orderedScreenIds
348 .filter { screenId -> screenId % 2 == 1 }
349 .forEach { screenId ->
350 screenIds.remove(screenId)
351 // In case the pair is not added, add it
352 if (!launcher.workspace.containsScreenId(screenId - 1)) {
353 screenIds.add(screenId - 1)
354 }
355 }
356 return screenIds.array
357 }
358
359 override fun setIsFirstPagePinnedItemEnabled(isFirstPagePinnedItemEnabled: Boolean) {
360 this.isFirstPagePinnedItemEnabled = isFirstPagePinnedItemEnabled
361 launcher.workspace.bindAndInitFirstWorkspaceScreen()
362 }
363
364 override fun bindStringCache(cache: StringCache) {
365 stringCache = cache
366 launcher.appsView.updateWorkUI()
367 }
368
369 fun getIsFirstPagePinnedItemEnabled(): Boolean = isFirstPagePinnedItemEnabled
Sebastian Francobd7919c2023-09-19 10:55:37 -0700370}