Adding support for launcher preview generation

Creating a utility class which generates a launcher preview
for a provided InvariantDeviceProfile

Bug: 118758696
Change-Id: I0aebeb6eed37f72edd1cc305e58eece305aae3ff
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index bffdd72..a130604 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -204,4 +204,13 @@
     public boolean isDisabled() {
         return false;
     }
+
+    public int getViewId() {
+        // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
+        // This cast is safe as long as the id < 0x00FFFFFF
+        // Since we jail all the dynamically generated views, there should be no clashes
+        // with any other views.
+        return id;
+    }
+
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b6fa071..7da3b5d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -460,14 +460,6 @@
         return !isWorkspaceLoading();
     }
 
-    public int getViewIdForItem(ItemInfo info) {
-        // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
-        // This cast is safe as long as the id < 0x00FFFFFF
-        // Since we jail all the dynamically generated views, there should be no clashes
-        // with any other views.
-        return (int) info.id;
-    }
-
     public PopupDataProvider getPopupDataProvider() {
         return mPopupDataProvider;
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index c8e660b..c94e44f 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -79,7 +79,6 @@
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.touch.WorkspaceTouchListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -107,8 +106,8 @@
  */
 public class Workspace extends PagedView<WorkspacePageIndicator>
         implements DropTarget, DragSource, View.OnTouchListener,
-        DragController.DragListener, Insettable, LauncherStateManager.StateHandler {
-    private static final String TAG = "Launcher.Workspace";
+        DragController.DragListener, Insettable, LauncherStateManager.StateHandler,
+        WorkspaceLayoutManager {
 
     /** The value that {@link #mTransitionProgress} must be greater than for
      * {@link #transitionStateShouldAllowDrop()} to return true. */
@@ -130,11 +129,6 @@
     public static final boolean MAP_NO_RECURSE = false;
     public static final boolean MAP_RECURSE = true;
 
-    // The screen id used for the empty screen always present to the right.
-    public static final int EXTRA_EMPTY_SCREEN_ID = -201;
-    // The is the first screen. It is always present, even if its empty.
-    public static final int FIRST_SCREEN_ID = 0;
-
     private LayoutTransition mLayoutTransition;
     @Thunk final WallpaperManager mWallpaperManager;
 
@@ -740,6 +734,17 @@
         return newId;
     }
 
+    @Override
+    public Hotseat getHotseat() {
+        return mLauncher.getHotseat();
+    }
+
+    @Override
+    public void onAddDropTarget(DropTarget target) {
+        mDragController.addDropTarget(target);
+    }
+
+    @Override
     public CellLayout getScreenWithId(int screenId) {
         return mWorkspaceScreens.get(screenId);
     }
@@ -836,107 +841,6 @@
     }
 
     /**
-     * At bind time, we use the rank (screenId) to compute x and y for hotseat items.
-     * See {@link #addInScreen}.
-     */
-    public void addInScreenFromBind(View child, ItemInfo info) {
-        int x = info.cellX;
-        int y = info.cellY;
-        if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-            int screenId = (int) info.screenId;
-            x = mLauncher.getHotseat().getCellXFromOrder(screenId);
-            y = mLauncher.getHotseat().getCellYFromOrder(screenId);
-        }
-        addInScreen(child, info.container, info.screenId, x, y, info.spanX, info.spanY);
-    }
-
-    /**
-     * Adds the specified child in the specified screen based on the {@param info}
-     * See {@link #addInScreen(View, int, int, int, int, int, int)}.
-     */
-    public void addInScreen(View child, ItemInfo info) {
-        addInScreen(child, info.container, info.screenId, info.cellX, info.cellY,
-                info.spanX, info.spanY);
-    }
-
-    /**
-     * Adds the specified child in the specified screen. The position and dimension of
-     * the child are defined by x, y, spanX and spanY.
-     *
-     * @param child The child to add in one of the workspace's screens.
-     * @param screenId The screen in which to add the child.
-     * @param x The X position of the child in the screen's grid.
-     * @param y The Y position of the child in the screen's grid.
-     * @param spanX The number of cells spanned horizontally by the child.
-     * @param spanY The number of cells spanned vertically by the child.
-     */
-    private void addInScreen(View child, int container, int screenId, int x, int y,
-            int spanX, int spanY) {
-        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-            if (getScreenWithId(screenId) == null) {
-                Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
-                // DEBUGGING - Print out the stack trace to see where we are adding from
-                new Throwable().printStackTrace();
-                return;
-            }
-        }
-        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
-            // This should never happen
-            throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
-        }
-
-        final CellLayout layout;
-        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-            layout = mLauncher.getHotseat();
-
-            // Hide folder title in the hotseat
-            if (child instanceof FolderIcon) {
-                ((FolderIcon) child).setTextVisible(false);
-            }
-        } else {
-            // Show folder title if not in the hotseat
-            if (child instanceof FolderIcon) {
-                ((FolderIcon) child).setTextVisible(true);
-            }
-            layout = getScreenWithId(screenId);
-        }
-
-        ViewGroup.LayoutParams genericLp = child.getLayoutParams();
-        CellLayout.LayoutParams lp;
-        if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
-            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
-        } else {
-            lp = (CellLayout.LayoutParams) genericLp;
-            lp.cellX = x;
-            lp.cellY = y;
-            lp.cellHSpan = spanX;
-            lp.cellVSpan = spanY;
-        }
-
-        if (spanX < 0 && spanY < 0) {
-            lp.isLockedToGrid = false;
-        }
-
-        // Get the canonical child id to uniquely represent this view in this screen
-        ItemInfo info = (ItemInfo) child.getTag();
-        int childId = mLauncher.getViewIdForItem(info);
-
-        boolean markCellsAsOccupied = !(child instanceof Folder);
-        if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
-            // TODO: This branch occurs when the workspace is adding views
-            // outside of the defined grid
-            // maybe we should be deleting these items from the LauncherModel?
-            Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
-        }
-
-        child.setHapticFeedbackEnabled(false);
-        child.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
-        if (child instanceof DropTarget) {
-            mDragController.addDropTarget((DropTarget) child);
-        }
-    }
-
-    /**
      * Called directly from a CellLayout (not by the framework), after we've been added as a
      * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
      * that it should intercept touch events, which is not something that is normally supported.
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
new file mode 100644
index 0000000..ea2d4d0
--- /dev/null
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3;
+
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.touch.ItemLongClickListener;
+
+public interface WorkspaceLayoutManager {
+
+    String TAG = "Launcher.Workspace";
+
+    // The screen id used for the empty screen always present to the right.
+    int EXTRA_EMPTY_SCREEN_ID = -201;
+    // The is the first screen. It is always present, even if its empty.
+    int FIRST_SCREEN_ID = 0;
+
+    /**
+     * At bind time, we use the rank (screenId) to compute x and y for hotseat items.
+     * See {@link #addInScreen}.
+     */
+    default void addInScreenFromBind(View child, ItemInfo info) {
+        int x = info.cellX;
+        int y = info.cellY;
+        if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+            int screenId = info.screenId;
+            x = getHotseat().getCellXFromOrder(screenId);
+            y = getHotseat().getCellYFromOrder(screenId);
+        }
+        addInScreen(child, info.container, info.screenId, x, y, info.spanX, info.spanY);
+    }
+
+    /**
+     * Adds the specified child in the specified screen based on the {@param info}
+     * See {@link #addInScreen(View, int, int, int, int, int, int)}.
+     */
+    default void addInScreen(View child, ItemInfo info) {
+        addInScreen(child, info.container, info.screenId, info.cellX, info.cellY,
+                info.spanX, info.spanY);
+    }
+
+    /**
+     * Adds the specified child in the specified screen. The position and dimension of
+     * the child are defined by x, y, spanX and spanY.
+     *
+     * @param child The child to add in one of the workspace's screens.
+     * @param screenId The screen in which to add the child.
+     * @param x The X position of the child in the screen's grid.
+     * @param y The Y position of the child in the screen's grid.
+     * @param spanX The number of cells spanned horizontally by the child.
+     * @param spanY The number of cells spanned vertically by the child.
+     */
+    default void addInScreen(View child, int container, int screenId, int x, int y,
+            int spanX, int spanY) {
+        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+            if (getScreenWithId(screenId) == null) {
+                Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
+                // DEBUGGING - Print out the stack trace to see where we are adding from
+                new Throwable().printStackTrace();
+                return;
+            }
+        }
+        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
+            // This should never happen
+            throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
+        }
+
+        final CellLayout layout;
+        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+            layout = getHotseat();
+
+            // Hide folder title in the hotseat
+            if (child instanceof FolderIcon) {
+                ((FolderIcon) child).setTextVisible(false);
+            }
+        } else {
+            // Show folder title if not in the hotseat
+            if (child instanceof FolderIcon) {
+                ((FolderIcon) child).setTextVisible(true);
+            }
+            layout = getScreenWithId(screenId);
+        }
+
+        ViewGroup.LayoutParams genericLp = child.getLayoutParams();
+        CellLayout.LayoutParams lp;
+        if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
+            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
+        } else {
+            lp = (CellLayout.LayoutParams) genericLp;
+            lp.cellX = x;
+            lp.cellY = y;
+            lp.cellHSpan = spanX;
+            lp.cellVSpan = spanY;
+        }
+
+        if (spanX < 0 && spanY < 0) {
+            lp.isLockedToGrid = false;
+        }
+
+        // Get the canonical child id to uniquely represent this view in this screen
+        ItemInfo info = (ItemInfo) child.getTag();
+        int childId = info.getViewId();
+
+        boolean markCellsAsOccupied = !(child instanceof Folder);
+        if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
+            // TODO: This branch occurs when the workspace is adding views
+            // outside of the defined grid
+            // maybe we should be deleting these items from the LauncherModel?
+            Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
+        }
+
+        child.setHapticFeedbackEnabled(false);
+        child.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
+        if (child instanceof DropTarget) {
+            onAddDropTarget((DropTarget) child);
+        }
+    }
+
+    Hotseat getHotseat();
+
+    CellLayout getScreenWithId(int screenId);
+
+    default void onAddDropTarget(DropTarget target) { }
+}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 8439e79..06eaf38 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -225,8 +225,7 @@
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
         lp.cellX = item.cellX;
         lp.cellY = item.cellY;
