From ef92b8277896890c61cab7e945144c5689ce9151 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 21 Nov 2018 14:12:00 -0800 Subject: [PATCH] Adding support for launcher preview generation Creating a utility class which generates a launcher preview for a provided InvariantDeviceProfile Bug: 118758696 Change-Id: I0aebeb6eed37f72edd1cc305e58eece305aae3ff --- AndroidManifest-common.xml | 10 + res/layout/launcher_preview_layout.xml | 50 +++ res/values/attrs.xml | 5 + res/values/strings.xml | 2 + src/com/android/launcher3/ItemInfo.java | 9 + src/com/android/launcher3/Launcher.java | 8 - src/com/android/launcher3/Workspace.java | 122 +------- .../launcher3/WorkspaceLayoutManager.java | 141 +++++++++ .../launcher3/folder/FolderPagedView.java | 6 +- .../graphics/FragmentWithPreview.java | 48 +++ .../graphics/LauncherPreviewRenderer.java | 292 ++++++++++++++++++ .../launcher3/qsb/QsbContainerView.java | 42 +-- .../android/launcher3/util/MyActivity.java | 63 ++++ 13 files changed, 657 insertions(+), 141 deletions(-) create mode 100644 res/layout/launcher_preview_layout.xml create mode 100644 src/com/android/launcher3/WorkspaceLayoutManager.java create mode 100644 src/com/android/launcher3/graphics/FragmentWithPreview.java create mode 100644 src/com/android/launcher3/graphics/LauncherPreviewRenderer.java create mode 100644 src/com/android/launcher3/util/MyActivity.java diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml index 1beaea53ef..9b85784462 100644 --- a/AndroidManifest-common.xml +++ b/AndroidManifest-common.xml @@ -132,6 +132,16 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 956270cba3..b4b69782c2 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -148,4 +148,9 @@ + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 7e5784d0bf..5711f34836 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -66,6 +66,8 @@ No apps found matching \"%1$s\" Search for more apps + + App diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java index bffdd724b7..a130604779 100644 --- a/src/com/android/launcher3/ItemInfo.java +++ b/src/com/android/launcher3/ItemInfo.java @@ -204,4 +204,13 @@ public class ItemInfo { 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 b6fa0718bc..7da3b5d924 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -460,14 +460,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, 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 c8e660b921..c94e44f654 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -79,7 +79,6 @@ import com.android.launcher3.graphics.PreloadIconDrawable; 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 @@ import java.util.Set; */ public class Workspace extends PagedView 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 class Workspace extends PagedView 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 @@ public class Workspace extends PagedView 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); } @@ -835,107 +840,6 @@ public class Workspace extends PagedView } } - /** - * 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 diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java new file mode 100644 index 0000000000..ea2d4d0995 --- /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 8439e794f5..06eaf38bca 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -225,8 +225,7 @@ public class FolderPagedView extends PagedView { 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 @@ public class FolderPagedView extends PagedView { } 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 0000000000..250eca29d1 --- /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 0000000000..dc6f50fdbe --- /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 ac1fafb42e..57a458b09c 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.LauncherAppState; 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 @@ public class QsbContainerView extends FrameLayout { /** * 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 @@ public class QsbContainerView extends FrameLayout { 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 class QsbContainerView extends FrameLayout { 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 @@ public class QsbContainerView extends FrameLayout { 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 @@ public class QsbContainerView extends FrameLayout { } 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 @@ public class QsbContainerView extends FrameLayout { } 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 @@ public class QsbContainerView extends FrameLayout { return; } - if (mWrapper != null && getActivity() != null) { + if (mWrapper != null && getContext() != null) { mWrapper.removeAllViews(); mWrapper.addView(createQsb(mWrapper)); } @@ -217,10 +219,10 @@ public class QsbContainerView extends FrameLayout { } 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 @@ public class QsbContainerView extends FrameLayout { */ 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 0000000000..379a8da4e9 --- /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() { + @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(); + } +}