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