1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.graphics;
17 
18 import static android.view.View.MeasureSpec.EXACTLY;
19 import static android.view.View.MeasureSpec.makeMeasureSpec;
20 import static android.view.View.VISIBLE;
21 
22 import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER;
23 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
24 import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
25 import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
26 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
27 
28 import android.annotation.TargetApi;
29 import android.app.Fragment;
30 import android.appwidget.AppWidgetHostView;
31 import android.content.Context;
32 import android.content.ContextWrapper;
33 import android.content.Intent;
34 import android.content.pm.ShortcutInfo;
35 import android.content.res.TypedArray;
36 import android.graphics.Color;
37 import android.graphics.Rect;
38 import android.graphics.drawable.AdaptiveIconDrawable;
39 import android.graphics.drawable.ColorDrawable;
40 import android.os.Build;
41 import android.os.Handler;
42 import android.os.Looper;
43 import android.os.Process;
44 import android.util.AttributeSet;
45 import android.util.Log;
46 import android.view.ContextThemeWrapper;
47 import android.view.LayoutInflater;
48 import android.view.View;
49 import android.view.ViewGroup;
50 import android.widget.TextClock;
51 
52 import com.android.launcher3.BubbleTextView;
53 import com.android.launcher3.CellLayout;
54 import com.android.launcher3.DeviceProfile;
55 import com.android.launcher3.Hotseat;
56 import com.android.launcher3.InsettableFrameLayout;
57 import com.android.launcher3.InvariantDeviceProfile;
58 import com.android.launcher3.LauncherAppState;
59 import com.android.launcher3.LauncherModel;
60 import com.android.launcher3.LauncherSettings;
61 import com.android.launcher3.LauncherSettings.Favorites;
62 import com.android.launcher3.R;
63 import com.android.launcher3.WorkspaceLayoutManager;
64 import com.android.launcher3.allapps.SearchUiManager;
65 import com.android.launcher3.config.FeatureFlags;
66 import com.android.launcher3.folder.FolderIcon;
67 import com.android.launcher3.icons.BaseIconFactory;
68 import com.android.launcher3.icons.BitmapInfo;
69 import com.android.launcher3.icons.LauncherIcons;
70 import com.android.launcher3.model.AllAppsList;
71 import com.android.launcher3.model.BgDataModel;
72 import com.android.launcher3.model.BgDataModel.Callbacks;
73 import com.android.launcher3.model.LoaderResults;
74 import com.android.launcher3.model.LoaderTask;
75 import com.android.launcher3.model.WidgetItem;
76 import com.android.launcher3.model.WidgetsModel;
77 import com.android.launcher3.model.data.AppInfo;
78 import com.android.launcher3.model.data.FolderInfo;
79 import com.android.launcher3.model.data.ItemInfo;
80 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
81 import com.android.launcher3.model.data.WorkspaceItemInfo;
82 import com.android.launcher3.pm.InstallSessionHelper;
83 import com.android.launcher3.pm.UserCache;
84 import com.android.launcher3.uioverrides.PredictedAppIconInflater;
85 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
86 import com.android.launcher3.util.IntArray;
87 import com.android.launcher3.util.MainThreadInitializedObject;
88 import com.android.launcher3.views.ActivityContext;
89 import com.android.launcher3.views.BaseDragLayer;
90 import com.android.launcher3.widget.custom.CustomWidgetManager;
91 
92 import java.util.ArrayList;
93 import java.util.Arrays;
94 import java.util.HashMap;
95 import java.util.HashSet;
96 import java.util.List;
97 import java.util.Map;
98 import java.util.Set;
99 import java.util.concurrent.Callable;
100 import java.util.concurrent.ConcurrentLinkedQueue;
101 import java.util.concurrent.ExecutionException;
102 import java.util.concurrent.Executor;
103 import java.util.concurrent.FutureTask;
104 import java.util.concurrent.TimeUnit;
105 import java.util.concurrent.TimeoutException;
106 
107 /**
108  * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
109  * Steps:
110  *   1) Create a dummy icon info with just white icon
111  *   2) Inflate a strip down layout definition for Launcher
112  *   3) Place appropriate elements like icons and first-page qsb
113  *   4) Measure and draw the view on a canvas
114  */
115 @TargetApi(Build.VERSION_CODES.O)
116 public class LauncherPreviewRenderer {
117 
118     private static final String TAG = "LauncherPreviewRenderer";
119 
120     /**
121      * Context used just for preview. It also provides a few objects (e.g. UserCache) just for
122      * preview purposes.
123      */
124     public static class PreviewContext extends ContextWrapper {
125 
126         private static final Set<MainThreadInitializedObject> WHITELIST = new HashSet<>(
127                 Arrays.asList(UserCache.INSTANCE, InstallSessionHelper.INSTANCE,
128                         LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
129                         CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE));
130 
131         private final InvariantDeviceProfile mIdp;
132         private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
133         private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool =
134                 new ConcurrentLinkedQueue<>();
135 
PreviewContext(Context base, InvariantDeviceProfile idp)136         public PreviewContext(Context base, InvariantDeviceProfile idp) {
137             super(base);
138             mIdp = idp;
139         }
140 
141         @Override
getApplicationContext()142         public Context getApplicationContext() {
143             return this;
144         }
145 
onDestroy()146         public void onDestroy() {
147             CustomWidgetManager customWidgetManager = (CustomWidgetManager) mObjectMap.get(
148                     CustomWidgetManager.INSTANCE);
149             if (customWidgetManager != null) {
150                 customWidgetManager.onDestroy();
151             }
152         }
153 
154         /**
155          * Find a cached object from mObjectMap if we have already created one. If not, generate
156          * an object using the provider.
157          */
getObject(MainThreadInitializedObject<T> mainThreadInitializedObject, MainThreadInitializedObject.ObjectProvider<T> provider)158         public <T> T getObject(MainThreadInitializedObject<T> mainThreadInitializedObject,
159                 MainThreadInitializedObject.ObjectProvider<T> provider) {
160             if (!WHITELIST.contains(mainThreadInitializedObject)) {
161                 throw new IllegalStateException("Leaking unknown objects");
162             }
163             if (mainThreadInitializedObject == LauncherAppState.INSTANCE) {
164                 throw new IllegalStateException(
165                         "Should not use MainThreadInitializedObject to initialize this with "
166                                 + "PreviewContext");
167             }
168             if (mainThreadInitializedObject == InvariantDeviceProfile.INSTANCE) {
169                 return (T) mIdp;
170             }
171             if (mObjectMap.containsKey(mainThreadInitializedObject)) {
172                 return (T) mObjectMap.get(mainThreadInitializedObject);
173             }
174             T t = provider.get(this);
175             mObjectMap.put(mainThreadInitializedObject, t);
176             return t;
177         }
178 
newLauncherIcons(Context context, boolean shapeDetection)179         public LauncherIcons newLauncherIcons(Context context, boolean shapeDetection) {
180             LauncherIconsForPreview launcherIconsForPreview = mIconPool.poll();
181             if (launcherIconsForPreview != null) {
182                 return launcherIconsForPreview;
183             }
184             return new LauncherIconsForPreview(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize,
185                     -1 /* poolId */, shapeDetection);
186         }
187 
188         private final class LauncherIconsForPreview extends LauncherIcons {
189 
LauncherIconsForPreview(Context context, int fillResIconDpi, int iconBitmapSize, int poolId, boolean shapeDetection)190             private LauncherIconsForPreview(Context context, int fillResIconDpi, int iconBitmapSize,
191                     int poolId, boolean shapeDetection) {
192                 super(context, fillResIconDpi, iconBitmapSize, poolId, shapeDetection);
193             }
194 
195             @Override
recycle()196             public void recycle() {
197                 // Clear any temporary state variables
198                 clear();
199                 mIconPool.offer(this);
200             }
201         }
202     }
203 
204     private final Handler mUiHandler;
205     private final Context mContext;
206     private final InvariantDeviceProfile mIdp;
207     private final DeviceProfile mDp;
208     private final boolean mMigrated;
209     private final Rect mInsets;
210 
211     private final WorkspaceItemInfo mWorkspaceItemInfo;
212 
LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp, boolean migrated)213     public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp, boolean migrated) {
214         mUiHandler = new Handler(Looper.getMainLooper());
215         mContext = context;
216         mIdp = idp;
217         mDp = idp.portraitProfile.copy(context);
218         mMigrated = migrated;
219 
220         // TODO: get correct insets once display cutout API is available.
221         mInsets = new Rect();
222         mInsets.left = mInsets.right = (mDp.widthPx - mDp.availableWidthPx) / 2;
223         mInsets.top = mInsets.bottom = (mDp.heightPx - mDp.availableHeightPx) / 2;
224         mDp.updateInsets(mInsets);
225 
226         BaseIconFactory iconFactory =
227                 new BaseIconFactory(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize) { };
228         BitmapInfo iconInfo = iconFactory.createBadgedIconBitmap(new AdaptiveIconDrawable(
229                         new ColorDrawable(Color.WHITE), new ColorDrawable(Color.WHITE)),
230                 Process.myUserHandle(),
231                 Build.VERSION.SDK_INT);
232 
233         mWorkspaceItemInfo = new WorkspaceItemInfo();
234         mWorkspaceItemInfo.bitmap = iconInfo;
235         mWorkspaceItemInfo.intent = new Intent();
236         mWorkspaceItemInfo.contentDescription = mWorkspaceItemInfo.title =
237                 context.getString(R.string.label_application);
238     }
239 
240     /** Populate preview and render it. */
getRenderedView()241     public View getRenderedView() {
242         MainThreadRenderer renderer = new MainThreadRenderer(mContext);
243         renderer.populate();
244         return renderer.mRootView;
245     }
246 
247     private class MainThreadRenderer extends ContextThemeWrapper
248             implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
249 
250         private final LayoutInflater mHomeElementInflater;
251         private final InsettableFrameLayout mRootView;
252 
253         private final Hotseat mHotseat;
254         private final CellLayout mWorkspace;
255 
MainThreadRenderer(Context context)256         MainThreadRenderer(Context context) {
257             super(context, R.style.AppTheme);
258 
259             mHomeElementInflater = LayoutInflater.from(
260                     new ContextThemeWrapper(this, R.style.HomeScreenElementTheme));
261             mHomeElementInflater.setFactory2(this);
262 
263             mRootView = (InsettableFrameLayout) mHomeElementInflater.inflate(
264                     R.layout.launcher_preview_layout, null, false);
265             mRootView.setInsets(mInsets);
266             measureView(mRootView, mDp.widthPx, mDp.heightPx);
267 
268             mHotseat = mRootView.findViewById(R.id.hotseat);
269             mHotseat.resetLayout(false);
270 
271             mWorkspace = mRootView.findViewById(R.id.workspace);
272             mWorkspace.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
273                     mDp.workspacePadding.top,
274                     mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
275                     mDp.workspacePadding.bottom);
276         }
277 
278         @Override
onCreateView(View parent, String name, Context context, AttributeSet attrs)279         public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
280             if ("TextClock".equals(name)) {
281                 // Workaround for TextClock accessing handler for unregistering ticker.
282                 return new TextClock(context, attrs) {
283 
284                     @Override
285                     public Handler getHandler() {
286                         return mUiHandler;
287                     }
288                 };
289             } else if (!"fragment".equals(name)) {
290                 return null;
291             }
292 
293             TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PreviewFragment);
294             FragmentWithPreview f = (FragmentWithPreview) Fragment.instantiate(
295                     context, ta.getString(R.styleable.PreviewFragment_android_name));
296             f.enterPreviewMode(context);
297             f.onInit(null);
298 
299             View view = f.onCreateView(LayoutInflater.from(context), (ViewGroup) parent, null);
300             view.setId(ta.getInt(R.styleable.PreviewFragment_android_id, View.NO_ID));
301             return view;
302         }
303 
304         @Override
onCreateView(String name, Context context, AttributeSet attrs)305         public View onCreateView(String name, Context context, AttributeSet attrs) {
306             return onCreateView(null, name, context, attrs);
307         }
308 
309         @Override
getDragLayer()310         public BaseDragLayer getDragLayer() {
311             throw new UnsupportedOperationException();
312         }
313 
314         @Override
getDeviceProfile()315         public DeviceProfile getDeviceProfile() {
316             return mDp;
317         }
318 
319         @Override
getHotseat()320         public Hotseat getHotseat() {
321             return mHotseat;
322         }
323 
324         @Override
getScreenWithId(int screenId)325         public CellLayout getScreenWithId(int screenId) {
326             return mWorkspace;
327         }
328 
inflateAndAddIcon(WorkspaceItemInfo info)329         private void inflateAndAddIcon(WorkspaceItemInfo info) {
330             BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate(
331                     R.layout.app_icon, mWorkspace, false);
332             icon.applyFromWorkspaceItem(info);
333             addInScreenFromBind(icon, info);
334         }
335 
inflateAndAddFolder(FolderInfo info)336         private void inflateAndAddFolder(FolderInfo info) {
337             FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, mWorkspace,
338                     info);
339             addInScreenFromBind(folderIcon, info);
340         }
341 
inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel)342         private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
343             WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
344                     info.providerName);
345             if (widgetItem == null) {
346                 return;
347             }
348             AppWidgetHostView view = new AppWidgetHostView(mContext);
349             view.setAppWidget(-1, widgetItem.widgetInfo);
350             view.updateAppWidget(null);
351             view.setTag(info);
352             addInScreenFromBind(view, info);
353         }
354 
inflateAndAddPredictedIcon(WorkspaceItemInfo info)355         private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) {
356             View view = PredictedAppIconInflater.inflate(mHomeElementInflater, mWorkspace, info);
357             if (view != null) {
358                 addInScreenFromBind(view, info);
359             }
360         }
361 
dispatchVisibilityAggregated(View view, boolean isVisible)362         private void dispatchVisibilityAggregated(View view, boolean isVisible) {
363             // Similar to View.dispatchVisibilityAggregated implementation.
364             final boolean thisVisible = view.getVisibility() == VISIBLE;
365             if (thisVisible || !isVisible) {
366                 view.onVisibilityAggregated(isVisible);
367             }
368 
369             if (view instanceof ViewGroup) {
370                 isVisible = thisVisible && isVisible;
371                 ViewGroup vg = (ViewGroup) view;
372                 int count = vg.getChildCount();
373 
374                 for (int i = 0; i < count; i++) {
375                     dispatchVisibilityAggregated(vg.getChildAt(i), isVisible);
376                 }
377             }
378         }
379 
populate()380         private void populate() {
381             if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
382                 WorkspaceFetcher fetcher;
383                 PreviewContext previewContext = null;
384                 if (mMigrated) {
385                     previewContext = new PreviewContext(mContext, mIdp);
386                     LauncherAppState appForPreview = new LauncherAppState(
387                             previewContext, null /* iconCacheFileName */);
388                     fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview);
389                     MODEL_EXECUTOR.execute(fetcher);
390                 } else {
391                     fetcher = new WorkspaceItemsInfoFetcher();
392                     LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
393                             (LauncherModel.ModelUpdateTask) fetcher);
394                 }
395                 WorkspaceResult workspaceResult = fetcher.get();
396                 if (previewContext != null) {
397                     previewContext.onDestroy();
398                 }
399 
400                 if (workspaceResult == null) {
401                     return;
402                 }
403 
404                 // Separate the items that are on the current screen, and all the other remaining
405                 // items
406                 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
407                 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
408                 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
409                 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
410 
411                 filterCurrentWorkspaceItems(0 /* currentScreenId */,
412                         workspaceResult.mWorkspaceItems, currentWorkspaceItems,
413                         otherWorkspaceItems);
414                 filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceResult.mAppWidgets,
415                         currentAppWidgets, otherAppWidgets);
416                 sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
417 
418                 for (ItemInfo itemInfo : currentWorkspaceItems) {
419                     switch (itemInfo.itemType) {
420                         case Favorites.ITEM_TYPE_APPLICATION:
421                         case Favorites.ITEM_TYPE_SHORTCUT:
422                         case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
423                             inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
424                             break;
425                         case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
426                             inflateAndAddFolder((FolderInfo) itemInfo);
427                             break;
428                         default:
429                             break;
430                     }
431                 }
432                 for (ItemInfo itemInfo : currentAppWidgets) {
433                     switch (itemInfo.itemType) {
434                         case Favorites.ITEM_TYPE_APPWIDGET:
435                         case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
436                             inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
437                                     workspaceResult.mWidgetsModel);
438                             break;
439                         default:
440                             break;
441                     }
442                 }
443 
444                 IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems,
445                         mIdp.numHotseatIcons);
446                 int count = Math.min(ranks.size(), workspaceResult.mCachedPredictedItems.size());
447                 for (int i = 0; i < count; i++) {
448                     AppInfo appInfo = workspaceResult.mCachedPredictedItems.get(i);
449                     int rank = ranks.get(i);
450                     WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(appInfo);
451                     itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
452                     itemInfo.rank = rank;
453                     itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
454                     itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
455                     itemInfo.screenId = rank;
456                     inflateAndAddPredictedIcon(itemInfo);
457                 }
458             } else {
459                 // Add hotseat icons
460                 for (int i = 0; i < mIdp.numHotseatIcons; i++) {
461                     WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
462                     info.container = Favorites.CONTAINER_HOTSEAT;
463                     info.screenId = i;
464                     inflateAndAddIcon(info);
465                 }
466                 // Add workspace icons
467                 for (int i = 0; i < mIdp.numColumns; i++) {
468                     WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
469                     info.container = Favorites.CONTAINER_DESKTOP;
470                     info.screenId = 0;
471                     info.cellX = i;
472                     info.cellY = mIdp.numRows - 1;
473                     inflateAndAddIcon(info);
474                 }
475             }
476 
477             // Add first page QSB
478             if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
479                 View qsb = mHomeElementInflater.inflate(
480                         R.layout.search_container_workspace, mWorkspace, false);
481                 CellLayout.LayoutParams lp =
482                         new CellLayout.LayoutParams(0, 0, mWorkspace.getCountX(), 1);
483                 lp.canReorder = false;
484                 mWorkspace.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
485             }
486 
487             // Setup search view
488             SearchUiManager searchUiManager =
489                     mRootView.findViewById(R.id.search_container_all_apps);
490             mRootView.findViewById(R.id.apps_view).setTranslationY(
491                     mDp.heightPx - searchUiManager.getScrollRangeDelta(mInsets));
492 
493             measureView(mRootView, mDp.widthPx, mDp.heightPx);
494             dispatchVisibilityAggregated(mRootView, true);
495             measureView(mRootView, mDp.widthPx, mDp.heightPx);
496             // Additional measure for views which use auto text size API
497             measureView(mRootView, mDp.widthPx, mDp.heightPx);
498         }
499     }
500 
501     private static void measureView(View view, int width, int height) {
502         view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
503         view.layout(0, 0, width, height);
504     }
505 
506     private static class WorkspaceItemsInfoFetcher implements LauncherModel.ModelUpdateTask,
507             WorkspaceFetcher {
508 
509         private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
510 
511         private LauncherAppState mApp;
512         private LauncherModel mModel;
513         private BgDataModel mBgDataModel;
514         private AllAppsList mAllAppsList;
515 
516         @Override
517         public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel,
518                 AllAppsList allAppsList, Executor uiExecutor) {
519             mApp = app;
520             mModel = model;
521             mBgDataModel = dataModel;
522             mAllAppsList = allAppsList;
523         }
524 
525         @Override
526         public FutureTask<WorkspaceResult> getTask() {
527             return mTask;
528         }
529 
530         @Override
531         public void run() {
532             mTask.run();
533         }
534 
535         @Override
536         public WorkspaceResult call() throws Exception {
537             if (!mModel.isModelLoaded()) {
538                 Log.d(TAG, "Workspace not loaded, loading now");
539                 mModel.startLoaderForResults(
540                         new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0]));
541                 return null;
542             }
543 
544             return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
545                     mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel);
546         }
547     }
548 
549     private static class WorkspaceItemsInfoFromPreviewFetcher extends LoaderTask implements
550             WorkspaceFetcher {
551 
552         private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
553 
554         WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) {
555             super(app, null, new BgDataModel(), null);
556         }
557 
558         @Override
559         public FutureTask<WorkspaceResult> getTask() {
560             return mTask;
561         }
562 
563         @Override
564         public void run() {
565             mTask.run();
566         }
567 
568         @Override
569         public WorkspaceResult call() throws Exception {
570             List<ShortcutInfo> allShortcuts = new ArrayList<>();
571             loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI);
572             mBgDataModel.widgetsModel.update(mApp, null);
573             return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
574                     mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel);
575         }
576     }
577 
578     private interface WorkspaceFetcher extends Runnable, Callable<WorkspaceResult> {
579         FutureTask<WorkspaceResult> getTask();
580 
581         default WorkspaceResult get() {
582             try {
583                 return getTask().get(5, TimeUnit.SECONDS);
584             } catch (InterruptedException | ExecutionException | TimeoutException e) {
585                 Log.d(TAG, "Error fetching workspace items info", e);
586                 return null;
587             }
588         }
589     }
590 
591     private static class WorkspaceResult {
592         private final ArrayList<ItemInfo> mWorkspaceItems;
593         private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
594         private final ArrayList<AppInfo> mCachedPredictedItems;
595         private final WidgetsModel mWidgetsModel;
596 
597         private WorkspaceResult(ArrayList<ItemInfo> workspaceItems,
598                 ArrayList<LauncherAppWidgetInfo> appWidgets,
599                 ArrayList<AppInfo> cachedPredictedItems, WidgetsModel widgetsModel) {
600             mWorkspaceItems = workspaceItems;
601             mAppWidgets = appWidgets;
602             mCachedPredictedItems = cachedPredictedItems;
603             mWidgetsModel = widgetsModel;
604         }
605     }
606 }
607