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