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