<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.TestEventEmitter
15 import com.android.launcher3.debug.TestEventEmitter.TestEvent
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.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 Trace.endAsyncSection(
92 TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
93 TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE,
94 )
95 synchronouslyBoundPages = boundPages
96 pagesToBindSynchronously = LIntSet()
97 clearPendingBinds()
98 if (!launcher.isInState(LauncherState.ALL_APPS) && !Flags.enableWorkspaceInflation()) {
99 launcher.appsView.appsStore.enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW)
100 pendingTasks.add {
101 launcher.appsView.appsStore.disableDeferUpdates(
102 AllAppsStore.DEFER_UPDATES_NEXT_DRAW
103 )
104 }
105 }
106 val executor =
107 ViewOnDrawExecutor(pendingTasks) {
108 if (pendingExecutor == it) {
109 pendingExecutor = null
110 }
111 }
112 pendingExecutor = executor
113
114 if (Flags.enableWorkspaceInflation()) {
115 // Finish the executor as soon as the pending inflation is completed
116 onCompleteSignal.add(executor::markCompleted)
117 } else {
118 // Pending executor is already completed, wait until first draw to run the tasks
119 executor.attachTo(launcher)
120 }
121 launcher.bindComplete(workspaceItemCount, isBindSync)
122 }
123
124 /**
125 * Callback saying that there aren't any more items to bind.
126 *
127 * Implementation of the method from LauncherModel.Callbacks.
128 */
129 override fun finishBindingItems(pagesBoundFirst: LIntSet?) {
130 TraceHelper.INSTANCE.beginSection("finishBindingItems")
131 val deviceProfile = launcher.deviceProfile
132 launcher.workspace.restoreInstanceStateForRemainingPages()
133 workspaceLoading = false
134 launcher.processActivityResult()
135 val currentPage =
136 if (pagesBoundFirst != null && !pagesBoundFirst.isEmpty)
137 launcher.workspace.getPageIndexForScreenId(pagesBoundFirst.array[0])
138 else PagedView.INVALID_PAGE
139 // When undoing the removal of the last item on a page, return to that page.
140 // Since we are just resetting the current page without user interaction,
141 // override the previous page so we don't log the page switch.
142 launcher.workspace.setCurrentPage(currentPage, currentPage /* overridePrevPage */)
143 pagesToBindSynchronously = LIntSet()
144
145 // Cache one page worth of icons
146 launcher.viewCache.setCacheSize(
147 R.layout.folder_application,
148 deviceProfile.numFolderColumns * deviceProfile.numFolderRows,
149 )
150 launcher.viewCache.setCacheSize(R.layout.folder_page, 2)
151 TraceHelper.INSTANCE.endSection()
152 launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true)
153 launcher.workspace.pageIndicator.setPauseScroll(
154 /*pause=*/ false,
155 deviceProfile.isTwoPanels,
156 )
157 TestEventEmitter.sendEvent(TestEvent.WORKSPACE_FINISH_LOADING)
158 }
159
160 /**
161 * Clear any pending bind callbacks. This is called when is loader is planning to perform a full
162 * rebind from scratch.
163 */
164 override fun clearPendingBinds() {
165 pendingExecutor?.cancel() ?: return
166 pendingExecutor = null
167
168 // We might have set this flag previously and forgot to clear it.
169 launcher.appsView.appsStore.disableDeferUpdatesSilently(
170 AllAppsStore.DEFER_UPDATES_NEXT_DRAW
171 )
172 }
173
174 override fun preAddApps() {
175 // If there's an undo snackbar, force it to complete to ensure empty screens are removed
176 // before trying to add new items.
177 launcher.modelWriter.commitDelete()
178 val snackbar =
179 AbstractFloatingView.getOpenView<AbstractFloatingView>(
180 launcher,
181 AbstractFloatingView.TYPE_SNACKBAR,
182 )
183 snackbar?.post { snackbar.close(true) }
184 }
185
186 @UiThread
187 override fun bindAllApplications(
188 apps: Array<AppInfo?>?,
189 flags: Int,
190 packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?,
191 ) {
192 Preconditions.assertUIThread()
193 val hadWorkApps = launcher.appsView.shouldShowTabs()
194 launcher.appsView.appsStore.setApps(apps, flags, packageUserKeytoUidMap)
195 PopupContainerWithArrow.dismissInvalidPopup(launcher)
196 if (
197 hadWorkApps != launcher.appsView.shouldShowTabs() &&
198 launcher.stateManager.state == LauncherState.ALL_APPS
199 ) {
200 launcher.stateManager.goToState(LauncherState.NORMAL)
201 }
202 }
203
204 /**
205 * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
206 * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
207 */
208 override fun bindDeepShortcutMap(deepShortcutMapCopy: HashMap<ComponentKey?, Int?>?) {
209 launcher.popupDataProvider.setDeepShortcutMap(deepShortcutMapCopy)
210 }
211
212 override fun bindIncrementalDownloadProgressUpdated(app: AppInfo?) {
213 launcher.appsView.appsStore.updateProgressBar(app)
214 }
215
216 /**
217 * Update the state of a package, typically related to install state. Implementation of the
218 * method from LauncherModel.Callbacks.
219 */
220 override fun bindItemsUpdated(updates: Set<ItemInfo>) {
221 launcher.workspace.updateContainerItems(updates, launcher)
222 PopupContainerWithArrow.dismissInvalidPopup(launcher)
223 }
224
225 /**
226 * A package was uninstalled/updated. We take both the super set of packageNames in addition to
227 * specific applications to remove, the reason being that this can be called when a package is
228 * updated as well. In that scenario, we only remove specific components from the workspace and
229 * hotseat, where as package-removal should clear all items by package name.
230 */
231 override fun bindWorkspaceComponentsRemoved(matcher: Predicate<ItemInfo?>?) {
232 launcher.workspace.removeItemsByMatcher(matcher)
233 launcher.dragController.onAppsRemoved(matcher)
234 PopupContainerWithArrow.dismissInvalidPopup(launcher)
235 }
236
237 override fun bindAllWidgets(allWidgets: List<WidgetsListBaseEntry>) {
238 launcher.widgetPickerDataProvider.setWidgets(allWidgets)
239 }
240
241 /** Returns the ids of the workspaces to bind. */
242 override fun getPagesToBindSynchronously(orderedScreenIds: LIntArray): LIntSet {
243 // If workspace binding is still in progress, getCurrentPageScreenIds won't be
244 // accurate, and we should use mSynchronouslyBoundPages that's set during initial binding.
245 val visibleIds =
246 when {
247 !pagesToBindSynchronously.isEmpty -> pagesToBindSynchronously
248 !workspaceLoading -> launcher.workspace.currentPageScreenIds
249 else -> synchronouslyBoundPages
250 }
251 // Launcher IntArray has the same name as Kotlin IntArray
252 val result = LIntSet()
253 if (visibleIds.isEmpty) {
254 return result
255 }
256 val actualIds = orderedScreenIds.clone()
257 val firstId = visibleIds.first()
258 val pairId = launcher.workspace.getScreenPair(firstId)
259 // Double check that actual screenIds contains the visibleId, as empty screens are hidden
260 // in single panel.
261 if (actualIds.contains(firstId)) {
262 result.add(firstId)
263 if (launcher.deviceProfile.isTwoPanels && actualIds.contains(pairId)) {
264 result.add(pairId)
265 }
266 } else if (
267 LauncherAppState.getIDP(launcher).supportedProfiles.any(DeviceProfile::isTwoPanels) &&
268 actualIds.contains(pairId)
269 ) {
270 // Add the right panel if left panel is hidden when switching display, due to empty
271 // pages being hidden in single panel.
272 result.add(pairId)
273 }
274 return result
275 }
276
277 override fun bindSmartspaceWidget() {
278 val cl: CellLayout? =
279 launcher.workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID)
280 val spanX = InvariantDeviceProfile.INSTANCE.get(launcher).numSearchContainerColumns
281
282 if (cl?.isRegionVacant(0, 0, spanX, 1) != true) {
283 return
284 }
285
286 val widgetsListBaseEntry: WidgetsListBaseEntry =
287 launcher.widgetPickerDataProvider.get().allWidgets.firstOrNull {
288 item: WidgetsListBaseEntry ->
289 item.mPkgItem.packageName == BuildConfig.APPLICATION_ID
290 } ?: return
291
292 val info =
293 PendingAddWidgetInfo(
294 widgetsListBaseEntry.mWidgets[0].widgetInfo,
295 LauncherSettings.Favorites.CONTAINER_DESKTOP,
296 )
297 launcher.addPendingItem(
298 info,
299 info.container,
300 WorkspaceLayoutManager.FIRST_SCREEN_ID,
301 intArrayOf(0, 0),
302 info.spanX,
303 info.spanY,
304 )
305 }
306
307 override fun bindScreens(orderedScreenIds: LIntArray) {
308 launcher.workspace.pageIndicator.setPauseScroll(
309 /*pause=*/ true,
310 launcher.deviceProfile.isTwoPanels,
311 )
312 val firstScreenPosition = 0
313 if (
314 (isFirstPagePinnedItemEnabled && !SHOULD_SHOW_FIRST_PAGE_WIDGET) &&
315 orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition
316 ) {
317 orderedScreenIds.removeValue(FIRST_SCREEN_ID)
318 orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID)
319 } else if (
320 (!isFirstPagePinnedItemEnabled || SHOULD_SHOW_FIRST_PAGE_WIDGET) &&
321 orderedScreenIds.isEmpty
322 ) {
323 // If there are no screens, we need to have an empty screen
324 launcher.workspace.addExtraEmptyScreens()
325 }
326 bindAddScreens(orderedScreenIds)
327
328 // After we have added all the screens, if the wallpaper was locked to the default state,
329 // then notify to indicate that it can be released and a proper wallpaper offset can be
330 // computed before the next layout
331 launcher.workspace.unlockWallpaperFromDefaultPageOnNextLayout()
332 }
333
334 override fun bindAppsAdded(
335 newScreens: LIntArray?,
336 addNotAnimated: java.util.ArrayList<ItemInfo?>?,
337 addAnimated: java.util.ArrayList<ItemInfo?>?,
338 ) {
339 // Add the new screens
340 if (newScreens != null) {
341 // newScreens can contain an empty right panel that is already bound, but not known
342 // by BgDataModel.
343 newScreens.removeAllValues(launcher.workspace.mScreenOrder)
344 bindAddScreens(newScreens)
345 }
346
347 // We add the items without animation on non-visible pages, and with
348 // animations on the new page (which we will try and snap to).
349 if (!addNotAnimated.isNullOrEmpty()) {
350 launcher.bindItems(addNotAnimated, false)
351 }
352 if (!addAnimated.isNullOrEmpty()) {
353 launcher.bindItems(addAnimated, true)
354 }
355
356 // Remove the extra empty screen
357 launcher.workspace.removeExtraEmptyScreen(false)
358 }
359
360 private fun bindAddScreens(orderedScreenIdsArg: LIntArray) {
361 var orderedScreenIds = orderedScreenIdsArg
362 if (launcher.deviceProfile.isTwoPanels) {
363 if (FeatureFlags.FOLDABLE_SINGLE_PAGE.get()) {
364 orderedScreenIds = filterTwoPanelScreenIds(orderedScreenIds)
365 } else {
366 // Some empty pages might have been removed while the phone was in a single panel
367 // mode, so we want to add those empty pages back.
368 val screenIds = LIntSet.wrap(orderedScreenIds)
369 orderedScreenIds.forEach { screenId: Int ->
370 screenIds.add(launcher.workspace.getScreenPair(screenId))
371 }
372 orderedScreenIds = screenIds.array
373 }
374 }
375 orderedScreenIds
376 .filterNot { screenId ->
377 isFirstPagePinnedItemEnabled &&
378 !SHOULD_SHOW_FIRST_PAGE_WIDGET &&
379 screenId == WorkspaceLayoutManager.FIRST_SCREEN_ID
380 }
381 .forEach { screenId ->
382 launcher.workspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId)
383 }
384 }
385
386 /**
387 * Remove odd number because they are already included when isTwoPanels and add the pair screen
388 * if not present.
389 */
390 private fun filterTwoPanelScreenIds(orderedScreenIds: LIntArray): LIntArray {
391 val screenIds = LIntSet.wrap(orderedScreenIds)
392 orderedScreenIds
393 .filter { screenId -> screenId % 2 == 1 }
394 .forEach { screenId ->
395 screenIds.remove(screenId)
396 // In case the pair is not added, add it
397 if (!launcher.workspace.containsScreenId(screenId - 1)) {
398 screenIds.add(screenId - 1)
399 }
400 }
401 return screenIds.array
402 }
403
404 override fun setIsFirstPagePinnedItemEnabled(isFirstPagePinnedItemEnabled: Boolean) {
405 this.isFirstPagePinnedItemEnabled = isFirstPagePinnedItemEnabled
406 launcher.workspace.bindAndInitFirstWorkspaceScreen()
407 }
408
409 override fun bindStringCache(cache: StringCache) {
410 stringCache = cache
411 launcher.appsView.updateWorkUI()
412 }
413
414 fun getIsFirstPagePinnedItemEnabled(): Boolean = isFirstPagePinnedItemEnabled
415
416 override fun getItemInflater() = launcher.itemInflater
417 }
418