• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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