-        getPageAt(pageNo).addViewToCellLayout(
-                view, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true);
+        getPageAt(pageNo).addViewToCellLayout(view, -1, item.getViewId(), lp, true);
     }
 
     @SuppressLint("InflateParams")
@@ -351,8 +350,7 @@
                 }
                 lp.cellX = info.cellX;
                 lp.cellY = info.cellY;
-                currentPage.addViewToCellLayout(
-                        v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
+                currentPage.addViewToCellLayout(v, -1, info.getViewId(), lp, true);
 
                 if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) {
                     ((BubbleTextView) v).verifyHighRes();
diff --git a/src/com/android/launcher3/graphics/FragmentWithPreview.java b/src/com/android/launcher3/graphics/FragmentWithPreview.java
new file mode 100644
index 0000000..250eca2
--- /dev/null
+++ b/src/com/android/launcher3/graphics/FragmentWithPreview.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.graphics;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+
+/**
+ * Extension of fragment, with support for preview mode.
+ */
+public class FragmentWithPreview extends Fragment {
+
+    private Context mPreviewContext;
+
+    public final void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        onInit(savedInstanceState);
+    }
+
+    public void onInit(Bundle savedInstanceState) { }
+
+
+    public Context getContext() {
+        return mPreviewContext != null ? mPreviewContext : getActivity();
+    }
+
+    void enterPreviewMode(Context context) {
+        mPreviewContext = context;
+    }
+
+    public boolean isInPreviewMode() {
+        return mPreviewContext != null;
+    }
+}
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
new file mode 100644
index 0000000..dc6f50f
--- /dev/null
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.graphics;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+import static android.view.View.VISIBLE;
+
+import android.annotation.TargetApi;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextClock;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceLayoutManager;
+import com.android.launcher3.allapps.SearchUiManager;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.BaseIconFactory;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
+ * Steps:
+ *   1) Create a dummy icon info with just white icon
+ *   2) Inflate a strip down layout definition for Launcher
+ *   3) Place appropriate elements like icons and first-page qsb
+ *   4) Measure and draw the view on a canvas
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class LauncherPreviewRenderer {
+
+    private static final String TAG = "LauncherPreviewRenderer";
+
+    private final Handler mUiHandler;
+    private final Context mContext;
+    private final InvariantDeviceProfile mIdp;
+    private final DeviceProfile mDp;
+    private final Rect mInsets;
+
+    private final ShortcutInfo mShortcutInfo;
+
+    public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp) {
+        mUiHandler = new Handler(Looper.getMainLooper());
+        mContext = context;
+        mIdp = idp;
+        mDp = idp.portraitProfile.copy(context);
+
+        // TODO: get correct insets once display cutout API is available.
+        mInsets = new Rect();
+        mInsets.left = mInsets.right = (mDp.widthPx - mDp.availableWidthPx) / 2;
+        mInsets.top = mInsets.bottom = (mDp.heightPx - mDp.availableHeightPx) / 2;
+        mDp.updateInsets(mInsets);
+
+        BaseIconFactory iconFactory =
+                new BaseIconFactory(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize) { };
+        BitmapInfo iconInfo = iconFactory.createBadgedIconBitmap(new AdaptiveIconDrawable(
+                        new ColorDrawable(Color.WHITE), new ColorDrawable(Color.WHITE)),
+                Process.myUserHandle(),
+                Build.VERSION.SDK_INT);
+
+        mShortcutInfo = new ShortcutInfo();
+        mShortcutInfo.applyFrom(iconInfo);
+        mShortcutInfo.intent = new Intent();
+        mShortcutInfo.contentDescription = mShortcutInfo.title =
+                context.getString(R.string.label_application);
+    }
+
+    public Bitmap createScreenShot() {
+        return BitmapRenderer.createHardwareBitmap(mDp.widthPx, mDp.heightPx, c -> {
+
+            if (Looper.myLooper() == Looper.getMainLooper()) {
+                new MainThreadRenderer(mContext).renderScreenShot(c);
+            } else {
+                CountDownLatch latch = new CountDownLatch(1);
+                Utilities.postAsyncCallback(mUiHandler, () -> {
+                    new MainThreadRenderer(mContext).renderScreenShot(c);
+                    latch.countDown();
+                });
+
+                try {
+                    latch.await();
+                } catch (Exception e) {
+                    Log.e(TAG, "Error drawing on main thread", e);
+                }
+            }
+        });
+    }
+
+    private class MainThreadRenderer extends ContextThemeWrapper
+            implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
+
+        private final LayoutInflater mHomeElementInflater;
+        private final InsettableFrameLayout mRootView;
+
+        private final Hotseat mHotseat;
+        private final CellLayout mWorkspace;
+
+        MainThreadRenderer(Context context) {
+            super(context, R.style.AppTheme);
+
+            mHomeElementInflater = LayoutInflater.from(
+                    new ContextThemeWrapper(this, R.style.HomeScreenElementTheme));
+            mHomeElementInflater.setFactory2(this);
+
+            mRootView = (InsettableFrameLayout) mHomeElementInflater.inflate(
+                    R.layout.launcher_preview_layout, null, false);
+            mRootView.setInsets(mInsets);
+            measureView(mRootView, mDp.widthPx, mDp.heightPx);
+
+            mHotseat = mRootView.findViewById(R.id.hotseat);
+            mHotseat.resetLayout(false);
+
+            mWorkspace = mRootView.findViewById(R.id.workspace);
+            mWorkspace.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
+                    mDp.workspacePadding.top,
+                    mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
+                    mDp.workspacePadding.bottom);
+        }
+
+        @Override
+        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+            if ("TextClock".equals(name)) {
+                // Workaround for TextClock accessing handler for unregistering ticker.
+                return new TextClock(context, attrs) {
+
+                    @Override
+                    public Handler getHandler() {
+                        return mUiHandler;
+                    }
+                };
+            } else if (!"fragment".equals(name)) {
+                return null;
+            }
+
+            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PreviewFragment);
+            FragmentWithPreview f = (FragmentWithPreview) Fragment.instantiate(
+                    context, ta.getString(R.styleable.PreviewFragment_android_name));
+            f.enterPreviewMode(context);
+            f.onInit(null);
+
+            View view = f.onCreateView(LayoutInflater.from(context), (ViewGroup) parent, null);
+            view.setId(ta.getInt(R.styleable.PreviewFragment_android_id, View.NO_ID));
+            return view;
+        }
+
+        @Override
+        public View onCreateView(String name, Context context, AttributeSet attrs) {
+            return onCreateView(null, name, context, attrs);
+        }
+
+        @Override
+        public BaseDragLayer getDragLayer() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public DeviceProfile getDeviceProfile() {
+            return mDp;
+        }
+
+        @Override
+        public Hotseat getHotseat() {
+            return mHotseat;
+        }
+
+        @Override
+        public CellLayout getScreenWithId(int screenId) {
+            return mWorkspace;
+        }
+
+        private void inflateAndAddIcon(ShortcutInfo info) {
+            BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate(
+                    R.layout.app_icon, mWorkspace, false);
+            icon.applyFromShortcutInfo(info);
+            addInScreenFromBind(icon, info);
+        }
+
+        private void dispatchVisibilityAggregated(View view, boolean isVisible) {
+            // Similar to View.dispatchVisibilityAggregated implementation.
+            final boolean thisVisible = view.getVisibility() == VISIBLE;
+            if (thisVisible || !isVisible) {
+                view.onVisibilityAggregated(isVisible);
+            }
+
+            if (view instanceof ViewGroup) {
+                isVisible = thisVisible && isVisible;
+                ViewGroup vg = (ViewGroup) view;
+                int count = vg.getChildCount();
+
+                for (int i = 0; i < count; i++) {
+                    dispatchVisibilityAggregated(vg.getChildAt(i), isVisible);
+                }
+            }
+        }
+
+        private void renderScreenShot(Canvas canvas) {
+            // Add hotseat icons
+            for (int i = 0; i < mIdp.numHotseatIcons; i++) {
+                ShortcutInfo info = new ShortcutInfo(mShortcutInfo);
+                info.container = Favorites.CONTAINER_HOTSEAT;
+                info.screenId = i;
+                inflateAndAddIcon(info);
+            }
+
+            // Add workspace icons
+            for (int i = 0; i < mIdp.numColumns; i++) {
+                ShortcutInfo info = new ShortcutInfo(mShortcutInfo);
+                info.container = Favorites.CONTAINER_DESKTOP;
+                info.screenId = 0;
+                info.cellX = i;
+                info.cellY = mIdp.numRows - 1;
+                inflateAndAddIcon(info);
+            }
+
+            // Add first page QSB
+            if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
+                View qsb = mHomeElementInflater.inflate(
+                        R.layout.search_container_workspace, mWorkspace, false);
+                CellLayout.LayoutParams lp =
+                        new CellLayout.LayoutParams(0, 0, mWorkspace.getCountX(), 1);
+                lp.canReorder = false;
+                mWorkspace.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
+            }
+
+            // Setup search view
+            SearchUiManager searchUiManager =
+                    mRootView.findViewById(R.id.search_container_all_apps);
+            mRootView.findViewById(R.id.apps_view).setTranslationY(
+                    mDp.heightPx - searchUiManager.getScrollRangeDelta(mInsets));
+
+            measureView(mRootView, mDp.widthPx, mDp.heightPx);
+            dispatchVisibilityAggregated(mRootView, true);
+            measureView(mRootView, mDp.widthPx, mDp.heightPx);
+            // Additional measure for views which use auto text size API
+            measureView(mRootView, mDp.widthPx, mDp.heightPx);
+
+            canvas.drawColor(Color.GRAY);
+            mRootView.draw(canvas);
+            dispatchVisibilityAggregated(mRootView, false);
+        }
+    }
+
+    private static void measureView(View view, int width, int height) {
+        view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
+        view.layout(0, 0, width, height);
+    }
+}
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index ac1fafb..57a458b 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.FragmentWithPreview;
 
 /**
  * A frame layout which contains a QSB. This internally uses fragment to bind the view, which
@@ -78,7 +79,7 @@
     /**
      * A fragment to display the QSB.
      */
