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