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