-    public static class QsbFragment extends Fragment {
+    public static class QsbFragment extends FragmentWithPreview {
 
         public static final int QSB_WIDGET_HOST_ID = 1026;
         private static final int REQUEST_BIND_QSB = 1;
@@ -93,14 +94,13 @@
         private int mOrientation;
 
         @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
+        public void onInit(Bundle savedInstanceState) {
             mQsbWidgetHost = createHost();
             mOrientation = getContext().getResources().getConfiguration().orientation;
         }
 
         protected QsbWidgetHost createHost() {
-            return new QsbWidgetHost(getActivity(), QSB_WIDGET_HOST_ID,
+            return new QsbWidgetHost(getContext(), QSB_WIDGET_HOST_ID,
                     (c) -> new QsbWidgetHostView(c));
         }
 
@@ -110,7 +110,7 @@
         public View onCreateView(
                 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 
-            mWrapper = new FrameLayout(getActivity());
+            mWrapper = new FrameLayout(getContext());
 
             // Only add the view when enabled
             if (isQsbEnabled()) {
@@ -126,16 +126,16 @@
                 return getDefaultView(container, false /* show setup icon */);
             }
             Bundle opts = createBindOptions();
-            Activity activity = getActivity();
-            AppWidgetManager widgetManager = AppWidgetManager.getInstance(activity);
+            Context context = getContext();
+            AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
 
-            int widgetId = Utilities.getPrefs(activity).getInt(mKeyWidgetId, -1);
+            int widgetId = Utilities.getPrefs(context).getInt(mKeyWidgetId, -1);
             AppWidgetProviderInfo widgetInfo = widgetManager.getAppWidgetInfo(widgetId);
             boolean isWidgetBound = (widgetInfo != null) &&
                     widgetInfo.provider.equals(mWidgetInfo.provider);
 
             int oldWidgetId = widgetId;
-            if (!isWidgetBound) {
+            if (!isWidgetBound && !isInPreviewMode()) {
                 if (widgetId > -1) {
                     // widgetId is already bound and its not the correct provider. reset host.
                     mQsbWidgetHost.deleteHost();
@@ -155,14 +155,16 @@
             }
 
             if (isWidgetBound) {
-                mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(activity, widgetId, mWidgetInfo);
+                mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(context, widgetId, mWidgetInfo);
                 mQsb.setId(R.id.qsb_widget);
 
-                if (!Utilities.containsAll(AppWidgetManager.getInstance(activity)
-                        .getAppWidgetOptions(widgetId), opts)) {
-                    mQsb.updateAppWidgetOptions(opts);
+                if (!isInPreviewMode()) {
+                    if (!Utilities.containsAll(AppWidgetManager.getInstance(context)
+                            .getAppWidgetOptions(widgetId), opts)) {
+                        mQsb.updateAppWidgetOptions(opts);
+                    }
+                    mQsbWidgetHost.startListening();
                 }
-                mQsbWidgetHost.startListening();
                 return mQsb;
             }
 
@@ -171,7 +173,7 @@
         }
 
         private void saveWidgetId(int widgetId) {
-            Utilities.getPrefs(getActivity()).edit().putInt(mKeyWidgetId, widgetId).apply();
+            Utilities.getPrefs(getContext()).edit().putInt(mKeyWidgetId, widgetId).apply();
         }
 
         @Override
@@ -206,7 +208,7 @@
                 return;
             }
 
-            if (mWrapper != null && getActivity() != null) {
+            if (mWrapper != null && getContext() != null) {
                 mWrapper.removeAllViews();
                 mWrapper.addView(createQsb(mWrapper));
             }
@@ -217,10 +219,10 @@
         }
 
         protected Bundle createBindOptions() {
-            InvariantDeviceProfile idp = LauncherAppState.getIDP(getActivity());
+            InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
 
             Bundle opts = new Bundle();
-            Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(getActivity(),
+            Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(getContext(),
                     idp.numColumns, 1, null);
             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, size.left);
             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, size.top);
@@ -252,14 +254,14 @@
          */
         protected AppWidgetProviderInfo getSearchWidgetProvider() {
             SearchManager searchManager =
-                    (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
+                    (SearchManager) getContext().getSystemService(Context.SEARCH_SERVICE);
             ComponentName searchComponent = searchManager.getGlobalSearchActivity();
             if (searchComponent == null) return null;
             String providerPkg = searchComponent.getPackageName();
 
             AppWidgetProviderInfo defaultWidgetForSearchPackage = null;
 
-            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getActivity());
+            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getContext());
             for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
                 if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) {
                     if ((info.widgetCategory
diff --git a/src/com/android/launcher3/util/MyActivity.java b/src/com/android/launcher3/util/MyActivity.java
new file mode 100644
index 0000000..379a8da
--- /dev/null
+++ b/src/com/android/launcher3/util/MyActivity.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.launcher3.LauncherAppState;
+
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
+
+/**
+ * TODO: Remove this class
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class MyActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        new AsyncTask<Void, Void, Bitmap>() {
+            @Override
+            protected Bitmap doInBackground(Void... voids) {
+                LauncherPreviewRenderer renderer = new LauncherPreviewRenderer(MyActivity.this,
+                        LauncherAppState.getIDP(MyActivity.this));
+
+                Bitmap b = renderer.createScreenShot();
+                return b;
+            }
+
+            @Override
+            protected void onPostExecute(Bitmap bitmap) {
+                ImageView view = new ImageView(MyActivity.this);
+                view.setImageBitmap(bitmap);
+                setContentView(view);
+
+                view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+
+            }
+        }.execute();
+    }
+}