<lambda>null1 package com.android.launcher3
2
3 import android.annotation.TargetApi
4 import android.os.Build
5 import android.os.Trace
6 import android.util.Log
7 import androidx.annotation.UiThread
8 import com.android.launcher3.Flags.enableSmartspaceRemovalToggle
9 import com.android.launcher3.LauncherConstants.TraceEvents
10 import com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET
11 import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID
12 import com.android.launcher3.allapps.AllAppsStore
13 import com.android.launcher3.config.FeatureFlags
14 import com.android.launcher3.debug.TestEvent
15 import com.android.launcher3.debug.TestEventEmitter
16 import com.android.launcher3.model.BgDataModel
17 import com.android.launcher3.model.StringCache
18 import com.android.launcher3.model.data.AppInfo
19 import com.android.launcher3.model.data.ItemInfo
20 import com.android.launcher3.model.data.LauncherAppWidgetInfo
21 import com.android.launcher3.model.data.WorkspaceItemInfo
22 import com.android.launcher3.popup.PopupContainerWithArrow
23 import com.android.launcher3.util.ComponentKey
24 import com.android.launcher3.util.IntArray as LIntArray
25 import com.android.launcher3.util.IntSet as LIntSet
26 import com.android.launcher3.util.PackageUserKey
27 import com.android.launcher3.util.Preconditions
28 import com.android.launcher3.util.RunnableList
29 import com.android.launcher3.util.TraceHelper
30 import com.android.launcher3.util.ViewOnDrawExecutor
31 import com.android.launcher3.widget.PendingAddWidgetInfo
32 import com.android.launcher3.widget.model.WidgetsListBaseEntry
33 import java.util.function.Predicate
34
35 private const val TAG = "ModelCallbacks"
36
37 class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks {
38
39 var synchronouslyBoundPages = LIntSet()
40 var pagesToBindSynchronously = LIntSet()
41
42 private var isFirstPagePinnedItemEnabled =
43 (BuildConfig.QSB_ON_FIRST_SCREEN && !enableSmartspaceRemovalToggle())
44
45 var stringCache: StringCache? = null
46
47 var pendingExecutor: ViewOnDrawExecutor? = null
48
49 var workspaceLoading = true
50
51 /**
52 * Refreshes the shortcuts shown on the workspace.
53 *
54 * Implementation of the method from LauncherModel.Callbacks.
55 */
56 override fun startBinding() {
57 TraceHelper.INSTANCE.beginSection("startBinding")
58 // Floating panels (except the full widget sheet) are associated with individual icons. If
59 // we are starting a fresh bind, close all such panels as all the icons are about
60 // to go away.
61 AbstractFloatingView.closeOpenViews(
62 launcher,
63 true,
64 AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv(),
65 )
66 workspaceLoading = true
67
68 // Clear the workspace because it's going to be rebound
69 launcher.dragController.cancelDrag()
70 launcher.workspace.clearDropTargets()
71 launcher.workspace.removeAllWorkspaceScreens()
72 // Avoid clearing the widget update listeners for staying up-to-date with widget info
73 launcher.appWidgetHolder.clearWidgetViews()
74 // TODO(b/335141365): Remove this log after the bug is fixed.
75 Log.d(
76 TAG,
77 "startBinding: " +
78 "hotseat layout was vertical: ${launcher.hotseat?.isHasVerticalHotseat}" +
79 " and is setting to ${launcher.deviceProfile.isVerticalBarLayout}",
80 )
81 launcher.hotseat?.resetLayout(launcher.deviceProfile.isVerticalBarLayout)
82 TraceHelper.INSTANCE.endSection()
83 }
84
85 @TargetApi(Build.VERSION_CODES.S)
86 override fun onInitialBindComplete(
87 boundPages: LIntSet,
88 pendingTasks: RunnableList,
89 onCompleteSignal: RunnableList,
90 workspaceItemCount: Int,
91 isBindSync: Boolean,
92 ) {
93 Trace.endAsyncSection(
94 TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
95 TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE,
96 )
97 synchronouslyBoundPages = boundPages
98 pagesToBindSynchronously = LIntSet()
99 clearPendingBinds()
100 if (!launcher.isInState(LauncherState.ALL_APPS)) {
101 launcher.appsView.appsStore.enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW)
102 pendingTasks.add {
103 launcher.appsView.appsStore.disableDeferUpdates(
104 AllAppsStore.DEFER_UPDATES_NEXT_DRAW
105 )
106 }
107 }
108 val executor =
109 ViewOnDrawExecutor(pendingTasks) {
110 if (pendingExecutor == it) {
111 pendingExecutor = null
112 }
113 }
114 pendingExecutor = executor
115
116 if (Flags.enableWorkspaceInflation()) {
117 // Finish the executor as soon as the pending inflation is completed
118 onCompleteSignal.add(executor::markCompleted)
119 } else {
120 // Pending executor is already completed, wait until first draw to run the tasks
121 executor.attachTo(launcher)
122 }
123 launcher.bindComplete(workspaceItemCount, isBindSync)
124 }
125
126 /**
127 * Callback saying that there aren't any more items to bind.
128 *
129 * Implementation of the method from LauncherModel.Callbacks.
130 */
131 override fun finishBindingItems(pagesBoundFirst: LIntSet?) {
132 TraceHelper.INSTANCE.beginSection("finishBindingItems")
133 val deviceProfile = launcher.deviceProfile
134 launcher.workspace.restoreInstanceStateForRemainingPages()
135 workspaceLoading = false
136 launcher.processActivityResult()
137 val currentPage =
138 if (pagesBoundFirst != null && !pagesBoundFirst.isEmpty)
139 launcher.workspace.getPageIndexForScreenId(pagesBoundFirst.array[0])
140 else PagedView.INVALID_PAGE
141 // When undoing the removal of the last item on a page, return to that page.
142 // Since we are just resetting the current page without user interaction,
143 // override the previous page so we don't log the page switch.
144 launcher.workspace.setCurrentPage(currentPage, currentPage /* overridePrevPage */)
145 pagesToBindSynchronously = LIntSet()
146
147 // Cache one page worth of icons
148 launcher.viewCache.setCacheSize(
149 R.layout.folder_application,
150 deviceProfile.numFolderColumns * deviceProfile.numFolderRows,
151 )
152 launcher.viewCache.setCacheSize(R.layout.folder_page, 2)
153 TraceHelper.INSTANCE.endSection()
154 launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true)
155 launcher.workspace.pageIndicator.setPauseScroll(
156 /*pause=*/ false,
157 deviceProfile.isTwoPanels,
158 )
159 TestEventEmitter.INSTANCE.get(launcher).sendEvent(TestEvent.WORKSPACE_FINISH_LOADING)
160 }
161
162 /**
163 * Clear any pending bind callbacks. This is called when is loader is planning to perform a full
164 * rebind from scratch.
165 */
166 override fun clearPendingBinds() {
167 pendingExecutor?.cancel() ?: return
168 pendingExecutor = null
169
170 // We might have set this flag previously and forgot to clear it.
171 launcher.appsView.appsStore.disableDeferUpdatesSilently(
172 AllAppsStore.DEFER_UPDATES_NEXT_DRAW
173 )
174 }
175
176 override fun preAddApps() {
177 // If there's an undo snackbar, force it to complete to ensure empty screens are removed
178 // before trying to add new items.
179 launcher.modelWriter.commitDelete()
180 val snackbar =
181 AbstractFloatingView.getOpenView<AbstractFloatingView>(
182 launcher,
183 AbstractFloatingView.TYPE_SNACKBAR,
184 )
185 snackbar?.post { snackbar.close(true) }
186 }
187
188 @UiThread
189 override fun bindAllApplications(
190 apps: Array<AppInfo?>?,
191 flags: Int,
192 packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?,
193 ) {
194 Preconditions.assertUIThread()
195 val hadWorkApps = launcher.appsView.shouldShowTabs()
196 launcher.appsView.appsStore.setApps(apps, flags, packageUserKeytoUidMap)
197 PopupContainerWithArrow.dismissInvalidPopup(launcher)
198 if (
199 hadWorkApps != launcher.appsView.shouldShowTabs() &&
200 launcher.stateManager.state == LauncherState.ALL_APPS
201 ) {
202 launcher.stateManager.goToState(LauncherState.NORMAL)
203 }
204 }
205
206 /**
207 * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
208 * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
209 */
210 override fun bindDeepShortcutMap(deepShortcutMapCopy: HashMap<ComponentKey?, Int?>?) {
211 launcher.popupDataProvider.setDeepShortcutMap(deepShortcutMapCopy)
212 }
213
214 override fun bindIncrementalDownloadProgressUpdated(app: AppInfo?) {
215 launcher.appsView.appsStore.updateProgressBar(app)
216 }
217
218 override fun bindWidgetsRestored(widgets: ArrayList<LauncherAppWidgetInfo?>?) {
219 launcher.workspace.widgetsRestored(widgets)
220 }
221
222 /**
223 * Some shortcuts were updated in the background. Implementation of the method from
224 * LauncherModel.Callbacks.
225 *
226 * @param updated list of shortcuts which have changed.
227 */
228 override fun bindWorkspaceItemsChanged(updated: List<WorkspaceItemInfo?>) {
229 if (updated.isNotEmpty()) {
230 launcher.workspace.updateWorkspaceItems(updated, launcher)
231 PopupContainerWithArrow.dismissInvalidPopup(launcher)
232 }
233 }
234
235 /**
236 * Update the state of a package, typically related to install state. Implementation of the
237 * method from LauncherModel.Callbacks.
238 */
239 override fun bindRestoreItemsChange(updates: HashSet<ItemInfo?>?) {
240 launcher.workspace.updateRestoreItems(updates, launcher)
241 }
242
243 /**
244 * A package was uninstalled/updated. We take both the super set of packageNames in addition to
245 * specific applications to remove, the reason being that this can be called when a package is
246 * updated as well. In that scenario, we only remove specific components from the workspace and
247 * hotseat, where as package-removal should clear all items by package name.
248 */
249 override fun bindWorkspaceComponentsRemoved(matcher: Predicate<ItemInfo?>?) {
250 launcher.workspace.removeItemsByMatcher(matcher)
251 launcher.dragController.onAppsRemoved(matcher)
252 PopupContainerWithArrow.dismissInvalidPopup(launcher)
253 }
254
255 override fun bindAllWidgets(
256 allWidgets: List<WidgetsListBaseEntry>,
257 defaultWidgets: List<WidgetsListBaseEntry>,
258 ) {
259 launcher.widgetPickerDataProvider.setWidgets(allWidgets, defaultWidgets)
260 }
261
262 /** Returns the ids of the workspaces to bind. */
263 override fun getPagesToBindSynchronously(orderedScreenIds: LIntArray): LIntSet {
264 // If workspace binding is still in progress, getCurrentPageScreenIds won't be
265 // accurate, and we should use mSynchronouslyBoundPages that's set during initial binding.
266 val visibleIds =
267 when {
268 !pagesToBindSynchronously.isEmpty -> pagesToBindSynchronously
269 !workspaceLoading -> launcher.workspace.currentPageScreenIds
270 else -> synchronouslyBoundPages
271 }
272 // Launcher IntArray has the same name as Kotlin IntArray
273 val result = LIntSet()
274 if (visibleIds.isEmpty) {
275 return result
276 }
277 val actualIds = orderedScreenIds.clone()
278 val firstId = visibleIds.first()
279 val pairId = launcher.workspace.getScreenPair(firstId)
280 // Double check that actual screenIds contains the visibleId, as empty screens are hidden
281 // in single panel.
282 if (actualIds.contains(firstId)) {
283 result.add(firstId)
284 if (launcher.deviceProfile.isTwoPanels && actualIds.contains(pairId)) {
285 result.add(pairId)
286 }
287 } else if (
288 LauncherAppState.getIDP(launcher).supportedProfiles.any(DeviceProfile::isTwoPanels) &&
289 actualIds.contains(pairId)
290 ) {
291 // Add the right panel if left panel is hidden when switching display, due to empty
292 // pages being hidden in single panel.
293 result.add(pairId)
294 }
295 return result
296 }
297
298 override fun bindSmartspaceWidget() {
299 val cl: CellLayout? =
300 launcher.workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID)
301 val spanX = InvariantDeviceProfile.INSTANCE.get(launcher).numSearchContainerColumns
302
303 if (cl?.isRegionVacant(0, 0, spanX, 1) != true) {
304 return
305 }
306
307 val widgetsListBaseEntry: WidgetsListBaseEntry =
308 launcher.widgetPickerDataProvider.get().allWidgets.firstOrNull {
309 item: WidgetsListBaseEntry ->
310 item.mPkgItem.packageName == BuildConfig.APPLICATION_ID
311 } ?: return
312
313 val info =
314 PendingAddWidgetInfo(
315 widgetsListBaseEntry.mWidgets[0].widgetInfo,
316 LauncherSettings.Favorites.CONTAINER_DESKTOP,
317 )
318 launcher.addPendingItem(
319 info,
320 info.container,
321 WorkspaceLayoutManager.FIRST_SCREEN_ID,
322 intArrayOf(0, 0),
323 info.spanX,
324 info.spanY,
325 )
326 }
327
328 override fun bindScreens(orderedScreenIds: LIntArray) {
329 launcher.workspace.pageIndicator.setPauseScroll(
330 /*pause=*/ true,
331 launcher.deviceProfile.isTwoPanels,
332 )
333 val firstScreenPosition = 0
334 if (
335 (isFirstPagePinnedItemEnabled && !SHOULD_SHOW_FIRST_PAGE_WIDGET) &&
336 orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition
337 ) {
338 orderedScreenIds.removeValue(FIRST_SCREEN_ID)
339 orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID)
340 } else if (
341 (!isFirstPagePinnedItemEnabled || SHOULD_SHOW_FIRST_PAGE_WIDGET) &&
342 orderedScreenIds.isEmpty
343 ) {
344 // If there are no screens, we need to have an empty screen
345 launcher.workspace.addExtraEmptyScreens()
346 }
347 bindAddScreens(orderedScreenIds)
348
349 // After we have added all the screens, if the wallpaper was locked to the default state,
350 // then notify to indicate that it can be released and a proper wallpaper offset can be
351 // computed before the next layout
352 launcher.workspace.unlockWallpaperFromDefaultPageOnNextLayout()
353 }
354
355 override fun bindAppsAdded(
356 newScreens: LIntArray?,
357 addNotAnimated: java.util.ArrayList<ItemInfo?>?,
358 addAnimated: java.util.ArrayList<ItemInfo?>?,
359 ) {
360 // Add the new screens
361 if (newScreens != null) {
362 // newScreens can contain an empty right panel that is already bound, but not known
363 // by BgDataModel.
364 newScreens.removeAllValues(launcher.workspace.mScreenOrder)
365 bindAddScreens(newScreens)
366 }
367
368 // We add the items without animation on non-visible pages, and with
369 // animations on the new page (which we will try and snap to).
370 if (!addNotAnimated.isNullOrEmpty()) {
371 launcher.bindItems(addNotAnimated, false)
372 }
373 if (!addAnimated.isNullOrEmpty()) {
374 launcher.bindItems(addAnimated, true)
375 }
376
377 // Remove the extra empty screen
378 launcher.workspace.removeExtraEmptyScreen(false)
379 }
380
381 private fun bindAddScreens(orderedScreenIdsArg: LIntArray) {
382 var orderedScreenIds = orderedScreenIdsArg
383 if (launcher.deviceProfile.isTwoPanels) {
384 if (FeatureFlags.FOLDABLE_SINGLE_PAGE.get()) {
385 orderedScreenIds = filterTwoPanelScreenIds(orderedScreenIds)
386 } else {
387 // Some empty pages might have been removed while the phone was in a single panel
388 // mode, so we want to add those empty pages back.
389 val screenIds = LIntSet.wrap(orderedScreenIds)
390 orderedScreenIds.forEach { screenId: Int ->
391 screenIds.add(launcher.workspace.getScreenPair(screenId))
392 }
393 orderedScreenIds = screenIds.array
394 }
395 }
396 orderedScreenIds
397 .filterNot { screenId ->
398 isFirstPagePinnedItemEnabled &&
399 !SHOULD_SHOW_FIRST_PAGE_WIDGET &&
400 screenId == WorkspaceLayoutManager.FIRST_SCREEN_ID
401 }
402 .forEach { screenId ->
403 launcher.workspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId)
404 }
405 }
406
407 /**
408 * Remove odd number because they are already included when isTwoPanels and add the pair screen
409 * if not present.
410 */
411 private fun filterTwoPanelScreenIds(orderedScreenIds: LIntArray): LIntArray {
412 val screenIds = LIntSet.wrap(orderedScreenIds)
413 orderedScreenIds
414 .filter { screenId -> screenId % 2 == 1 }
415 .forEach { screenId ->
416 screenIds.remove(screenId)
417 // In case the pair is not added, add it
418 if (!launcher.workspace.containsScreenId(screenId - 1)) {
419 screenIds.add(screenId - 1)
420 }
421 }
422 return screenIds.array
423 }
424
425 override fun setIsFirstPagePinnedItemEnabled(isFirstPagePinnedItemEnabled: Boolean) {
426 this.isFirstPagePinnedItemEnabled = isFirstPagePinnedItemEnabled
427 launcher.workspace.bindAndInitFirstWorkspaceScreen()
428 }
429
430 override fun bindStringCache(cache: StringCache) {
431 stringCache = cache
432 launcher.appsView.updateWorkUI()
433 }
434
435 fun getIsFirstPagePinnedItemEnabled(): Boolean = isFirstPagePinnedItemEnabled
436
437 override fun getItemInflater() = launcher.itemInflater
438 }
439