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:
parent
aa576bce02
commit
ef92b82778
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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] -->
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) { }
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
if (!isInPreviewMode()) {
|
||||
if (!Utilities.containsAll(AppWidgetManager.getInstance(context)
|
||||
.getAppWidgetOptions(widgetId), opts)) {
|
||||
mQsb.updateAppWidgetOptions(opts);
|
||||
}
|
||||
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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue