summaryrefslogtreecommitdiff
path: root/src/com/android/launcher3/ModelCallbacks.kt
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/launcher3/ModelCallbacks.kt')
-rw-r--r--src/com/android/launcher3/ModelCallbacks.kt416
1 files changed, 416 insertions, 0 deletions
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
new file mode 100644
index 0000000000..51729992d4
--- /dev/null
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -0,0 +1,416 @@
+package com.android.launcher3
+
+import android.annotation.TargetApi
+import android.os.Build
+import android.os.Trace
+import android.view.ViewTreeObserver.OnDrawListener
+import androidx.annotation.UiThread
+import com.android.launcher3.LauncherConstants.TraceEvents
+import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID
+import com.android.launcher3.allapps.AllAppsStore
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget
+import com.android.launcher3.model.BgDataModel
+import com.android.launcher3.model.StringCache
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.popup.PopupContainerWithArrow
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.IntArray as LIntArray
+import com.android.launcher3.util.IntSet as LIntSet
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.Preconditions
+import com.android.launcher3.util.RunnableList
+import com.android.launcher3.util.TraceHelper
+import com.android.launcher3.util.ViewOnDrawExecutor
+import com.android.launcher3.widget.PendingAddWidgetInfo
+import com.android.launcher3.widget.model.WidgetsListBaseEntry
+import java.util.function.Predicate
+
+class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks {
+
+ var synchronouslyBoundPages = LIntSet()
+ var pagesToBindSynchronously = LIntSet()
+
+ private var isFirstPagePinnedItemEnabled =
+ (BuildConfig.QSB_ON_FIRST_SCREEN && !FeatureFlags.ENABLE_SMARTSPACE_REMOVAL.get())
+
+ var stringCache: StringCache? = null
+
+ var pendingExecutor: ViewOnDrawExecutor? = null
+
+ var workspaceLoading = true
+
+ /**
+ * Refreshes the shortcuts shown on the workspace.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ override fun startBinding() {
+ TraceHelper.INSTANCE.beginSection("startBinding")
+ // Floating panels (except the full widget sheet) are associated with individual icons. If
+ // we are starting a fresh bind, close all such panels as all the icons are about
+ // to go away.
+ AbstractFloatingView.closeOpenViews(
+ launcher,
+ true,
+ AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv()
+ )
+ workspaceLoading = true
+
+ // Clear the workspace because it's going to be rebound
+ launcher.dragController.cancelDrag()
+ launcher.workspace.clearDropTargets()
+ launcher.workspace.removeAllWorkspaceScreens()
+ launcher.appWidgetHolder.clearViews()
+ launcher.hotseat?.resetLayout(launcher.deviceProfile.isVerticalBarLayout)
+ TraceHelper.INSTANCE.endSection()
+ }
+
+ @TargetApi(Build.VERSION_CODES.S)
+ override fun onInitialBindComplete(
+ boundPages: LIntSet,
+ pendingTasks: RunnableList,
+ workspaceItemCount: Int,
+ isBindSync: Boolean
+ ) {
+ synchronouslyBoundPages = boundPages
+ pagesToBindSynchronously = LIntSet()
+ clearPendingBinds()
+ val executor = ViewOnDrawExecutor(pendingTasks)
+ pendingExecutor = executor
+ if (!launcher.isInState(LauncherState.ALL_APPS)) {
+ launcher.appsView.appsStore.enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW)
+ pendingTasks.add {
+ launcher.appsView.appsStore.disableDeferUpdates(
+ AllAppsStore.DEFER_UPDATES_NEXT_DRAW
+ )
+ }
+ }
+ executor.onLoadAnimationCompleted()
+ executor.attachTo(launcher)
+ if (Utilities.ATLEAST_S) {
+ Trace.endAsyncSection(
+ TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
+ TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE
+ )
+ }
+ launcher.bindComplete(workspaceItemCount, isBindSync)
+ launcher.rootView.viewTreeObserver.addOnDrawListener(
+ object : OnDrawListener {
+ override fun onDraw() {
+ Executors.MAIN_EXECUTOR.handler.postAtFrontOfQueue {
+ launcher.rootView.getViewTreeObserver().removeOnDrawListener(this)
+ }
+ }
+ }
+ )
+ }
+
+ /**
+ * Callback saying that there aren't any more items to bind.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ override fun finishBindingItems(pagesBoundFirst: LIntSet?) {
+ TraceHelper.INSTANCE.beginSection("finishBindingItems")
+ val deviceProfile = launcher.deviceProfile
+ launcher.workspace.restoreInstanceStateForRemainingPages()
+ workspaceLoading = false
+ launcher.processActivityResult()
+ val currentPage =
+ if (pagesBoundFirst != null && !pagesBoundFirst.isEmpty)
+ launcher.workspace.getPageIndexForScreenId(pagesBoundFirst.array[0])
+ else PagedView.INVALID_PAGE
+ // When undoing the removal of the last item on a page, return to that page.
+ // Since we are just resetting the current page without user interaction,
+ // override the previous page so we don't log the page switch.
+ launcher.workspace.setCurrentPage(currentPage, currentPage /* overridePrevPage */)
+ pagesToBindSynchronously = LIntSet()
+
+ // Cache one page worth of icons
+ launcher.viewCache.setCacheSize(
+ R.layout.folder_application,
+ deviceProfile.numFolderColumns * deviceProfile.numFolderRows
+ )
+ launcher.viewCache.setCacheSize(R.layout.folder_page, 2)
+ TraceHelper.INSTANCE.endSection()
+ launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true)
+ launcher.workspace.pageIndicator.setAreScreensBinding(false, deviceProfile.isTwoPanels)
+ }
+
+ /**
+ * Clear any pending bind callbacks. This is called when is loader is planning to perform a full
+ * rebind from scratch.
+ */
+ override fun clearPendingBinds() {
+ pendingExecutor?.cancel() ?: return
+ pendingExecutor = null
+
+ // We might have set this flag previously and forgot to clear it.
+ launcher.appsView.appsStore.disableDeferUpdatesSilently(
+ AllAppsStore.DEFER_UPDATES_NEXT_DRAW
+ )
+ }
+
+ override fun preAddApps() {
+ // If there's an undo snackbar, force it to complete to ensure empty screens are removed
+ // before trying to add new items.
+ launcher.modelWriter.commitDelete()
+ val snackbar =
+ AbstractFloatingView.getOpenView<AbstractFloatingView>(
+ launcher,
+ AbstractFloatingView.TYPE_SNACKBAR
+ )
+ snackbar?.post { snackbar.close(true) }
+ }
+
+ @UiThread
+ override fun bindAllApplications(
+ apps: Array<AppInfo?>?,
+ flags: Int,
+ packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?
+ ) {
+ Preconditions.assertUIThread()
+ val hadWorkApps = launcher.appsView.shouldShowTabs()
+ launcher.appsView.appsStore.setApps(apps, flags, packageUserKeytoUidMap)
+ PopupContainerWithArrow.dismissInvalidPopup(launcher)
+ if (hadWorkApps != launcher.appsView.shouldShowTabs()) {
+ launcher.stateManager.goToState(LauncherState.NORMAL)
+ }
+ }
+
+ /**
+ * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
+ * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
+ */
+ override fun bindDeepShortcutMap(deepShortcutMapCopy: HashMap<ComponentKey?, Int?>?) {
+ launcher.popupDataProvider.setDeepShortcutMap(deepShortcutMapCopy)
+ }
+
+ override fun bindIncrementalDownloadProgressUpdated(app: AppInfo?) {
+ launcher.appsView.appsStore.updateProgressBar(app)
+ }
+
+ override fun bindWidgetsRestored(widgets: ArrayList<LauncherAppWidgetInfo?>?) {
+ launcher.workspace.widgetsRestored(widgets)
+ }
+
+ /**
+ * Some shortcuts were updated in the background. Implementation of the method from
+ * LauncherModel.Callbacks.
+ *
+ * @param updated list of shortcuts which have changed.
+ */
+ override fun bindWorkspaceItemsChanged(updated: List<WorkspaceItemInfo?>) {
+ if (updated.isNotEmpty()) {
+ launcher.workspace.updateWorkspaceItems(updated, launcher)
+ PopupContainerWithArrow.dismissInvalidPopup(launcher)
+ }
+ }
+
+ /**
+ * Update the state of a package, typically related to install state. Implementation of the
+ * method from LauncherModel.Callbacks.
+ */
+ override fun bindRestoreItemsChange(updates: HashSet<ItemInfo?>?) {
+ launcher.workspace.updateRestoreItems(updates, launcher)
+ }
+
+ /**
+ * A package was uninstalled/updated. We take both the super set of packageNames in addition to
+ * specific applications to remove, the reason being that this can be called when a package is
+ * updated as well. In that scenario, we only remove specific components from the workspace and
+ * hotseat, where as package-removal should clear all items by package name.
+ */
+ override fun bindWorkspaceComponentsRemoved(matcher: Predicate<ItemInfo?>?) {
+ launcher.workspace.removeItemsByMatcher(matcher)
+ launcher.dragController.onAppsRemoved(matcher)
+ PopupContainerWithArrow.dismissInvalidPopup(launcher)
+ }
+
+ override fun bindAllWidgets(allWidgets: List<WidgetsListBaseEntry?>?) {
+ launcher.popupDataProvider.allWidgets = allWidgets
+ }
+
+ /** Returns the ids of the workspaces to bind. */
+ override fun getPagesToBindSynchronously(orderedScreenIds: LIntArray): LIntSet {
+ // If workspace binding is still in progress, getCurrentPageScreenIds won't be
+ // accurate, and we should use mSynchronouslyBoundPages that's set during initial binding.
+ val visibleIds =
+ when {
+ !pagesToBindSynchronously.isEmpty -> pagesToBindSynchronously
+ !workspaceLoading -> launcher.workspace.currentPageScreenIds
+ else -> synchronouslyBoundPages
+ }
+ // Launcher IntArray has the same name as Kotlin IntArray
+ val result = LIntSet()
+ if (visibleIds.isEmpty) {
+ return result
+ }
+ val actualIds = orderedScreenIds.clone()
+ val firstId = visibleIds.first()
+ val pairId = launcher.workspace.getScreenPair(firstId)
+ // Double check that actual screenIds contains the visibleId, as empty screens are hidden
+ // in single panel.
+ if (actualIds.contains(firstId)) {
+ result.add(firstId)
+ if (launcher.deviceProfile.isTwoPanels && actualIds.contains(pairId)) {
+ result.add(pairId)
+ }
+ } else if (
+ LauncherAppState.getIDP(launcher).supportedProfiles.any(DeviceProfile::isTwoPanels) &&
+ actualIds.contains(pairId)
+ ) {
+ // Add the right panel if left panel is hidden when switching display, due to empty
+ // pages being hidden in single panel.
+ result.add(pairId)
+ }
+ return result
+ }
+
+ override fun bindSmartspaceWidget() {
+ val cl: CellLayout? =
+ launcher.workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID)
+ val spanX = InvariantDeviceProfile.INSTANCE.get(launcher).numSearchContainerColumns
+
+ if (cl?.isRegionVacant(0, 0, spanX, 1) != true) {
+ return
+ }
+
+ val widgetsListBaseEntry: WidgetsListBaseEntry =
+ launcher.popupDataProvider.allWidgets.firstOrNull { item: WidgetsListBaseEntry ->
+ item.mPkgItem.packageName == BuildConfig.APPLICATION_ID
+ }
+ ?: return
+
+ val info =
+ PendingAddWidgetInfo(
+ widgetsListBaseEntry.mWidgets[0].widgetInfo,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP
+ )
+ launcher.addPendingItem(
+ info,
+ info.container,
+ WorkspaceLayoutManager.FIRST_SCREEN_ID,
+ intArrayOf(0, 0),
+ info.spanX,
+ info.spanY
+ )
+ }
+
+ override fun bindScreens(orderedScreenIds: LIntArray) {
+ launcher.workspace.pageIndicator.setAreScreensBinding(
+ true,
+ launcher.deviceProfile.isTwoPanels
+ )
+ val firstScreenPosition = 0
+ if (
+ (FeatureFlags.QSB_ON_FIRST_SCREEN &&
+ isFirstPagePinnedItemEnabled &&
+ !shouldShowFirstPageWidget()) &&
+ orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition
+ ) {
+ orderedScreenIds.removeValue(FIRST_SCREEN_ID)
+ orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID)
+ } else if (
+ (!FeatureFlags.QSB_ON_FIRST_SCREEN && !isFirstPagePinnedItemEnabled ||
+ shouldShowFirstPageWidget()) && orderedScreenIds.isEmpty
+ ) {
+ // If there are no screens, we need to have an empty screen
+ launcher.workspace.addExtraEmptyScreens()
+ }
+ bindAddScreens(orderedScreenIds)
+
+ // After we have added all the screens, if the wallpaper was locked to the default state,
+ // then notify to indicate that it can be released and a proper wallpaper offset can be
+ // computed before the next layout
+ launcher.workspace.unlockWallpaperFromDefaultPageOnNextLayout()
+ }
+
+ override fun bindAppsAdded(
+ newScreens: LIntArray?,
+ addNotAnimated: java.util.ArrayList<ItemInfo?>?,
+ addAnimated: java.util.ArrayList<ItemInfo?>?
+ ) {
+ // Add the new screens
+ if (newScreens != null) {
+ // newScreens can contain an empty right panel that is already bound, but not known
+ // by BgDataModel.
+ newScreens.removeAllValues(launcher.workspace.mScreenOrder)
+ bindAddScreens(newScreens)
+ }
+
+ // We add the items without animation on non-visible pages, and with
+ // animations on the new page (which we will try and snap to).
+ if (!addNotAnimated.isNullOrEmpty()) {
+ launcher.bindItems(addNotAnimated, false)
+ }
+ if (!addAnimated.isNullOrEmpty()) {
+ launcher.bindItems(addAnimated, true)
+ }
+
+ // Remove the extra empty screen
+ launcher.workspace.removeExtraEmptyScreen(false)
+ }
+
+ private fun bindAddScreens(orderedScreenIdsArg: LIntArray) {
+ var orderedScreenIds = orderedScreenIdsArg
+ if (launcher.deviceProfile.isTwoPanels) {
+ if (FeatureFlags.FOLDABLE_SINGLE_PAGE.get()) {
+ orderedScreenIds = filterTwoPanelScreenIds(orderedScreenIds)
+ } else {
+ // Some empty pages might have been removed while the phone was in a single panel
+ // mode, so we want to add those empty pages back.
+ val screenIds = LIntSet.wrap(orderedScreenIds)
+ orderedScreenIds.forEach { screenId: Int ->
+ screenIds.add(launcher.workspace.getScreenPair(screenId))
+ }
+ orderedScreenIds = screenIds.array
+ }
+ }
+ orderedScreenIds
+ .filterNot { screenId ->
+ FeatureFlags.QSB_ON_FIRST_SCREEN &&
+ isFirstPagePinnedItemEnabled &&
+ !FeatureFlags.shouldShowFirstPageWidget() &&
+ screenId == WorkspaceLayoutManager.FIRST_SCREEN_ID
+ }
+ .forEach { screenId ->
+ launcher.workspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId)
+ }
+ }
+
+ /**
+ * Remove odd number because they are already included when isTwoPanels and add the pair screen
+ * if not present.
+ */
+ private fun filterTwoPanelScreenIds(orderedScreenIds: LIntArray): LIntArray {
+ val screenIds = LIntSet.wrap(orderedScreenIds)
+ orderedScreenIds
+ .filter { screenId -> screenId % 2 == 1 }
+ .forEach { screenId ->
+ screenIds.remove(screenId)
+ // In case the pair is not added, add it
+ if (!launcher.workspace.containsScreenId(screenId - 1)) {
+ screenIds.add(screenId - 1)
+ }
+ }
+ return screenIds.array
+ }
+
+ override fun setIsFirstPagePinnedItemEnabled(isFirstPagePinnedItemEnabled: Boolean) {
+ this.isFirstPagePinnedItemEnabled = isFirstPagePinnedItemEnabled
+ launcher.workspace.bindAndInitFirstWorkspaceScreen()
+ }
+
+ override fun bindStringCache(cache: StringCache) {
+ stringCache = cache
+ launcher.appsView.updateWorkUI()
+ }
+
+ fun getIsFirstPagePinnedItemEnabled(): Boolean = isFirstPagePinnedItemEnabled
+}