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