| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.launcher3; |
| |
| import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED; |
| |
| import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD; |
| import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD; |
| import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_AVAILABLE; |
| import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_UNAVAILABLE; |
| import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing; |
| import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; |
| import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; |
| |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.LauncherApps; |
| import android.content.pm.PackageInstaller; |
| import android.content.pm.ShortcutInfo; |
| import android.os.UserHandle; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.util.Pair; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.WorkerThread; |
| |
| import com.android.launcher3.celllayout.CellPosMapper; |
| import com.android.launcher3.config.FeatureFlags; |
| import com.android.launcher3.icons.IconCache; |
| import com.android.launcher3.logging.FileLog; |
| import com.android.launcher3.model.AddWorkspaceItemsTask; |
| import com.android.launcher3.model.AllAppsList; |
| import com.android.launcher3.model.BaseModelUpdateTask; |
| import com.android.launcher3.model.BgDataModel; |
| import com.android.launcher3.model.BgDataModel.Callbacks; |
| import com.android.launcher3.model.CacheDataUpdatedTask; |
| import com.android.launcher3.model.ItemInstallQueue; |
| import com.android.launcher3.model.LauncherBinder; |
| import com.android.launcher3.model.LoaderTask; |
| import com.android.launcher3.model.ModelDbController; |
| import com.android.launcher3.model.ModelDelegate; |
| import com.android.launcher3.model.ModelWriter; |
| import com.android.launcher3.model.PackageIncrementalDownloadUpdatedTask; |
| import com.android.launcher3.model.PackageInstallStateChangedTask; |
| import com.android.launcher3.model.PackageUpdatedTask; |
| import com.android.launcher3.model.ReloadStringCacheTask; |
| import com.android.launcher3.model.ShortcutsChangedTask; |
| import com.android.launcher3.model.UserLockStateChangedTask; |
| import com.android.launcher3.model.data.AppInfo; |
| import com.android.launcher3.model.data.ItemInfo; |
| import com.android.launcher3.model.data.WorkspaceItemInfo; |
| import com.android.launcher3.pm.InstallSessionTracker; |
| import com.android.launcher3.pm.PackageInstallInfo; |
| import com.android.launcher3.pm.UserCache; |
| import com.android.launcher3.shortcuts.ShortcutRequest; |
| import com.android.launcher3.util.IntSet; |
| import com.android.launcher3.util.ItemInfoMatcher; |
| import com.android.launcher3.util.PackageUserKey; |
| import com.android.launcher3.util.Preconditions; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.concurrent.CancellationException; |
| import java.util.concurrent.Executor; |
| import java.util.function.Consumer; |
| import java.util.function.Supplier; |
| |
| /** |
| * Maintains in-memory state of the Launcher. It is expected that there should be only one |
| * LauncherModel object held in a static. Also provide APIs for updating the database state |
| * for the Launcher. |
| */ |
| public class LauncherModel extends LauncherApps.Callback implements InstallSessionTracker.Callback { |
| private static final boolean DEBUG_RECEIVER = false; |
| |
| static final String TAG = "Launcher.Model"; |
| |
| @NonNull |
| private final LauncherAppState mApp; |
| @NonNull |
| private final ModelDbController mModelDbController; |
| @NonNull |
| private final Object mLock = new Object(); |
| @Nullable |
| private LoaderTask mLoaderTask; |
| private boolean mIsLoaderTaskRunning; |
| |
| // only allow this once per reboot to reload work apps |
| private boolean mShouldReloadWorkProfile = true; |
| |
| // Indicates whether the current model data is valid or not. |
| // We start off with everything not loaded. After that, we assume that |
| // our monitoring of the package manager provides all updates and we never |
| // need to do a requery. This is only ever touched from the loader thread. |
| private boolean mModelLoaded; |
| private boolean mModelDestroyed = false; |
| public boolean isModelLoaded() { |
| synchronized (mLock) { |
| return mModelLoaded && mLoaderTask == null && !mModelDestroyed; |
| } |
| } |
| |
| @NonNull |
| private final ArrayList<Callbacks> mCallbacksList = new ArrayList<>(1); |
| |
| // < only access in worker thread > |
| @NonNull |
| private final AllAppsList mBgAllAppsList; |
| |
| /** |
| * All the static data should be accessed on the background thread, A lock should be acquired |
| * on this object when accessing any data from this model. |
| */ |
| @NonNull |
| private final BgDataModel mBgDataModel = new BgDataModel(); |
| |
| @NonNull |
| private final ModelDelegate mModelDelegate; |
| |
| private int mLastLoadId = -1; |
| |
| // Runnable to check if the shortcuts permission has changed. |
| @NonNull |
| private final Runnable mDataValidationCheck = new Runnable() { |
| @Override |
| public void run() { |
| if (mModelLoaded) { |
| mModelDelegate.validateData(); |
| } |
| } |
| }; |
| |
| LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app, |
| @NonNull final IconCache iconCache, @NonNull final AppFilter appFilter, |
| final boolean isPrimaryInstance) { |
| mApp = app; |
| mModelDbController = new ModelDbController(context); |
| mBgAllAppsList = new AllAppsList(iconCache, appFilter); |
| mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel, |
| isPrimaryInstance); |
| } |
| |
| @NonNull |
| public ModelDelegate getModelDelegate() { |
| return mModelDelegate; |
| } |
| |
| public ModelDbController getModelDbController() { |
| return mModelDbController; |
| } |
| |
| /** |
| * Adds the provided items to the workspace. |
| */ |
| public void addAndBindAddedWorkspaceItems( |
| @NonNull final List<Pair<ItemInfo, Object>> itemList) { |
| for (Callbacks cb : getCallbacks()) { |
| cb.preAddApps(); |
| } |
| enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList)); |
| } |
| |
| @NonNull |
| public ModelWriter getWriter(final boolean hasVerticalHotseat, final boolean verifyChanges, |
| CellPosMapper cellPosMapper, @Nullable final Callbacks owner) { |
| return new ModelWriter(mApp.getContext(), this, mBgDataModel, |
| hasVerticalHotseat, verifyChanges, cellPosMapper, owner); |
| } |
| |
| @Override |
| public void onPackageChanged( |
| @NonNull final String packageName, @NonNull final UserHandle user) { |
| int op = PackageUpdatedTask.OP_UPDATE; |
| enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); |
| } |
| |
| @Override |
| public void onPackageRemoved( |
| @NonNull final String packageName, @NonNull final UserHandle user) { |
| onPackagesRemoved(user, packageName); |
| } |
| |
| public void onPackagesRemoved( |
| @NonNull final UserHandle user, @NonNull final String... packages) { |
| int op = PackageUpdatedTask.OP_REMOVE; |
| FileLog.d(TAG, "package removed received " + TextUtils.join(",", packages)); |
| enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages)); |
| } |
| |
| @Override |
| public void onPackageAdded(@NonNull final String packageName, @NonNull final UserHandle user) { |
| int op = PackageUpdatedTask.OP_ADD; |
| enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); |
| } |
| |
| @Override |
| public void onPackagesAvailable(@NonNull final String[] packageNames, |
| @NonNull final UserHandle user, final boolean replacing) { |
| enqueueModelUpdateTask( |
| new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames)); |
| } |
| |
| @Override |
| public void onPackagesUnavailable(@NonNull final String[] packageNames, |
| @NonNull final UserHandle user, final boolean replacing) { |
| if (!replacing) { |
| enqueueModelUpdateTask(new PackageUpdatedTask( |
| PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames)); |
| } |
| } |
| |
| @Override |
| public void onPackagesSuspended( |
| @NonNull final String[] packageNames, @NonNull final UserHandle user) { |
| enqueueModelUpdateTask(new PackageUpdatedTask( |
| PackageUpdatedTask.OP_SUSPEND, user, packageNames)); |
| } |
| |
| @Override |
| public void onPackagesUnsuspended( |
| @NonNull final String[] packageNames, @NonNull final UserHandle user) { |
| enqueueModelUpdateTask(new PackageUpdatedTask( |
| PackageUpdatedTask.OP_UNSUSPEND, user, packageNames)); |
| } |
| |
| @Override |
| public void onPackageLoadingProgressChanged(@NonNull final String packageName, |
| @NonNull final UserHandle user, final float progress) { |
| if (Utilities.ATLEAST_S) { |
| enqueueModelUpdateTask(new PackageIncrementalDownloadUpdatedTask( |
| packageName, user, progress)); |
| } |
| } |
| |
| @Override |
| public void onShortcutsChanged(@NonNull final String packageName, |
| @NonNull final List<ShortcutInfo> shortcuts, @NonNull final UserHandle user) { |
| enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true)); |
| } |
| |
| /** |
| * Called when the icon for an app changes, outside of package event |
| */ |
| @WorkerThread |
| public void onAppIconChanged(@NonNull final String packageName, |
| @NonNull final UserHandle user) { |
| // Update the icon for the calendar package |
| Context context = mApp.getContext(); |
| onPackageChanged(packageName, user); |
| |
| List<ShortcutInfo> pinnedShortcuts = new ShortcutRequest(context, user) |
| .forPackage(packageName).query(ShortcutRequest.PINNED); |
| if (!pinnedShortcuts.isEmpty()) { |
| enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, pinnedShortcuts, user, |
| false)); |
| } |
| } |
| |
| /** |
| * Called when the workspace items have drastically changed |
| */ |
| public void onWorkspaceUiChanged() { |
| MODEL_EXECUTOR.execute(mModelDelegate::workspaceLoadComplete); |
| } |
| |
| /** |
| * Called when the model is destroyed |
| */ |
| public void destroy() { |
| mModelDestroyed = true; |
| MODEL_EXECUTOR.execute(mModelDelegate::destroy); |
| } |
| |
| public void onBroadcastIntent(@NonNull final Intent intent) { |
| if (DEBUG_RECEIVER || sDebugTracing) Log.d(TAG, "onReceive intent=" + intent); |
| final String action = intent.getAction(); |
| if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { |
| // If we have changed locale we need to clear out the labels in all apps/workspace. |
| forceReload(); |
| } else if (ACTION_DEVICE_POLICY_RESOURCE_UPDATED.equals(action)) { |
| enqueueModelUpdateTask(new ReloadStringCacheTask(mModelDelegate)); |
| } else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) { |
| for (Callbacks cb : getCallbacks()) { |
| if (cb instanceof Launcher) { |
| ((Launcher) cb).recreate(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Called then there use a user event |
| * @see UserCache#addUserEventListener |
| */ |
| public void onUserEvent(UserHandle user, String action) { |
| if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) |
| && mShouldReloadWorkProfile) { |
| mShouldReloadWorkProfile = false; |
| forceReload(); |
| } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) |
| || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { |
| mShouldReloadWorkProfile = false; |
| enqueueModelUpdateTask(new PackageUpdatedTask( |
| PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)); |
| } else if (UserCache.ACTION_PROFILE_LOCKED.equals(action) |
| || UserCache.ACTION_PROFILE_UNLOCKED.equals(action)) { |
| enqueueModelUpdateTask(new UserLockStateChangedTask( |
| user, UserCache.ACTION_PROFILE_UNLOCKED.equals(action))); |
| } else if (UserCache.ACTION_PROFILE_ADDED.equals(action) |
| || UserCache.ACTION_PROFILE_REMOVED.equals(action)) { |
| forceReload(); |
| } else if (ACTION_PROFILE_AVAILABLE.equals(action) |
| || ACTION_PROFILE_UNAVAILABLE.equals(action)) { |
| /* |
| * This broadcast is only available when android.os.Flags.allowPrivateProfile() is set. |
| * For Work-profile this broadcast will be sent in addition to |
| * ACTION_MANAGED_PROFILE_AVAILABLE/UNAVAILABLE. |
| * So effectively, this if block only handles the non-work profile case. |
| */ |
| enqueueModelUpdateTask(new PackageUpdatedTask( |
| PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)); |
| } |
| } |
| |
| /** |
| * Reloads the workspace items from the DB and re-binds the workspace. This should generally |
| * not be called as DB updates are automatically followed by UI update |
| */ |
| public void forceReload() { |
| synchronized (mLock) { |
| // Stop any existing loaders first, so they don't set mModelLoaded to true later |
| stopLoader(); |
| mModelLoaded = false; |
| } |
| |
| // Start the loader if launcher is already running, otherwise the loader will run, |
| // the next time launcher starts |
| if (hasCallbacks()) { |
| startLoader(); |
| } |
| } |
| |
| /** |
| * Rebinds all existing callbacks with already loaded model |
| */ |
| public void rebindCallbacks() { |
| if (hasCallbacks()) { |
| startLoader(); |
| } |
| } |
| |
| /** |
| * Removes an existing callback |
| */ |
| public void removeCallbacks(@NonNull final Callbacks callbacks) { |
| synchronized (mCallbacksList) { |
| Preconditions.assertUIThread(); |
| if (mCallbacksList.remove(callbacks)) { |
| if (stopLoader()) { |
| // Rebind existing callbacks |
| startLoader(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Adds a callbacks to receive model updates |
| * @return true if workspace load was performed synchronously |
| */ |
| public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) { |
| synchronized (mLock) { |
| addCallbacks(callbacks); |
| return startLoader(new Callbacks[] { callbacks }); |
| |
| } |
| } |
| |
| /** |
| * Adds a callbacks to receive model updates |
| */ |
| public void addCallbacks(@NonNull final Callbacks callbacks) { |
| Preconditions.assertUIThread(); |
| synchronized (mCallbacksList) { |
| mCallbacksList.add(callbacks); |
| } |
| } |
| |
| /** |
| * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible. |
| * @return true if the page could be bound synchronously. |
| */ |
| public boolean startLoader() { |
| return startLoader(new Callbacks[0]); |
| } |
| |
| private boolean startLoader(@NonNull final Callbacks[] newCallbacks) { |
| // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems |
| ItemInstallQueue.INSTANCE.get(mApp.getContext()) |
| .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING); |
| synchronized (mLock) { |
| // If there is already one running, tell it to stop. |
| boolean wasRunning = stopLoader(); |
| boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning; |
| boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0; |
| final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks; |
| |
| if (callbacksList.length > 0) { |
| // Clear any pending bind-runnables from the synchronized load process. |
| for (Callbacks cb : callbacksList) { |
| MAIN_EXECUTOR.execute(cb::clearPendingBinds); |
| } |
| |
| LauncherBinder launcherBinder = new LauncherBinder( |
| mApp, mBgDataModel, mBgAllAppsList, callbacksList); |
| if (bindDirectly) { |
| // Divide the set of loaded items into those that we are binding synchronously, |
| // and everything else that is to be bound normally (asynchronously). |
| launcherBinder.bindWorkspace(bindAllCallbacks, /* isBindSync= */ true); |
| // For now, continue posting the binding of AllApps as there are other |
| // issues that arise from that. |
| launcherBinder.bindAllApps(); |
| launcherBinder.bindDeepShortcuts(); |
| launcherBinder.bindWidgets(); |
| if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) { |
| mModelDelegate.bindAllModelExtras(callbacksList); |
| } |
| return true; |
| } else { |
| stopLoader(); |
| mLoaderTask = new LoaderTask( |
| mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, launcherBinder); |
| |
| // Always post the loader task, instead of running directly |
| // (even on same thread) so that we exit any nested synchronized blocks |
| MODEL_EXECUTOR.post(mLoaderTask); |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * If there is already a loader task running, tell it to stop. |
| * @return true if an existing loader was stopped. |
| */ |
| private boolean stopLoader() { |
| synchronized (mLock) { |
| LoaderTask oldTask = mLoaderTask; |
| mLoaderTask = null; |
| if (oldTask != null) { |
| oldTask.stopLocked(); |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Loads the model if not loaded |
| * @param callback called with the data model upon successful load or null on model thread. |
| */ |
| public void loadAsync(@NonNull final Consumer<BgDataModel> callback) { |
| synchronized (mLock) { |
| if (!mModelLoaded && !mIsLoaderTaskRunning) { |
| startLoader(); |
| } |
| } |
| MODEL_EXECUTOR.post(() -> callback.accept(isModelLoaded() ? mBgDataModel : null)); |
| } |
| |
| @Override |
| public void onInstallSessionCreated(@NonNull final PackageInstallInfo sessionInfo) { |
| if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) { |
| enqueueModelUpdateTask(new BaseModelUpdateTask() { |
| @Override |
| public void execute(@NonNull final LauncherAppState app, |
| @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) { |
| apps.addPromiseApp(app.getContext(), sessionInfo); |
| bindApplicationsIfNeeded(); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void onSessionFailure(@NonNull final String packageName, |
| @NonNull final UserHandle user) { |
| enqueueModelUpdateTask(new BaseModelUpdateTask() { |
| @Override |
| public void execute(@NonNull final LauncherAppState app, |
| @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) { |
| final IntSet removedIds = new IntSet(); |
| synchronized (dataModel) { |
| for (ItemInfo info : dataModel.itemsIdMap) { |
| if (info instanceof WorkspaceItemInfo |
| && ((WorkspaceItemInfo) info).hasPromiseIconUi() |
| && user.equals(info.user) |
| && info.getIntent() != null |
| && TextUtils.equals(packageName, info.getIntent().getPackage())) { |
| removedIds.add(info.id); |
| } |
| } |
| } |
| |
| if (!removedIds.isEmpty()) { |
| deleteAndBindComponentsRemoved( |
| ItemInfoMatcher.ofItemIds(removedIds), |
| "removed because install session failed"); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void onPackageStateChanged(@NonNull final PackageInstallInfo installInfo) { |
| enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo)); |
| } |
| |
| /** |
| * Updates the icons and label of all pending icons for the provided package name. |
| */ |
| @Override |
| public void onUpdateSessionDisplay(@NonNull final PackageUserKey key, |
| @NonNull final PackageInstaller.SessionInfo info) { |
| mApp.getIconCache().updateSessionCache(key, info); |
| |
| HashSet<String> packages = new HashSet<>(); |
| packages.add(key.mPackageName); |
| enqueueModelUpdateTask(new CacheDataUpdatedTask( |
| CacheDataUpdatedTask.OP_SESSION_UPDATE, key.mUser, packages)); |
| } |
| |
| public class LoaderTransaction implements AutoCloseable { |
| |
| @NonNull |
| private final LoaderTask mTask; |
| |
| private LoaderTransaction(@NonNull final LoaderTask task) throws CancellationException { |
| synchronized (mLock) { |
| if (mLoaderTask != task) { |
| throw new CancellationException("Loader already stopped"); |
| } |
| mLastLoadId++; |
| mTask = task; |
| mIsLoaderTaskRunning = true; |
| mModelLoaded = false; |
| } |
| } |
| |
| public void commit() { |
| synchronized (mLock) { |
| // Everything loaded bind the data. |
| mModelLoaded = true; |
| } |
| } |
| |
| @Override |
| public void close() { |
| synchronized (mLock) { |
| // If we are still the last one to be scheduled, remove ourselves. |
| if (mLoaderTask == mTask) { |
| mLoaderTask = null; |
| } |
| mIsLoaderTaskRunning = false; |
| } |
| } |
| } |
| |
| public LoaderTransaction beginLoader(@NonNull final LoaderTask task) |
| throws CancellationException { |
| return new LoaderTransaction(task); |
| } |
| |
| /** |
| * Refreshes the cached shortcuts if the shortcut permission has changed. |
| * Current implementation simply reloads the workspace, but it can be optimized to |
| * use partial updates similar to {@link UserCache} |
| */ |
| public void validateModelDataOnResume() { |
| MODEL_EXECUTOR.getHandler().removeCallbacks(mDataValidationCheck); |
| MODEL_EXECUTOR.post(mDataValidationCheck); |
| } |
| |
| /** |
| * Called when the icons for packages have been updated in the icon cache. |
| */ |
| public void onPackageIconsUpdated(@NonNull final HashSet<String> updatedPackages, |
| @NonNull final UserHandle user) { |
| // If any package icon has changed (app was updated while launcher was dead), |
| // update the corresponding shortcuts. |
| enqueueModelUpdateTask(new CacheDataUpdatedTask( |
| CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)); |
| } |
| |
| /** |
| * Called when the labels for the widgets has updated in the icon cache. |
| */ |
| public void onWidgetLabelsUpdated(@NonNull final HashSet<String> updatedPackages, |
| @NonNull final UserHandle user) { |
| enqueueModelUpdateTask(new BaseModelUpdateTask() { |
| @Override |
| public void execute(@NonNull final LauncherAppState app, |
| @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) { |
| dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, app); |
| bindUpdatedWidgets(dataModel); |
| } |
| }); |
| } |
| |
| public void enqueueModelUpdateTask(@NonNull final ModelUpdateTask task) { |
| if (mModelDestroyed) { |
| return; |
| } |
| task.init(mApp, this, mBgDataModel, mBgAllAppsList, MAIN_EXECUTOR); |
| MODEL_EXECUTOR.execute(task); |
| } |
| |
| /** |
| * A task to be executed on the current callbacks on the UI thread. |
| * If there is no current callbacks, the task is ignored. |
| */ |
| public interface CallbackTask { |
| |
| void execute(@NonNull Callbacks callbacks); |
| } |
| |
| /** |
| * A runnable which changes/updates the data model of the launcher based on certain events. |
| */ |
| public interface ModelUpdateTask extends Runnable { |
| |
| /** |
| * Called before the task is posted to initialize the internal state. |
| */ |
| void init(@NonNull LauncherAppState app, @NonNull LauncherModel model, |
| @NonNull BgDataModel dataModel, @NonNull AllAppsList allAppsList, |
| @NonNull Executor uiExecutor); |
| |
| } |
| |
| public void updateAndBindWorkspaceItem(@NonNull final WorkspaceItemInfo si, |
| @NonNull final ShortcutInfo info) { |
| updateAndBindWorkspaceItem(() -> { |
| si.updateFromDeepShortcutInfo(info, mApp.getContext()); |
| mApp.getIconCache().getShortcutIcon(si, info); |
| return si; |
| }); |
| } |
| |
| /** |
| * Utility method to update a shortcut on the background thread. |
| */ |
| public void updateAndBindWorkspaceItem( |
| @NonNull final Supplier<WorkspaceItemInfo> itemProvider) { |
| enqueueModelUpdateTask(new BaseModelUpdateTask() { |
| @Override |
| public void execute(@NonNull final LauncherAppState app, |
| @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) { |
| WorkspaceItemInfo info = itemProvider.get(); |
| getModelWriter().updateItemInDatabase(info); |
| ArrayList<WorkspaceItemInfo> update = new ArrayList<>(); |
| update.add(info); |
| bindUpdatedWorkspaceItems(update); |
| } |
| }); |
| } |
| |
| public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) { |
| enqueueModelUpdateTask(new BaseModelUpdateTask() { |
| @Override |
| public void execute(@NonNull final LauncherAppState app, |
| @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) { |
| dataModel.widgetsModel.update(app, packageUser); |
| bindUpdatedWidgets(dataModel); |
| } |
| }); |
| } |
| |
| public void dumpState(@Nullable final String prefix, @Nullable final FileDescriptor fd, |
| @NonNull final PrintWriter writer, @NonNull final String[] args) { |
| if (args.length > 0 && TextUtils.equals(args[0], "--all")) { |
| writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size()); |
| for (AppInfo info : mBgAllAppsList.data) { |
| writer.println(prefix + " title=\"" + info.title |
| + "\" bitmapIcon=" + info.bitmap.icon |
| + " componentName=" + info.componentName.getPackageName()); |
| } |
| writer.println(); |
| } |
| mModelDelegate.dump(prefix, fd, writer, args); |
| mBgDataModel.dump(prefix, fd, writer, args); |
| } |
| |
| /** |
| * Returns true if there are any callbacks attached to the model |
| */ |
| public boolean hasCallbacks() { |
| synchronized (mCallbacksList) { |
| return !mCallbacksList.isEmpty(); |
| } |
| } |
| |
| /** |
| * Returns an array of currently attached callbacks |
| */ |
| @NonNull |
| public Callbacks[] getCallbacks() { |
| synchronized (mCallbacksList) { |
| return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]); |
| } |
| } |
| |
| /** |
| * Returns the ID for the last model load. If the load ID doesn't match for a transaction, the |
| * transaction should be ignored. |
| */ |
| public int getLastLoadId() { |
| return mLastLoadId; |
| } |
| } |