Adding support for launcher preview generation

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

Bug: 118758696
Change-Id: I0aebeb6eed37f72edd1cc305e58eece305aae3ff
This commit is contained in:
Sunny Goyal 2018-11-21 14:12:00 -08:00
parent aa576bce02
commit ef92b82778
13 changed files with 657 additions and 141 deletions

View File

@ -132,6 +132,16 @@
</intent-filter>
</activity>
<!-- TODO: Remove -->
<activity android:name="com.android.launcher3.util.MyActivity"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!--
Should point to the content provider which can be used to dump Launcher3 compatible
worspace configuration to the dump's file descriptor by using launcher_dump.proto

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<com.android.launcher3.InsettableFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.android.launcher3.CellLayout
android:id="@+id/workspace"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top|left"
android:theme="@style/HomeScreenElementTheme"
launcher:containerType="workspace"
launcher:pageIndicator="@+id/page_indicator"/>
<com.android.launcher3.Hotseat
android:id="@+id/hotseat"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:theme="@style/HomeScreenElementTheme"
launcher:containerType="hotseat" />
<com.android.launcher3.InsettableFrameLayout
android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<include
android:id="@id/search_container_all_apps"
layout="@layout/search_container_all_apps"/>
</com.android.launcher3.InsettableFrameLayout>
</com.android.launcher3.InsettableFrameLayout>

View File

@ -148,4 +148,9 @@
<attr name="numMinRows" format="integer" />
<attr name="numMinColumns" format="integer" />
</declare-styleable>
<declare-styleable name="PreviewFragment">
<attr name="android:name" />
<attr name="android:id" />
</declare-styleable>
</resources>

View File

@ -66,6 +66,8 @@
<string name="all_apps_no_search_results">No apps found matching \"<xliff:g id="query" example="Android">%1$s</xliff:g>\"</string>
<!-- Label for the button which allows the user to get app search results. [CHAR_LIMIT=50] -->
<string name="all_apps_search_market_message">Search for more apps</string>
<!-- Label for an icon representing any generic app. [CHAR_LIMIT=50] -->
<string name="label_application">App</string>
<!-- Popup items -->
<!-- Text to display as the header above notifications. [CHAR_LIMIT=30] -->

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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<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 class Workspace extends PagedView<WorkspacePageIndicator>
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<WorkspacePageIndicator>
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<WorkspacePageIndicator>
}
}
/**
* 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

View File

@ -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) { }
}

View File

@ -225,8 +225,7 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> {
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<PageIndicatorDots> {
}
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();

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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();
}
}