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