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.app.WallpaperManager.FLAG_SYSTEM; 19 import static android.view.View.MeasureSpec.EXACTLY; 20 import static android.view.View.MeasureSpec.makeMeasureSpec; 21 import static android.view.View.VISIBLE; 22 23 import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE; 24 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; 25 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; 26 import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks; 27 28 import android.annotation.TargetApi; 29 import android.app.Fragment; 30 import android.app.WallpaperColors; 31 import android.app.WallpaperManager; 32 import android.appwidget.AppWidgetHost; 33 import android.appwidget.AppWidgetHostView; 34 import android.appwidget.AppWidgetProviderInfo; 35 import android.content.Context; 36 import android.content.ContextWrapper; 37 import android.content.Intent; 38 import android.content.res.TypedArray; 39 import android.graphics.Color; 40 import android.graphics.PointF; 41 import android.graphics.Rect; 42 import android.graphics.drawable.AdaptiveIconDrawable; 43 import android.graphics.drawable.ColorDrawable; 44 import android.os.Build; 45 import android.os.Handler; 46 import android.os.Looper; 47 import android.util.AttributeSet; 48 import android.util.Size; 49 import android.util.SparseArray; 50 import android.util.SparseIntArray; 51 import android.view.ContextThemeWrapper; 52 import android.view.LayoutInflater; 53 import android.view.MotionEvent; 54 import android.view.View; 55 import android.view.ViewGroup; 56 import android.view.WindowInsets; 57 import android.view.WindowManager; 58 import android.widget.TextClock; 59 60 import androidx.annotation.NonNull; 61 import androidx.annotation.Nullable; 62 63 import com.android.launcher3.BubbleTextView; 64 import com.android.launcher3.CellLayout; 65 import com.android.launcher3.DeviceProfile; 66 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; 67 import com.android.launcher3.Hotseat; 68 import com.android.launcher3.InsettableFrameLayout; 69 import com.android.launcher3.InvariantDeviceProfile; 70 import com.android.launcher3.LauncherAppState; 71 import com.android.launcher3.LauncherPrefs; 72 import com.android.launcher3.LauncherSettings.Favorites; 73 import com.android.launcher3.R; 74 import com.android.launcher3.Utilities; 75 import com.android.launcher3.Workspace; 76 import com.android.launcher3.WorkspaceLayoutManager; 77 import com.android.launcher3.celllayout.CellLayoutLayoutParams; 78 import com.android.launcher3.celllayout.CellPosMapper; 79 import com.android.launcher3.config.FeatureFlags; 80 import com.android.launcher3.folder.FolderIcon; 81 import com.android.launcher3.icons.BaseIconFactory; 82 import com.android.launcher3.icons.BitmapInfo; 83 import com.android.launcher3.icons.LauncherIcons; 84 import com.android.launcher3.model.BgDataModel; 85 import com.android.launcher3.model.BgDataModel.FixedContainerItems; 86 import com.android.launcher3.model.WidgetItem; 87 import com.android.launcher3.model.WidgetsModel; 88 import com.android.launcher3.model.data.FolderInfo; 89 import com.android.launcher3.model.data.ItemInfo; 90 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 91 import com.android.launcher3.model.data.WorkspaceItemInfo; 92 import com.android.launcher3.pm.InstallSessionHelper; 93 import com.android.launcher3.pm.UserCache; 94 import com.android.launcher3.uioverrides.PredictedAppIconInflater; 95 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; 96 import com.android.launcher3.util.ComponentKey; 97 import com.android.launcher3.util.DisplayController; 98 import com.android.launcher3.util.IntArray; 99 import com.android.launcher3.util.IntSet; 100 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext; 101 import com.android.launcher3.util.window.WindowManagerProxy; 102 import com.android.launcher3.views.ActivityContext; 103 import com.android.launcher3.views.BaseDragLayer; 104 import com.android.launcher3.widget.BaseLauncherAppWidgetHostView; 105 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; 106 import com.android.launcher3.widget.LauncherWidgetHolder; 107 import com.android.launcher3.widget.LocalColorExtractor; 108 import com.android.launcher3.widget.NavigableAppWidgetHostView; 109 import com.android.launcher3.widget.custom.CustomWidgetManager; 110 import com.android.launcher3.widget.util.WidgetSizes; 111 112 import java.util.ArrayList; 113 import java.util.Collections; 114 import java.util.HashMap; 115 import java.util.List; 116 import java.util.Map; 117 import java.util.concurrent.ConcurrentLinkedQueue; 118 119 /** 120 * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile. 121 * Steps: 122 * 1) Create a dummy icon info with just white icon 123 * 2) Inflate a strip down layout definition for Launcher 124 * 3) Place appropriate elements like icons and first-page qsb 125 * 4) Measure and draw the view on a canvas 126 */ 127 @TargetApi(Build.VERSION_CODES.R) 128 public class LauncherPreviewRenderer extends ContextWrapper 129 implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 { 130 131 /** 132 * Context used just for preview. It also provides a few objects (e.g. UserCache) just for 133 * preview purposes. 134 */ 135 public static class PreviewContext extends SandboxContext { 136 137 private final InvariantDeviceProfile mIdp; 138 private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool = 139 new ConcurrentLinkedQueue<>(); 140 PreviewContext(Context base, InvariantDeviceProfile idp)141 public PreviewContext(Context base, InvariantDeviceProfile idp) { 142 super(base, UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE, 143 LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE, 144 CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE, 145 WindowManagerProxy.INSTANCE, DisplayController.INSTANCE); 146 mIdp = idp; 147 mObjectMap.put(InvariantDeviceProfile.INSTANCE, idp); 148 mObjectMap.put(LauncherAppState.INSTANCE, 149 new LauncherAppState(this, null /* iconCacheFileName */)); 150 } 151 152 /** 153 * Creates a new LauncherIcons for the preview, skipping the global pool 154 */ newLauncherIcons(Context context)155 public LauncherIcons newLauncherIcons(Context context) { 156 LauncherIconsForPreview launcherIconsForPreview = mIconPool.poll(); 157 if (launcherIconsForPreview != null) { 158 return launcherIconsForPreview; 159 } 160 return new LauncherIconsForPreview(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize, 161 -1 /* poolId */); 162 } 163 164 private final class LauncherIconsForPreview extends LauncherIcons { 165 LauncherIconsForPreview(Context context, int fillResIconDpi, int iconBitmapSize, int poolId)166 private LauncherIconsForPreview(Context context, int fillResIconDpi, int iconBitmapSize, 167 int poolId) { 168 super(context, fillResIconDpi, iconBitmapSize, poolId); 169 } 170 171 @Override recycle()172 public void recycle() { 173 // Clear any temporary state variables 174 clear(); 175 mIconPool.offer(this); 176 } 177 } 178 } 179 180 private final List<OnDeviceProfileChangeListener> mDpChangeListeners = new ArrayList<>(); 181 private final Handler mUiHandler; 182 private final Context mContext; 183 private final InvariantDeviceProfile mIdp; 184 private final DeviceProfile mDp; 185 private final DeviceProfile mDpOrig; 186 private final Rect mInsets; 187 private final WorkspaceItemInfo mWorkspaceItemInfo; 188 private final LayoutInflater mHomeElementInflater; 189 private final InsettableFrameLayout mRootView; 190 private final Hotseat mHotseat; 191 private final Map<Integer, CellLayout> mWorkspaceScreens = new HashMap<>(); 192 private final AppWidgetHost mAppWidgetHost; 193 private final SparseIntArray mWallpaperColorResources; 194 private final SparseArray<Size> mLauncherWidgetSpanInfo; 195 LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp, WallpaperColors wallpaperColorsOverride, @Nullable final SparseArray<Size> launcherWidgetSpanInfo)196 public LauncherPreviewRenderer(Context context, 197 InvariantDeviceProfile idp, 198 WallpaperColors wallpaperColorsOverride, 199 @Nullable final SparseArray<Size> launcherWidgetSpanInfo) { 200 201 super(context); 202 mUiHandler = new Handler(Looper.getMainLooper()); 203 mContext = context; 204 mIdp = idp; 205 mDp = idp.getDeviceProfile(context).toBuilder(context).setViewScaleProvider( 206 this::getAppWidgetScale).build(); 207 if (context instanceof PreviewContext) { 208 Context tempContext = ((PreviewContext) context).getBaseContext(); 209 mDpOrig = new InvariantDeviceProfile(tempContext, InvariantDeviceProfile 210 .getCurrentGridName(tempContext)).getDeviceProfile(tempContext) 211 .copy(tempContext); 212 } else { 213 mDpOrig = mDp; 214 } 215 216 WindowInsets currentWindowInsets = context.getSystemService(WindowManager.class) 217 .getCurrentWindowMetrics().getWindowInsets(); 218 mInsets = new Rect( 219 currentWindowInsets.getSystemWindowInsetLeft(), 220 currentWindowInsets.getSystemWindowInsetTop(), 221 currentWindowInsets.getSystemWindowInsetRight(), 222 mDp.isTaskbarPresent ? 0 : currentWindowInsets.getSystemWindowInsetBottom()); 223 mDp.updateInsets(mInsets); 224 225 BaseIconFactory iconFactory = 226 new BaseIconFactory(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize) { }; 227 BitmapInfo iconInfo = iconFactory.createBadgedIconBitmap( 228 new AdaptiveIconDrawable( 229 new ColorDrawable(Color.WHITE), 230 new ColorDrawable(Color.WHITE))); 231 232 mWorkspaceItemInfo = new WorkspaceItemInfo(); 233 mWorkspaceItemInfo.bitmap = iconInfo; 234 mWorkspaceItemInfo.intent = new Intent(); 235 mWorkspaceItemInfo.contentDescription = mWorkspaceItemInfo.title = 236 context.getString(R.string.label_application); 237 238 mHomeElementInflater = LayoutInflater.from( 239 new ContextThemeWrapper(this, R.style.HomeScreenElementTheme)); 240 mHomeElementInflater.setFactory2(this); 241 242 int layoutRes = mDp.isTwoPanels ? R.layout.launcher_preview_two_panel_layout 243 : R.layout.launcher_preview_layout; 244 mRootView = (InsettableFrameLayout) mHomeElementInflater.inflate( 245 layoutRes, null, false); 246 mRootView.setInsets(mInsets); 247 measureView(mRootView, mDp.widthPx, mDp.heightPx); 248 249 mHotseat = mRootView.findViewById(R.id.hotseat); 250 mHotseat.resetLayout(false); 251 252 mLauncherWidgetSpanInfo = launcherWidgetSpanInfo == null ? new SparseArray<>() : 253 launcherWidgetSpanInfo; 254 255 CellLayout firstScreen = mRootView.findViewById(R.id.workspace); 256 firstScreen.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingPx.left, 257 mDp.workspacePadding.top + mDp.cellLayoutPaddingPx.top, 258 (mDp.isTwoPanels ? mDp.cellLayoutBorderSpacePx.x / 2 259 : mDp.workspacePadding.right) + mDp.cellLayoutPaddingPx.right, 260 mDp.workspacePadding.bottom + mDp.cellLayoutPaddingPx.bottom 261 ); 262 mWorkspaceScreens.put(FIRST_SCREEN_ID, firstScreen); 263 264 if (mDp.isTwoPanels) { 265 CellLayout rightPanel = mRootView.findViewById(R.id.workspace_right); 266 rightPanel.setPadding( 267 mDp.cellLayoutBorderSpacePx.x / 2 + mDp.cellLayoutPaddingPx.left, 268 mDp.workspacePadding.top + mDp.cellLayoutPaddingPx.top, 269 mDp.workspacePadding.right + mDp.cellLayoutPaddingPx.right, 270 mDp.workspacePadding.bottom + mDp.cellLayoutPaddingPx.bottom 271 ); 272 mWorkspaceScreens.put(Workspace.SECOND_SCREEN_ID, rightPanel); 273 } 274 275 if (Utilities.ATLEAST_S) { 276 WallpaperColors wallpaperColors = wallpaperColorsOverride != null 277 ? wallpaperColorsOverride 278 : WallpaperManager.getInstance(context).getWallpaperColors(FLAG_SYSTEM); 279 mWallpaperColorResources = wallpaperColors != null ? LocalColorExtractor.newInstance( 280 context).generateColorsOverride(wallpaperColors) : null; 281 } else { 282 mWallpaperColorResources = null; 283 } 284 mAppWidgetHost = FeatureFlags.WIDGETS_IN_LAUNCHER_PREVIEW.get() 285 ? new LauncherPreviewAppWidgetHost(context) 286 : null; 287 } 288 289 /** Populate preview and render it. */ getRenderedView(BgDataModel dataModel, Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap)290 public View getRenderedView(BgDataModel dataModel, 291 Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) { 292 populate(dataModel, widgetProviderInfoMap); 293 return mRootView; 294 } 295 296 @Override onCreateView(View parent, String name, Context context, AttributeSet attrs)297 public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { 298 if ("TextClock".equals(name)) { 299 // Workaround for TextClock accessing handler for unregistering ticker. 300 return new TextClock(context, attrs) { 301 302 @Override 303 public Handler getHandler() { 304 return mUiHandler; 305 } 306 }; 307 } else if (!"fragment".equals(name)) { 308 return null; 309 } 310 311 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PreviewFragment); 312 FragmentWithPreview f = (FragmentWithPreview) Fragment.instantiate( 313 context, ta.getString(R.styleable.PreviewFragment_android_name)); 314 f.enterPreviewMode(context); 315 f.onInit(null); 316 317 View view = f.onCreateView(LayoutInflater.from(context), (ViewGroup) parent, null); 318 view.setId(ta.getInt(R.styleable.PreviewFragment_android_id, View.NO_ID)); 319 return view; 320 } 321 322 @Override 323 public View onCreateView(String name, Context context, AttributeSet attrs) { 324 return onCreateView(null, name, context, attrs); 325 } 326 327 @Override 328 public BaseDragLayer getDragLayer() { 329 throw new UnsupportedOperationException(); 330 } 331 332 @Override 333 public DeviceProfile getDeviceProfile() { 334 return mDp; 335 } 336 337 @Override 338 public List<OnDeviceProfileChangeListener> getOnDeviceProfileChangeListeners() { 339 return mDpChangeListeners; 340 } 341 342 @Override 343 public Hotseat getHotseat() { 344 return mHotseat; 345 } 346 347 /** 348 * Hides the components in the bottom row. 349 * 350 * @param hide True to hide and false to show. 351 */ 352 public void hideBottomRow(boolean hide) { 353 mUiHandler.post(() -> { 354 if (mDp.isTaskbarPresent) { 355 // hotseat icons on bottom 356 mHotseat.setIconsAlpha(hide ? 0 : 1); 357 if (mDp.isQsbInline) { 358 mHotseat.setQsbAlpha(hide ? 0 : 1); 359 } 360 } else { 361 mHotseat.setQsbAlpha(hide ? 0 : 1); 362 } 363 }); 364 } 365 366 @Override 367 public CellLayout getScreenWithId(int screenId) { 368 return mWorkspaceScreens.get(screenId); 369 } 370 371 @Override 372 public CellPosMapper getCellPosMapper() { 373 return CellPosMapper.DEFAULT; 374 } 375 376 private void inflateAndAddIcon(WorkspaceItemInfo info) { 377 CellLayout screen = mWorkspaceScreens.get(info.screenId); 378 BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate( 379 R.layout.app_icon, screen, false); 380 icon.applyFromWorkspaceItem(info); 381 addInScreenFromBind(icon, info); 382 } 383 384 private void inflateAndAddFolder(FolderInfo info) { 385 CellLayout screen = info.container == Favorites.CONTAINER_DESKTOP 386 ? mWorkspaceScreens.get(info.screenId) 387 : mHotseat; 388 FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, screen, 389 info); 390 addInScreenFromBind(folderIcon, info); 391 } 392 393 private void inflateAndAddWidgets( 394 LauncherAppWidgetInfo info, 395 Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) { 396 if (widgetProviderInfoMap == null) { 397 return; 398 } 399 AppWidgetProviderInfo providerInfo = widgetProviderInfoMap.get( 400 new ComponentKey(info.providerName, info.user)); 401 if (providerInfo == null) { 402 return; 403 } 404 inflateAndAddWidgets(info, LauncherAppWidgetProviderInfo.fromProviderInfo( 405 getApplicationContext(), providerInfo)); 406 } 407 408 private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) { 409 WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName( 410 info.providerName, info.user); 411 if (widgetItem == null) { 412 return; 413 } 414 inflateAndAddWidgets(info, widgetItem.widgetInfo); 415 } 416 417 private void inflateAndAddWidgets( 418 LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo providerInfo) { 419 AppWidgetHostView view; 420 if (FeatureFlags.WIDGETS_IN_LAUNCHER_PREVIEW.get()) { 421 view = mAppWidgetHost.createView(mContext, info.appWidgetId, providerInfo); 422 } else { 423 view = new NavigableAppWidgetHostView(this) { 424 @Override 425 protected boolean shouldAllowDirectClick() { 426 return false; 427 } 428 }; 429 view.setAppWidget(-1, providerInfo); 430 view.updateAppWidget(null); 431 } 432 433 if (mWallpaperColorResources != null) { 434 view.setColorResources(mWallpaperColorResources); 435 } 436 437 view.setTag(info); 438 addInScreenFromBind(view, info); 439 } 440 441 @NonNull 442 private PointF getAppWidgetScale(@Nullable ItemInfo itemInfo) { 443 if (!(itemInfo instanceof LauncherAppWidgetInfo)) { 444 return DEFAULT_SCALE; 445 } 446 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) itemInfo; 447 final Size launcherWidgetSize = mLauncherWidgetSpanInfo.get(info.appWidgetId); 448 if (launcherWidgetSize == null) { 449 return DEFAULT_SCALE; 450 } 451 final Size origSize = WidgetSizes.getWidgetSizePx(mDpOrig, 452 launcherWidgetSize.getWidth(), launcherWidgetSize.getHeight()); 453 final Size newSize = WidgetSizes.getWidgetSizePx(mDp, info.spanX, info.spanY); 454 final Rect previewInset = new Rect(); 455 final Rect origInset = new Rect(); 456 // When the setup() is called for the LayoutParams, insets are added to the width 457 // and height of the view. This is not accounted for in WidgetSizes and is handled 458 // here. 459 if (mDp.shouldInsetWidgets()) { 460 previewInset.set(mDp.inv.defaultWidgetPadding); 461 } else { 462 previewInset.setEmpty(); 463 } 464 if (mDpOrig.shouldInsetWidgets()) { 465 origInset.set(mDpOrig.inv.defaultWidgetPadding); 466 } else { 467 origInset.setEmpty(); 468 } 469 470 return new PointF((float) newSize.getWidth() / (origSize.getWidth() 471 + origInset.left + origInset.right), 472 (float) newSize.getHeight() / (origSize.getHeight() 473 + origInset.top + origInset.bottom)); 474 } 475 476 private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) { 477 CellLayout screen = mWorkspaceScreens.get(info.screenId); 478 View view = PredictedAppIconInflater.inflate(mHomeElementInflater, screen, info); 479 if (view != null) { 480 addInScreenFromBind(view, info); 481 } 482 } 483 484 private void dispatchVisibilityAggregated(View view, boolean isVisible) { 485 // Similar to View.dispatchVisibilityAggregated implementation. 486 final boolean thisVisible = view.getVisibility() == VISIBLE; 487 if (thisVisible || !isVisible) { 488 view.onVisibilityAggregated(isVisible); 489 } 490 491 if (view instanceof ViewGroup) { 492 isVisible = thisVisible && isVisible; 493 ViewGroup vg = (ViewGroup) view; 494 int count = vg.getChildCount(); 495 496 for (int i = 0; i < count; i++) { 497 dispatchVisibilityAggregated(vg.getChildAt(i), isVisible); 498 } 499 } 500 } 501 502 private void populate(BgDataModel dataModel, 503 Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) { 504 // Separate the items that are on the current screen, and the other remaining items. 505 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>(); 506 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>(); 507 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>(); 508 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>(); 509 510 IntSet currentScreenIds = IntSet.wrap(mWorkspaceScreens.keySet()); 511 filterCurrentWorkspaceItems(currentScreenIds, dataModel.workspaceItems, 512 currentWorkspaceItems, otherWorkspaceItems); 513 filterCurrentWorkspaceItems(currentScreenIds, dataModel.appWidgets, currentAppWidgets, 514 otherAppWidgets); 515 for (ItemInfo itemInfo : currentWorkspaceItems) { 516 switch (itemInfo.itemType) { 517 case Favorites.ITEM_TYPE_APPLICATION: 518 case Favorites.ITEM_TYPE_SHORTCUT: 519 case Favorites.ITEM_TYPE_DEEP_SHORTCUT: 520 inflateAndAddIcon((WorkspaceItemInfo) itemInfo); 521 break; 522 case Favorites.ITEM_TYPE_FOLDER: 523 inflateAndAddFolder((FolderInfo) itemInfo); 524 break; 525 default: 526 break; 527 } 528 } 529 for (ItemInfo itemInfo : currentAppWidgets) { 530 switch (itemInfo.itemType) { 531 case Favorites.ITEM_TYPE_APPWIDGET: 532 case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 533 if (widgetProviderInfoMap != null) { 534 inflateAndAddWidgets( 535 (LauncherAppWidgetInfo) itemInfo, widgetProviderInfoMap); 536 } else { 537 inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, 538 dataModel.widgetsModel); 539 } 540 break; 541 default: 542 break; 543 } 544 } 545 IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems, 546 mDp.numShownHotseatIcons); 547 FixedContainerItems hotseatPredictions = 548 dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION); 549 List<ItemInfo> predictions = hotseatPredictions == null 550 ? Collections.emptyList() : hotseatPredictions.items; 551 int count = Math.min(ranks.size(), predictions.size()); 552 for (int i = 0; i < count; i++) { 553 int rank = ranks.get(i); 554 WorkspaceItemInfo itemInfo = 555 new WorkspaceItemInfo((WorkspaceItemInfo) predictions.get(i)); 556 itemInfo.container = CONTAINER_HOTSEAT_PREDICTION; 557 itemInfo.rank = rank; 558 itemInfo.cellX = mHotseat.getCellXFromOrder(rank); 559 itemInfo.cellY = mHotseat.getCellYFromOrder(rank); 560 itemInfo.screenId = rank; 561 inflateAndAddPredictedIcon(itemInfo); 562 } 563 564 // Add first page QSB 565 if (FeatureFlags.QSB_ON_FIRST_SCREEN) { 566 CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID); 567 View qsb = mHomeElementInflater.inflate(R.layout.qsb_preview, firstScreen, false); 568 CellLayoutLayoutParams lp = new CellLayoutLayoutParams( 569 0, 0, firstScreen.getCountX(), 1); 570 lp.canReorder = false; 571 firstScreen.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true); 572 } 573 574 measureView(mRootView, mDp.widthPx, mDp.heightPx); 575 dispatchVisibilityAggregated(mRootView, true); 576 measureView(mRootView, mDp.widthPx, mDp.heightPx); 577 // Additional measure for views which use auto text size API 578 measureView(mRootView, mDp.widthPx, mDp.heightPx); 579 } 580 581 private static void measureView(View view, int width, int height) { 582 view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY)); 583 view.layout(0, 0, width, height); 584 } 585 586 private class LauncherPreviewAppWidgetHost extends AppWidgetHost { 587 588 private LauncherPreviewAppWidgetHost(Context context) { 589 super(context, LauncherWidgetHolder.APPWIDGET_HOST_ID); 590 } 591 592 @Override 593 protected AppWidgetHostView onCreateView( 594 Context context, 595 int appWidgetId, 596 AppWidgetProviderInfo appWidget) { 597 return new LauncherPreviewAppWidgetHostView(LauncherPreviewRenderer.this); 598 } 599 } 600 601 private static class LauncherPreviewAppWidgetHostView extends BaseLauncherAppWidgetHostView { 602 private LauncherPreviewAppWidgetHostView(Context context) { 603 super(context); 604 } 605 606 @Override 607 protected boolean shouldAllowDirectClick() { 608 return false; 609 } 610 } 611 612 /** Root layout for launcher preview that intercepts all touch events. */ 613 public static class LauncherPreviewLayout extends InsettableFrameLayout { 614 public LauncherPreviewLayout(Context context, AttributeSet attrs) { 615 super(context, attrs); 616 } 617 618 @Override 619 public boolean onInterceptTouchEvent(MotionEvent ev) { 620 return true; 621 } 622 } 623 } 624