diff options
Diffstat (limited to 'src/com/android/launcher3/ModelCallbacks.kt')
-rw-r--r-- | src/com/android/launcher3/ModelCallbacks.kt | 416 |
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 +} |