| /* |
| * Copyright (C) 2018 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.graphics; |
| |
| import static android.view.View.MeasureSpec.EXACTLY; |
| import static android.view.View.MeasureSpec.makeMeasureSpec; |
| import static android.view.View.VISIBLE; |
| |
| import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; |
| import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER; |
| import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; |
| import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks; |
| import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially; |
| import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; |
| |
| import android.annotation.TargetApi; |
| import android.app.Fragment; |
| import android.appwidget.AppWidgetHostView; |
| import android.appwidget.AppWidgetProviderInfo; |
| import android.content.Context; |
| import android.content.ContextWrapper; |
| import android.content.Intent; |
| import android.content.pm.ShortcutInfo; |
| import android.content.res.TypedArray; |
| import android.graphics.Color; |
| import android.graphics.Rect; |
| import android.graphics.drawable.AdaptiveIconDrawable; |
| import android.graphics.drawable.ColorDrawable; |
| import android.os.Build; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Process; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.ContextThemeWrapper; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.TextClock; |
| |
| import com.android.launcher3.BubbleTextView; |
| import com.android.launcher3.CellLayout; |
| import com.android.launcher3.DeviceProfile; |
| import com.android.launcher3.Hotseat; |
| import com.android.launcher3.InsettableFrameLayout; |
| import com.android.launcher3.InvariantDeviceProfile; |
| import com.android.launcher3.LauncherAppState; |
| import com.android.launcher3.LauncherModel; |
| import com.android.launcher3.LauncherSettings; |
| import com.android.launcher3.LauncherSettings.Favorites; |
| import com.android.launcher3.R; |
| import com.android.launcher3.WorkspaceLayoutManager; |
| import com.android.launcher3.config.FeatureFlags; |
| import com.android.launcher3.folder.FolderIcon; |
| import com.android.launcher3.icons.BaseIconFactory; |
| import com.android.launcher3.icons.BitmapInfo; |
| import com.android.launcher3.icons.LauncherIcons; |
| import com.android.launcher3.model.AllAppsList; |
| import com.android.launcher3.model.BgDataModel; |
| import com.android.launcher3.model.BgDataModel.Callbacks; |
| import com.android.launcher3.model.BgDataModel.FixedContainerItems; |
| import com.android.launcher3.model.LoaderResults; |
| import com.android.launcher3.model.LoaderTask; |
| import com.android.launcher3.model.ModelDelegate; |
| import com.android.launcher3.model.WidgetItem; |
| import com.android.launcher3.model.WidgetsModel; |
| import com.android.launcher3.model.data.FolderInfo; |
| 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.pm.InstallSessionHelper; |
| import com.android.launcher3.pm.UserCache; |
| import com.android.launcher3.uioverrides.PredictedAppIconInflater; |
| import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; |
| import com.android.launcher3.util.ComponentKey; |
| import com.android.launcher3.util.IntArray; |
| import com.android.launcher3.util.MainThreadInitializedObject; |
| import com.android.launcher3.views.ActivityContext; |
| import com.android.launcher3.views.BaseDragLayer; |
| import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; |
| import com.android.launcher3.widget.custom.CustomWidgetManager; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ConcurrentLinkedQueue; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.FutureTask; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| |
| /** |
| * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile. |
| * Steps: |
| * 1) Create a dummy icon info with just white icon |
| * 2) Inflate a strip down layout definition for Launcher |
| * 3) Place appropriate elements like icons and first-page qsb |
| * 4) Measure and draw the view on a canvas |
| */ |
| @TargetApi(Build.VERSION_CODES.O) |
| public class LauncherPreviewRenderer extends ContextThemeWrapper |
| implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 { |
| |
| private static final String TAG = "LauncherPreviewRenderer"; |
| |
| /** |
| * Context used just for preview. It also provides a few objects (e.g. UserCache) just for |
| * preview purposes. |
| */ |
| public static class PreviewContext extends ContextWrapper { |
| |
| private final Set<MainThreadInitializedObject> mAllowedObjects = new HashSet<>( |
| Arrays.asList(UserCache.INSTANCE, InstallSessionHelper.INSTANCE, |
| LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE, |
| CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE)); |
| |
| private final InvariantDeviceProfile mIdp; |
| private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>(); |
| private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool = |
| new ConcurrentLinkedQueue<>(); |
| |
| public PreviewContext(Context base, InvariantDeviceProfile idp) { |
| super(base); |
| mIdp = idp; |
| } |
| |
| @Override |
| public Context getApplicationContext() { |
| return this; |
| } |
| |
| public void onDestroy() { |
| CustomWidgetManager customWidgetManager = (CustomWidgetManager) mObjectMap.get( |
| CustomWidgetManager.INSTANCE); |
| if (customWidgetManager != null) { |
| customWidgetManager.onDestroy(); |
| } |
| } |
| |
| /** |
| * Find a cached object from mObjectMap if we have already created one. If not, generate |
| * an object using the provider. |
| */ |
| public <T> T getObject(MainThreadInitializedObject<T> mainThreadInitializedObject, |
| MainThreadInitializedObject.ObjectProvider<T> provider) { |
| if (!mAllowedObjects.contains(mainThreadInitializedObject)) { |
| throw new IllegalStateException("Leaking unknown objects"); |
| } |
| if (mainThreadInitializedObject == LauncherAppState.INSTANCE) { |
| throw new IllegalStateException( |
| "Should not use MainThreadInitializedObject to initialize this with " |
| + "PreviewContext"); |
| } |
| if (mainThreadInitializedObject == InvariantDeviceProfile.INSTANCE) { |
| return (T) mIdp; |
| } |
| if (mObjectMap.containsKey(mainThreadInitializedObject)) { |
| return (T) mObjectMap.get(mainThreadInitializedObject); |
| } |
| T t = provider.get(this); |
| mObjectMap.put(mainThreadInitializedObject, t); |
| return t; |
| } |
| |
| public LauncherIcons newLauncherIcons(Context context, boolean shapeDetection) { |
| LauncherIconsForPreview launcherIconsForPreview = mIconPool.poll(); |
| if (launcherIconsForPreview != null) { |
| return launcherIconsForPreview; |
| } |
| return new LauncherIconsForPreview(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize, |
| -1 /* poolId */, shapeDetection); |
| } |
| |
| private final class LauncherIconsForPreview extends LauncherIcons { |
| |
| private LauncherIconsForPreview(Context context, int fillResIconDpi, int iconBitmapSize, |
| int poolId, boolean shapeDetection) { |
| super(context, fillResIconDpi, iconBitmapSize, poolId, shapeDetection); |
| } |
| |
| @Override |
| public void recycle() { |
| // Clear any temporary state variables |
| clear(); |
| mIconPool.offer(this); |
| } |
| } |
| } |
| |
| private final Handler mUiHandler; |
| private final Context mContext; |
| private final InvariantDeviceProfile mIdp; |
| private final DeviceProfile mDp; |
| private final boolean mMigrated; |
| private final Rect mInsets; |
| private final WorkspaceItemInfo mWorkspaceItemInfo; |
| private final LayoutInflater mHomeElementInflater; |
| private final InsettableFrameLayout mRootView; |
| private final Hotseat mHotseat; |
| private final CellLayout mWorkspace; |
| |
| public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp, boolean migrated) { |
| super(context, R.style.AppTheme); |
| mUiHandler = new Handler(Looper.getMainLooper()); |
| mContext = context; |
| mIdp = idp; |
| mDp = idp.portraitProfile.copy(context); |
| mMigrated = migrated; |
| |
| // TODO: get correct insets once display cutout API is available. |
| mInsets = new Rect(); |
| mInsets.left = mInsets.right = (mDp.widthPx - mDp.availableWidthPx) / 2; |
| mInsets.top = mInsets.bottom = (mDp.heightPx - mDp.availableHeightPx) / 2; |
| mDp.updateInsets(mInsets); |
| |
| BaseIconFactory iconFactory = |
| new BaseIconFactory(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize) { }; |
| BitmapInfo iconInfo = iconFactory.createBadgedIconBitmap(new AdaptiveIconDrawable( |
| new ColorDrawable(Color.WHITE), new ColorDrawable(Color.WHITE)), |
| Process.myUserHandle(), |
| Build.VERSION.SDK_INT); |
| |
| mWorkspaceItemInfo = new WorkspaceItemInfo(); |
| mWorkspaceItemInfo.bitmap = iconInfo; |
| mWorkspaceItemInfo.intent = new Intent(); |
| mWorkspaceItemInfo.contentDescription = mWorkspaceItemInfo.title = |
| context.getString(R.string.label_application); |
| |
| mHomeElementInflater = LayoutInflater.from( |
| new ContextThemeWrapper(this, R.style.HomeScreenElementTheme)); |
| mHomeElementInflater.setFactory2(this); |
| |
| mRootView = (InsettableFrameLayout) mHomeElementInflater.inflate( |
| R.layout.launcher_preview_layout, null, false); |
| mRootView.setInsets(mInsets); |
| measureView(mRootView, mDp.widthPx, mDp.heightPx); |
| |
| mHotseat = mRootView.findViewById(R.id.hotseat); |
| mHotseat.resetLayout(false); |
| |
| mWorkspace = mRootView.findViewById(R.id.workspace); |
| mWorkspace.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx, |
| mDp.workspacePadding.top, |
| mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx, |
| mDp.workspacePadding.bottom); |
| } |
| |
| /** Populate preview and render it. */ |
| public View getRenderedView() { |
| populate(); |
| return mRootView; |
| } |
| |
| public boolean shouldShowRealLauncherPreview() { |
| return ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get(); |
| } |
| |
| public boolean shouldShowQsb() { |
| return FeatureFlags.QSB_ON_FIRST_SCREEN; |
| } |
| |
| @Override |
| public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { |
| if ("TextClock".equals(name)) { |
| // Workaround for TextClock accessing handler for unregistering ticker. |
| return new TextClock(context, attrs) { |
| |
| @Override |
| public Handler getHandler() { |
| return mUiHandler; |
| } |
| }; |
| } else if (!"fragment".equals(name)) { |
| return null; |
| } |
| |
| TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PreviewFragment); |
| FragmentWithPreview f = (FragmentWithPreview) Fragment.instantiate( |
| context, ta.getString(R.styleable.PreviewFragment_android_name)); |
| f.enterPreviewMode(context); |
| f.onInit(null); |
| |
| View view = f.onCreateView(LayoutInflater.from(context), (ViewGroup) parent, null); |
| view.setId(ta.getInt(R.styleable.PreviewFragment_android_id, View.NO_ID)); |
| return view; |
| } |
| |
| @Override |
| public View onCreateView(String name, Context context, AttributeSet attrs) { |
| return onCreateView(null, name, context, attrs); |
| } |
| |
| @Override |
| public BaseDragLayer getDragLayer() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public DeviceProfile getDeviceProfile() { |
| return mDp; |
| } |
| |
| @Override |
| public Hotseat getHotseat() { |
| return mHotseat; |
| } |
| |
| @Override |
| public CellLayout getScreenWithId(int screenId) { |
| return mWorkspace; |
| } |
| |
| private void inflateAndAddIcon(WorkspaceItemInfo info) { |
| BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate( |
| R.layout.app_icon, mWorkspace, false); |
| icon.applyFromWorkspaceItem(info); |
| addInScreenFromBind(icon, info); |
| } |
| |
| private void inflateAndAddFolder(FolderInfo info) { |
| FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, mWorkspace, |
| info); |
| addInScreenFromBind(folderIcon, info); |
| } |
| |
| private void inflateAndAddWidgets( |
| LauncherAppWidgetInfo info, |
| Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) { |
| if (widgetProviderInfoMap == null) { |
| return; |
| } |
| AppWidgetProviderInfo providerInfo = widgetProviderInfoMap.get( |
| new ComponentKey(info.providerName, info.user)); |
| if (providerInfo == null) { |
| return; |
| } |
| inflateAndAddWidgets(info, LauncherAppWidgetProviderInfo.fromProviderInfo( |
| getApplicationContext(), providerInfo)); |
| } |
| |
| private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) { |
| WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName( |
| info.providerName); |
| if (widgetItem == null) { |
| return; |
| } |
| inflateAndAddWidgets(info, widgetItem.widgetInfo); |
| } |
| |
| private void inflateAndAddWidgets( |
| LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo providerInfo) { |
| AppWidgetHostView view = new AppWidgetHostView(mContext); |
| view.setAppWidget(-1, providerInfo); |
| view.updateAppWidget(null); |
| view.setTag(info); |
| addInScreenFromBind(view, info); |
| } |
| |
| private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) { |
| View view = PredictedAppIconInflater.inflate(mHomeElementInflater, mWorkspace, info); |
| if (view != null) { |
| addInScreenFromBind(view, info); |
| } |
| } |
| |
| private void dispatchVisibilityAggregated(View view, boolean isVisible) { |
| // Similar to View.dispatchVisibilityAggregated implementation. |
| final boolean thisVisible = view.getVisibility() == VISIBLE; |
| if (thisVisible || !isVisible) { |
| view.onVisibilityAggregated(isVisible); |
| } |
| |
| if (view instanceof ViewGroup) { |
| isVisible = thisVisible && isVisible; |
| ViewGroup vg = (ViewGroup) view; |
| int count = vg.getChildCount(); |
| |
| for (int i = 0; i < count; i++) { |
| dispatchVisibilityAggregated(vg.getChildAt(i), isVisible); |
| } |
| } |
| } |
| |
| private void populate() { |
| if (shouldShowRealLauncherPreview()) { |
| WorkspaceFetcher fetcher; |
| PreviewContext previewContext = null; |
| if (mMigrated) { |
| previewContext = new PreviewContext(mContext, mIdp); |
| LauncherAppState appForPreview = new LauncherAppState( |
| previewContext, null /* iconCacheFileName */); |
| fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview); |
| MODEL_EXECUTOR.execute(fetcher); |
| } else { |
| fetcher = new WorkspaceItemsInfoFetcher(); |
| LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask( |
| (LauncherModel.ModelUpdateTask) fetcher); |
| } |
| WorkspaceResult workspaceResult = fetcher.get(); |
| if (previewContext != null) { |
| previewContext.onDestroy(); |
| } |
| |
| if (workspaceResult == null) { |
| return; |
| } |
| |
| // Separate the items that are on the current screen, and the other remaining items. |
| ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>(); |
| ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>(); |
| ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>(); |
| ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>(); |
| filterCurrentWorkspaceItems(0 /* currentScreenId */, |
| workspaceResult.mWorkspaceItems, currentWorkspaceItems, |
| otherWorkspaceItems); |
| filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceResult.mAppWidgets, |
| currentAppWidgets, otherAppWidgets); |
| sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems); |
| for (ItemInfo itemInfo : currentWorkspaceItems) { |
| switch (itemInfo.itemType) { |
| case Favorites.ITEM_TYPE_APPLICATION: |
| case Favorites.ITEM_TYPE_SHORTCUT: |
| case Favorites.ITEM_TYPE_DEEP_SHORTCUT: |
| inflateAndAddIcon((WorkspaceItemInfo) itemInfo); |
| break; |
| case Favorites.ITEM_TYPE_FOLDER: |
| inflateAndAddFolder((FolderInfo) itemInfo); |
| break; |
| default: |
| break; |
| } |
| } |
| for (ItemInfo itemInfo : currentAppWidgets) { |
| switch (itemInfo.itemType) { |
| case Favorites.ITEM_TYPE_APPWIDGET: |
| case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: |
| if (mMigrated) { |
| inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, |
| workspaceResult.mWidgetProvidersMap); |
| } else { |
| inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, |
| workspaceResult.mWidgetsModel); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems, |
| mIdp.numHotseatIcons); |
| List<ItemInfo> predictions = workspaceResult.mHotseatPredictions == null |
| ? Collections.emptyList() : workspaceResult.mHotseatPredictions.items; |
| int count = Math.min(ranks.size(), predictions.size()); |
| for (int i = 0; i < count; i++) { |
| int rank = ranks.get(i); |
| WorkspaceItemInfo itemInfo = |
| new WorkspaceItemInfo((WorkspaceItemInfo) predictions.get(i)); |
| itemInfo.container = CONTAINER_HOTSEAT_PREDICTION; |
| itemInfo.rank = rank; |
| itemInfo.cellX = mHotseat.getCellXFromOrder(rank); |
| itemInfo.cellY = mHotseat.getCellYFromOrder(rank); |
| itemInfo.screenId = rank; |
| inflateAndAddPredictedIcon(itemInfo); |
| } |
| } else { |
| // Add hotseat icons |
| for (int i = 0; i < mIdp.numHotseatIcons; i++) { |
| WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo); |
| info.container = Favorites.CONTAINER_HOTSEAT; |
| info.screenId = i; |
| inflateAndAddIcon(info); |
| } |
| // Add workspace icons |
| for (int i = 0; i < mIdp.numColumns; i++) { |
| WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo); |
| info.container = Favorites.CONTAINER_DESKTOP; |
| info.screenId = 0; |
| info.cellX = i; |
| info.cellY = mIdp.numRows - 1; |
| inflateAndAddIcon(info); |
| } |
| } |
| |
| // Add first page QSB |
| if (shouldShowQsb()) { |
| View qsb = mHomeElementInflater.inflate( |
| R.layout.search_container_workspace, mWorkspace, false); |
| CellLayout.LayoutParams lp = |
| new CellLayout.LayoutParams(0, 0, mWorkspace.getCountX(), 1); |
| lp.canReorder = false; |
| mWorkspace.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true); |
| } |
| |
| measureView(mRootView, mDp.widthPx, mDp.heightPx); |
| dispatchVisibilityAggregated(mRootView, true); |
| measureView(mRootView, mDp.widthPx, mDp.heightPx); |
| // Additional measure for views which use auto text size API |
| measureView(mRootView, mDp.widthPx, mDp.heightPx); |
| } |
| |
| private static void measureView(View view, int width, int height) { |
| view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY)); |
| view.layout(0, 0, width, height); |
| } |
| |
| private static class WorkspaceItemsInfoFetcher implements LauncherModel.ModelUpdateTask, |
| WorkspaceFetcher { |
| |
| private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this); |
| |
| private LauncherAppState mApp; |
| private LauncherModel mModel; |
| private BgDataModel mBgDataModel; |
| private AllAppsList mAllAppsList; |
| |
| @Override |
| public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel, |
| AllAppsList allAppsList, Executor uiExecutor) { |
| mApp = app; |
| mModel = model; |
| mBgDataModel = dataModel; |
| mAllAppsList = allAppsList; |
| } |
| |
| @Override |
| public FutureTask<WorkspaceResult> getTask() { |
| return mTask; |
| } |
| |
| @Override |
| public void run() { |
| mTask.run(); |
| } |
| |
| @Override |
| public WorkspaceResult call() throws Exception { |
| if (!mModel.isModelLoaded()) { |
| Log.d(TAG, "Workspace not loaded, loading now"); |
| mModel.startLoaderForResults( |
| new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0])); |
| return null; |
| } |
| |
| return new WorkspaceResult(mBgDataModel, mBgDataModel.widgetsModel, null); |
| } |
| } |
| |
| private static class WorkspaceItemsInfoFromPreviewFetcher extends LoaderTask implements |
| WorkspaceFetcher { |
| |
| private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this); |
| |
| WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) { |
| super(app, null, new BgDataModel(), new ModelDelegate(), null); |
| } |
| |
| @Override |
| public FutureTask<WorkspaceResult> getTask() { |
| return mTask; |
| } |
| |
| @Override |
| public void run() { |
| mTask.run(); |
| } |
| |
| @Override |
| public WorkspaceResult call() { |
| List<ShortcutInfo> allShortcuts = new ArrayList<>(); |
| loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI, |
| LauncherSettings.Favorites.SCREEN + " = 0 or " |
| + LauncherSettings.Favorites.CONTAINER + " = " |
| + LauncherSettings.Favorites.CONTAINER_HOTSEAT); |
| return new WorkspaceResult(mBgDataModel, null, mWidgetProvidersMap); |
| } |
| } |
| |
| private interface WorkspaceFetcher extends Runnable, Callable<WorkspaceResult> { |
| FutureTask<WorkspaceResult> getTask(); |
| |
| default WorkspaceResult get() { |
| try { |
| return getTask().get(5, TimeUnit.SECONDS); |
| } catch (InterruptedException | ExecutionException | TimeoutException e) { |
| Log.d(TAG, "Error fetching workspace items info", e); |
| return null; |
| } |
| } |
| } |
| |
| private static class WorkspaceResult { |
| private final ArrayList<ItemInfo> mWorkspaceItems; |
| private final ArrayList<LauncherAppWidgetInfo> mAppWidgets; |
| private final FixedContainerItems mHotseatPredictions; |
| private final WidgetsModel mWidgetsModel; |
| private final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap; |
| |
| private WorkspaceResult(BgDataModel dataModel, |
| WidgetsModel widgetsModel, |
| Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) { |
| synchronized (dataModel) { |
| mWorkspaceItems = dataModel.workspaceItems; |
| mAppWidgets = dataModel.appWidgets; |
| mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION); |
| mWidgetsModel = widgetsModel; |
| mWidgetProvidersMap = widgetProviderInfoMap; |
| } |
| } |
| } |
| } |