Merge "Rewrite: Widget preview loader logic" into ub-launcher3-burnaby
This commit is contained in:
@@ -216,15 +216,6 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="com.android.launcher3.PackageChangedReceiver" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_CHANGED"/>
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED"/>
|
||||
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
|
||||
<data android:scheme="package"></data>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="com.android.launcher3.StartupReceiver" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
|
||||
@@ -29,10 +29,8 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
@@ -48,90 +46,6 @@ import com.android.launcher3.compat.AppWidgetManagerCompat;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* A simple callback interface which also provides the results of the task.
|
||||
*/
|
||||
interface AsyncTaskCallback {
|
||||
void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data);
|
||||
}
|
||||
|
||||
/**
|
||||
* The data needed to perform either of the custom AsyncTasks.
|
||||
*/
|
||||
class AsyncTaskPageData {
|
||||
enum Type {
|
||||
LoadWidgetPreviewData
|
||||
}
|
||||
|
||||
AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR,
|
||||
AsyncTaskCallback postR, WidgetPreviewLoader w) {
|
||||
page = p;
|
||||
items = l;
|
||||
generatedImages = new ArrayList<Bitmap>();
|
||||
maxImageWidth = cw;
|
||||
maxImageHeight = ch;
|
||||
doInBackgroundCallback = bgR;
|
||||
postExecuteCallback = postR;
|
||||
widgetPreviewLoader = w;
|
||||
}
|
||||
void cleanup(boolean cancelled) {
|
||||
// Clean up any references to source/generated bitmaps
|
||||
if (generatedImages != null) {
|
||||
if (cancelled) {
|
||||
for (int i = 0; i < generatedImages.size(); i++) {
|
||||
widgetPreviewLoader.recycleBitmap(items.get(i), generatedImages.get(i));
|
||||
}
|
||||
}
|
||||
generatedImages.clear();
|
||||
}
|
||||
}
|
||||
int page;
|
||||
ArrayList<Object> items;
|
||||
ArrayList<Bitmap> sourceImages;
|
||||
ArrayList<Bitmap> generatedImages;
|
||||
int maxImageWidth;
|
||||
int maxImageHeight;
|
||||
AsyncTaskCallback doInBackgroundCallback;
|
||||
AsyncTaskCallback postExecuteCallback;
|
||||
WidgetPreviewLoader widgetPreviewLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic template for an async task used in AppsCustomize.
|
||||
*/
|
||||
class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> {
|
||||
AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) {
|
||||
page = p;
|
||||
threadPriority = Process.THREAD_PRIORITY_DEFAULT;
|
||||
dataType = ty;
|
||||
}
|
||||
@Override
|
||||
protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) {
|
||||
if (params.length != 1) return null;
|
||||
// Load each of the widget previews in the background
|
||||
params[0].doInBackgroundCallback.run(this, params[0]);
|
||||
return params[0];
|
||||
}
|
||||
@Override
|
||||
protected void onPostExecute(AsyncTaskPageData result) {
|
||||
// All the widget previews are loaded, so we can just callback to inflate the page
|
||||
result.postExecuteCallback.run(this, result);
|
||||
}
|
||||
|
||||
void setThreadPriority(int p) {
|
||||
threadPriority = p;
|
||||
}
|
||||
void syncThreadPriority() {
|
||||
Process.setThreadPriority(threadPriority);
|
||||
}
|
||||
|
||||
// The page that this async task is associated with
|
||||
AsyncTaskPageData.Type dataType;
|
||||
int page;
|
||||
int threadPriority;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Apps/Customize page that displays all the applications, widgets, and shortcuts.
|
||||
@@ -142,6 +56,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
static final String TAG = "AppsCustomizePagedView";
|
||||
|
||||
private static Rect sTmpRect = new Rect();
|
||||
private static final int[] sTempPosArray = new int[2];
|
||||
|
||||
/**
|
||||
* The different content types that this paged view can show.
|
||||
@@ -171,10 +86,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
@Thunk int mWidgetCountX, mWidgetCountY;
|
||||
private int mNumWidgetPages;
|
||||
|
||||
// Previews & outlines
|
||||
ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
|
||||
private static final int sPageSleepDelay = 200;
|
||||
|
||||
private final PagedViewKeyListener mKeyListener = new PagedViewKeyListener();
|
||||
|
||||
private Runnable mInflateWidgetRunnable = null;
|
||||
@@ -192,11 +103,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
private Toast mWidgetInstructionToast;
|
||||
|
||||
// Deferral of loading widget previews during launcher transitions
|
||||
@Thunk boolean mInTransition;
|
||||
private ArrayList<AsyncTaskPageData> mDeferredSyncWidgetPageItems =
|
||||
new ArrayList<AsyncTaskPageData>();
|
||||
@Thunk ArrayList<Runnable> mDeferredPrepareLoadWidgetPreviewsTasks =
|
||||
new ArrayList<Runnable>();
|
||||
private boolean mInTransition;
|
||||
|
||||
WidgetPreviewLoader mWidgetPreviewLoader;
|
||||
|
||||
@@ -209,7 +116,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
mPackageManager = context.getPackageManager();
|
||||
mWidgets = new ArrayList<>();
|
||||
mIconCache = (LauncherAppState.getInstance()).getIconCache();
|
||||
mRunningTasks = new ArrayList<>();
|
||||
|
||||
// Save the default widget preview background
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
|
||||
@@ -253,7 +159,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
|
||||
WidgetPreviewLoader getWidgetPreviewLoader() {
|
||||
if (mWidgetPreviewLoader == null) {
|
||||
mWidgetPreviewLoader = new WidgetPreviewLoader(mLauncher);
|
||||
mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache();
|
||||
}
|
||||
return mWidgetPreviewLoader;
|
||||
}
|
||||
@@ -484,8 +390,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
info.boundWidget = hostView;
|
||||
mWidgetCleanupState = WIDGET_INFLATED;
|
||||
hostView.setVisibility(INVISIBLE);
|
||||
int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info.spanX,
|
||||
info.spanY, info, false);
|
||||
int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info, false);
|
||||
|
||||
// We want the first widget layout to be the correct size. This will be important
|
||||
// for width size reporting to the AppWidgetManager.
|
||||
@@ -554,7 +459,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
}
|
||||
}
|
||||
|
||||
private boolean beginDraggingWidget(View v) {
|
||||
private boolean beginDraggingWidget(PagedViewWidget v) {
|
||||
mDraggingWidget = true;
|
||||
// Get the widget preview as the drag representation
|
||||
ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
|
||||
@@ -582,10 +487,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
|
||||
PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo;
|
||||
createItemInfo = createWidgetInfo;
|
||||
int spanX = createItemInfo.spanX;
|
||||
int spanY = createItemInfo.spanY;
|
||||
int[] size = mLauncher.getWorkspace().estimateItemSize(spanX, spanY,
|
||||
createWidgetInfo, true);
|
||||
int[] size = mLauncher.getWorkspace().estimateItemSize(createWidgetInfo, true);
|
||||
|
||||
FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable();
|
||||
float minScale = 1.25f;
|
||||
@@ -594,10 +496,9 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
int[] previewSizeBeforeScale = new int[1];
|
||||
preview = getWidgetPreviewLoader().generateWidgetPreview(createWidgetInfo.info,
|
||||
maxWidth, null, previewSizeBeforeScale);
|
||||
|
||||
// Compare the size of the drag preview to the preview in the AppsCustomize tray
|
||||
int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0],
|
||||
getWidgetPreviewLoader().maxWidthForWidgetPreview(spanX));
|
||||
v.getActualItemWidth());
|
||||
scale = previewWidthInAppsCustomize / (float) preview.getWidth();
|
||||
|
||||
// The bitmap in the AppsCustomize tray is always the the same size, so there
|
||||
@@ -637,7 +538,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
if (!super.beginDragging(v)) return false;
|
||||
|
||||
if (v instanceof PagedViewWidget) {
|
||||
if (!beginDraggingWidget(v)) {
|
||||
if (!beginDraggingWidget((PagedViewWidget) v)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -690,7 +591,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
|
||||
mInTransition = true;
|
||||
if (toWorkspace) {
|
||||
cancelAllTasks();
|
||||
cancelAllTasks(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,15 +606,10 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
@Override
|
||||
public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
|
||||
mInTransition = false;
|
||||
for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) {
|
||||
onSyncWidgetPageItems(d, false);
|
||||
}
|
||||
mDeferredSyncWidgetPageItems.clear();
|
||||
for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) {
|
||||
r.run();
|
||||
}
|
||||
mDeferredPrepareLoadWidgetPreviewsTasks.clear();
|
||||
mForceDrawAllChildrenNextFrame = !toWorkspace;
|
||||
if (!toWorkspace) {
|
||||
loadPreviewsForPage(getNextPage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -782,45 +678,26 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
cancelAllTasks();
|
||||
cancelAllTasks(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trimMemory() {
|
||||
super.trimMemory();
|
||||
clearAllWidgetPages();
|
||||
cancelAllTasks(true);
|
||||
}
|
||||
|
||||
public void clearAllWidgetPages() {
|
||||
cancelAllTasks();
|
||||
int count = getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View v = getPageAt(i);
|
||||
if (v instanceof PagedViewGridLayout) {
|
||||
((PagedViewGridLayout) v).removeAllViewsOnPage();
|
||||
mDirtyPageContent.set(i, true);
|
||||
private void cancelAllTasks(boolean clearCompletedTasks) {
|
||||
for (int page = getPageCount() - 1; page >= 0; page--) {
|
||||
final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
|
||||
if (layout != null) {
|
||||
for (int i = 0; i < layout.getChildCount(); i++) {
|
||||
((PagedViewWidget) layout.getChildAt(i)).deletePreview(clearCompletedTasks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelAllTasks() {
|
||||
// Clean up all the async tasks
|
||||
Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
|
||||
while (iter.hasNext()) {
|
||||
AppsCustomizeAsyncTask task = iter.next();
|
||||
task.cancel(false);
|
||||
iter.remove();
|
||||
mDirtyPageContent.set(task.page, true);
|
||||
|
||||
// We've already preallocated the views for the data to load into, so clear them as well
|
||||
View v = getPageAt(task.page);
|
||||
if (v instanceof PagedViewGridLayout) {
|
||||
((PagedViewGridLayout) v).removeAllViewsOnPage();
|
||||
}
|
||||
}
|
||||
mDeferredSyncWidgetPageItems.clear();
|
||||
mDeferredPrepareLoadWidgetPreviewsTasks.clear();
|
||||
}
|
||||
|
||||
public void setContentType(ContentType type) {
|
||||
// Widgets appear to be cleared every time you leave, always force invalidate for them
|
||||
@@ -835,23 +712,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
return mContentType;
|
||||
}
|
||||
|
||||
protected void snapToPage(int whichPage, int delta, int duration) {
|
||||
super.snapToPage(whichPage, delta, duration);
|
||||
|
||||
// Update the thread priorities given the direction lookahead
|
||||
Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
|
||||
while (iter.hasNext()) {
|
||||
AppsCustomizeAsyncTask task = iter.next();
|
||||
int pageIndex = task.page;
|
||||
if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) ||
|
||||
(mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) {
|
||||
task.setThreadPriority(getThreadPriorityForPage(pageIndex));
|
||||
} else {
|
||||
task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setPageBackgroundsVisible(boolean visible) {
|
||||
mPageBackgroundsVisible = visible;
|
||||
int childCount = getChildCount();
|
||||
@@ -863,104 +723,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to return the priority for loading of the specified widget page.
|
||||
*/
|
||||
private int getWidgetPageLoadPriority(int page) {
|
||||
// If we are snapping to another page, use that index as the target page index
|
||||
int toPage = mCurrentPage;
|
||||
if (mNextPage > -1) {
|
||||
toPage = mNextPage;
|
||||
}
|
||||
|
||||
// We use the distance from the target page as an initial guess of priority, but if there
|
||||
// are no pages of higher priority than the page specified, then bump up the priority of
|
||||
// the specified page.
|
||||
Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
|
||||
int minPageDiff = Integer.MAX_VALUE;
|
||||
while (iter.hasNext()) {
|
||||
AppsCustomizeAsyncTask task = iter.next();
|
||||
minPageDiff = Math.abs(task.page - toPage);
|
||||
}
|
||||
|
||||
int rawPageDiff = Math.abs(page - toPage);
|
||||
return rawPageDiff - Math.min(rawPageDiff, minPageDiff);
|
||||
}
|
||||
/**
|
||||
* Return the appropriate thread priority for loading for a given page (we give the current
|
||||
* page much higher priority)
|
||||
*/
|
||||
private int getThreadPriorityForPage(int page) {
|
||||
// TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below
|
||||
int pageDiff = getWidgetPageLoadPriority(page);
|
||||
if (pageDiff <= 0) {
|
||||
return Process.THREAD_PRIORITY_LESS_FAVORABLE;
|
||||
} else if (pageDiff <= 1) {
|
||||
return Process.THREAD_PRIORITY_LOWEST;
|
||||
} else {
|
||||
return Process.THREAD_PRIORITY_LOWEST;
|
||||
}
|
||||
}
|
||||
private int getSleepForPage(int page) {
|
||||
int pageDiff = getWidgetPageLoadPriority(page);
|
||||
return Math.max(0, pageDiff * sPageSleepDelay);
|
||||
}
|
||||
/**
|
||||
* Creates and executes a new AsyncTask to load a page of widget previews.
|
||||
*/
|
||||
@Thunk void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets,
|
||||
int cellWidth, int cellHeight, int cellCountX) {
|
||||
|
||||
// Prune all tasks that are no longer needed
|
||||
Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
|
||||
while (iter.hasNext()) {
|
||||
AppsCustomizeAsyncTask task = iter.next();
|
||||
int taskPage = task.page;
|
||||
if (taskPage < getAssociatedLowerPageBound(mCurrentPage) ||
|
||||
taskPage > getAssociatedUpperPageBound(mCurrentPage)) {
|
||||
task.cancel(false);
|
||||
iter.remove();
|
||||
} else {
|
||||
task.setThreadPriority(getThreadPriorityForPage(taskPage));
|
||||
}
|
||||
}
|
||||
|
||||
// We introduce a slight delay to order the loading of side pages so that we don't thrash
|
||||
final int sleepMs = getSleepForPage(page);
|
||||
AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
|
||||
new AsyncTaskCallback() {
|
||||
@Override
|
||||
public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
|
||||
try {
|
||||
try {
|
||||
Thread.sleep(sleepMs);
|
||||
} catch (Exception e) {}
|
||||
loadWidgetPreviewsInBackground(task, data);
|
||||
} finally {
|
||||
if (task.isCancelled()) {
|
||||
data.cleanup(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new AsyncTaskCallback() {
|
||||
@Override
|
||||
public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
|
||||
mRunningTasks.remove(task);
|
||||
if (task.isCancelled()) return;
|
||||
// do cleanup inside onSyncWidgetPageItems
|
||||
onSyncWidgetPageItems(data, false);
|
||||
}
|
||||
}, getWidgetPreviewLoader());
|
||||
|
||||
// Ensure that the task is appropriately prioritized and runs in parallel
|
||||
AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
|
||||
AsyncTaskPageData.Type.LoadWidgetPreviewData);
|
||||
t.setThreadPriority(getThreadPriorityForPage(page));
|
||||
t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
|
||||
mRunningTasks.add(t);
|
||||
}
|
||||
|
||||
/*
|
||||
* Widgets PagedView implementation
|
||||
*/
|
||||
@@ -1051,95 +813,18 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
layout.addView(widget, lp);
|
||||
}
|
||||
|
||||
// wait until a call on onLayout to start loading, because
|
||||
// PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out
|
||||
// TODO: can we do a measure/layout immediately?
|
||||
layout.setOnLayoutListener(new Runnable() {
|
||||
public void run() {
|
||||
// Load the widget previews
|
||||
int maxPreviewWidth = cellWidth;
|
||||
int maxPreviewHeight = cellHeight;
|
||||
if (layout.getChildCount() > 0) {
|
||||
PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0);
|
||||
int[] maxSize = w.getPreviewSize();
|
||||
maxPreviewWidth = maxSize[0];
|
||||
maxPreviewHeight = maxSize[1];
|
||||
}
|
||||
|
||||
getWidgetPreviewLoader().setPreviewSize(maxPreviewWidth, maxPreviewHeight);
|
||||
if (immediate) {
|
||||
AsyncTaskPageData data = new AsyncTaskPageData(page, items,
|
||||
maxPreviewWidth, maxPreviewHeight, null, null, getWidgetPreviewLoader());
|
||||
loadWidgetPreviewsInBackground(null, data);
|
||||
onSyncWidgetPageItems(data, immediate);
|
||||
} else {
|
||||
if (mInTransition) {
|
||||
mDeferredPrepareLoadWidgetPreviewsTasks.add(this);
|
||||
} else {
|
||||
prepareLoadWidgetPreviewsTask(page, items,
|
||||
maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
|
||||
}
|
||||
}
|
||||
layout.setOnLayoutListener(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
@Thunk void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
|
||||
AsyncTaskPageData data) {
|
||||
// loadWidgetPreviewsInBackground can be called without a task to load a set of widget
|
||||
// previews synchronously
|
||||
if (task != null) {
|
||||
// Ensure that this task starts running at the correct priority
|
||||
task.syncThreadPriority();
|
||||
}
|
||||
|
||||
// Load each of the widget/shortcut previews
|
||||
ArrayList<Object> items = data.items;
|
||||
ArrayList<Bitmap> images = data.generatedImages;
|
||||
int count = items.size();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (task != null) {
|
||||
// Ensure we haven't been cancelled yet
|
||||
if (task.isCancelled()) break;
|
||||
// Before work on each item, ensure that this task is running at the correct
|
||||
// priority
|
||||
task.syncThreadPriority();
|
||||
}
|
||||
|
||||
images.add(getWidgetPreviewLoader().getPreview(items.get(i)));
|
||||
if (immediate && !mInTransition) {
|
||||
loadPreviewsForPage(page);
|
||||
}
|
||||
}
|
||||
|
||||
@Thunk void onSyncWidgetPageItems(AsyncTaskPageData data, boolean immediatelySyncItems) {
|
||||
if (!immediatelySyncItems && mInTransition) {
|
||||
mDeferredSyncWidgetPageItems.add(data);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
int page = data.page;
|
||||
PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
|
||||
private void loadPreviewsForPage(int page) {
|
||||
final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
|
||||
|
||||
ArrayList<Object> items = data.items;
|
||||
int count = items.size();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
|
||||
if (widget != null) {
|
||||
Bitmap preview = data.generatedImages.get(i);
|
||||
widget.applyPreview(new FastBitmapDrawable(preview), i);
|
||||
}
|
||||
if (layout != null) {
|
||||
for (int i = 0; i < layout.getChildCount(); i++) {
|
||||
((PagedViewWidget) layout.getChildAt(i)).ensurePreview();
|
||||
}
|
||||
|
||||
enableHwLayersOnVisiblePages();
|
||||
|
||||
// Update all thread priorities
|
||||
Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
|
||||
while (iter.hasNext()) {
|
||||
AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
|
||||
int pageIndex = task.page;
|
||||
task.setThreadPriority(getThreadPriorityForPage(pageIndex));
|
||||
}
|
||||
} finally {
|
||||
data.cleanup(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1148,7 +833,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
disablePagedViewAnimations();
|
||||
|
||||
removeAllViews();
|
||||
cancelAllTasks();
|
||||
cancelAllTasks(true);
|
||||
|
||||
Context context = getContext();
|
||||
if (mContentType == ContentType.Widgets) {
|
||||
@@ -1254,6 +939,17 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
mSaveInstanceStateItemIndex = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPageBeginMoving() {
|
||||
super.onPageBeginMoving();
|
||||
if (!mInTransition) {
|
||||
getVisiblePages(sTempPosArray);
|
||||
for (int i = sTempPosArray[0]; i <= sTempPosArray[1]; i++) {
|
||||
loadPreviewsForPage(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* AllAppsView implementation
|
||||
*/
|
||||
@@ -1274,7 +970,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
// request a layout to trigger the page data when ready.
|
||||
requestLayout();
|
||||
} else {
|
||||
cancelAllTasks();
|
||||
cancelAllTasks(false);
|
||||
invalidatePageData();
|
||||
}
|
||||
}
|
||||
@@ -1324,7 +1020,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
|
||||
// should stop this now.
|
||||
|
||||
// Stop all background tasks
|
||||
cancelAllTasks();
|
||||
cancelAllTasks(true);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -656,8 +656,8 @@ public class IconCache {
|
||||
|
||||
public ContentValues newContentValues(Bitmap icon, String label) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(IconDB.COLUMN_ICON, ItemInfo.flattenBitmap(icon));
|
||||
values.put(IconDB.COLUMN_ICON_LOW_RES, ItemInfo.flattenBitmap(
|
||||
values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon));
|
||||
values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(
|
||||
Bitmap.createScaledBitmap(icon,
|
||||
icon.getWidth() / LOW_RES_SCALE_FACTOR,
|
||||
icon.getHeight() / LOW_RES_SCALE_FACTOR, true)));
|
||||
|
||||
@@ -331,7 +331,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
|
||||
.key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
|
||||
.key(NAME_KEY).value(name);
|
||||
if (icon != null) {
|
||||
byte[] iconByteArray = ItemInfo.flattenBitmap(icon);
|
||||
byte[] iconByteArray = Utilities.flattenBitmap(icon);
|
||||
json = json.key(ICON_KEY).value(
|
||||
Base64.encodeToString(
|
||||
iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
|
||||
|
||||
@@ -20,13 +20,10 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.compat.UserHandleCompat;
|
||||
import com.android.launcher3.compat.UserManagerCompat;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
@@ -177,25 +174,9 @@ public class ItemInfo {
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] flattenBitmap(Bitmap bitmap) {
|
||||
// Try go guesstimate how much space the icon will take when serialized
|
||||
// to avoid unnecessary allocations/copies during the write.
|
||||
int size = bitmap.getWidth() * bitmap.getHeight() * 4;
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(size);
|
||||
try {
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||
out.flush();
|
||||
out.close();
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
Log.w("Favorite", "Could not write icon");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static void writeBitmap(ContentValues values, Bitmap bitmap) {
|
||||
if (bitmap != null) {
|
||||
byte[] data = flattenBitmap(bitmap);
|
||||
byte[] data = Utilities.flattenBitmap(bitmap);
|
||||
values.put(LauncherSettings.Favorites.ICON, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import android.os.Handler;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.View.AccessibilityDelegate;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.launcher3.compat.LauncherAppsCompat;
|
||||
@@ -49,12 +48,12 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
|
||||
private final BuildInfo mBuildInfo;
|
||||
@Thunk final LauncherModel mModel;
|
||||
private final IconCache mIconCache;
|
||||
private final WidgetPreviewLoader mWidgetCache;
|
||||
|
||||
private final boolean mIsScreenLarge;
|
||||
private final float mScreenDensity;
|
||||
private final int mLongPressTimeout = 300;
|
||||
|
||||
private WidgetPreviewLoader.CacheDb mWidgetPreviewCacheDb;
|
||||
private boolean mWallpaperChangedSinceLastCheck;
|
||||
|
||||
private static WeakReference<LauncherProvider> sLauncherProvider;
|
||||
@@ -101,9 +100,8 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
|
||||
// set sIsScreenXLarge and mScreenDensity *before* creating icon cache
|
||||
mIsScreenLarge = isScreenLarge(sContext.getResources());
|
||||
mScreenDensity = sContext.getResources().getDisplayMetrics().density;
|
||||
|
||||
recreateWidgetPreviewDb();
|
||||
mIconCache = new IconCache(sContext);
|
||||
mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
|
||||
|
||||
mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
|
||||
mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
|
||||
@@ -125,13 +123,6 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
|
||||
mFavoritesObserver);
|
||||
}
|
||||
|
||||
public void recreateWidgetPreviewDb() {
|
||||
if (mWidgetPreviewCacheDb != null) {
|
||||
mWidgetPreviewCacheDb.close();
|
||||
}
|
||||
mWidgetPreviewCacheDb = new WidgetPreviewLoader.CacheDb(sContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call from Application.onTerminate(), which is not guaranteed to ever be called.
|
||||
*/
|
||||
@@ -181,10 +172,6 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
|
||||
return mAppFilter == null || mAppFilter.shouldShowApp(componentName);
|
||||
}
|
||||
|
||||
WidgetPreviewLoader.CacheDb getWidgetPreviewCacheDb() {
|
||||
return mWidgetPreviewCacheDb;
|
||||
}
|
||||
|
||||
static void setLauncherProvider(LauncherProvider provider) {
|
||||
sLauncherProvider = new WeakReference<LauncherProvider>(provider);
|
||||
}
|
||||
@@ -240,6 +227,10 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
|
||||
return mDynamicGrid;
|
||||
}
|
||||
|
||||
public WidgetPreviewLoader getWidgetCache() {
|
||||
return mWidgetCache;
|
||||
}
|
||||
|
||||
public boolean isScreenLarge() {
|
||||
return mIsScreenLarge;
|
||||
}
|
||||
|
||||
@@ -630,7 +630,7 @@ public class LauncherBackupHelper implements BackupHelper {
|
||||
return;
|
||||
}
|
||||
final ContentResolver cr = mContext.getContentResolver();
|
||||
final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext);
|
||||
final WidgetPreviewLoader previewLoader = appState.getWidgetCache();
|
||||
final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
|
||||
final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
|
||||
if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
|
||||
@@ -646,7 +646,6 @@ public class LauncherBackupHelper implements BackupHelper {
|
||||
final long id = cursor.getLong(ID_INDEX);
|
||||
final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX);
|
||||
final int spanX = cursor.getInt(SPANX_INDEX);
|
||||
final int spanY = cursor.getInt(SPANY_INDEX);
|
||||
final ComponentName provider = ComponentName.unflattenFromString(providerName);
|
||||
Key key = null;
|
||||
String backupKey = null;
|
||||
@@ -665,12 +664,10 @@ public class LauncherBackupHelper implements BackupHelper {
|
||||
if (DEBUG) Log.d(TAG, "I can count this high: " + backupWidgetCount);
|
||||
if (backupWidgetCount < MAX_WIDGETS_PER_PASS) {
|
||||
if (DEBUG) Log.d(TAG, "saving widget " + backupKey);
|
||||
previewLoader.setPreviewSize(
|
||||
spanX * profile.cellWidthPx,
|
||||
spanY * profile.cellHeightPx);
|
||||
UserHandleCompat user = UserHandleCompat.myUserHandle();
|
||||
writeRowToBackup(key,
|
||||
packWidget(dpi, previewLoader, mIconCache, provider, user),
|
||||
packWidget(dpi, previewLoader,spanX * profile.cellWidthPx,
|
||||
mIconCache, provider, user),
|
||||
data);
|
||||
mKeys.add(key);
|
||||
backupWidgetCount ++;
|
||||
@@ -980,7 +977,8 @@ public class LauncherBackupHelper implements BackupHelper {
|
||||
}
|
||||
|
||||
/** Serialize a widget for persistence, including a checksum wrapper. */
|
||||
private Widget packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache,
|
||||
private Widget packWidget(int dpi, WidgetPreviewLoader previewLoader,
|
||||
int previewWidth, IconCache iconCache,
|
||||
ComponentName provider, UserHandleCompat user) {
|
||||
final LauncherAppWidgetProviderInfo info =
|
||||
LauncherModel.getProviderInfo(mContext, provider, user);
|
||||
@@ -1000,7 +998,7 @@ public class LauncherBackupHelper implements BackupHelper {
|
||||
}
|
||||
if (info.previewImage != 0) {
|
||||
widget.preview = new Resource();
|
||||
Bitmap preview = previewLoader.generateWidgetPreview(info, null);
|
||||
Bitmap preview = previewLoader.generateWidgetPreview(info, previewWidth, null);
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
if (preview.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
|
||||
widget.preview.data = os.toByteArray();
|
||||
|
||||
@@ -1622,6 +1622,9 @@ public class LauncherModel extends BroadcastReceiver
|
||||
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
|
||||
loadAndBindAllApps();
|
||||
|
||||
// Remove entries for packages which changed while the launcher was dead.
|
||||
LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews();
|
||||
|
||||
// Restore the default thread priority after we are done loading items
|
||||
synchronized (mLock) {
|
||||
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
|
||||
@@ -3007,8 +3010,7 @@ public class LauncherModel extends BroadcastReceiver
|
||||
if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
|
||||
mIconCache.updateIconsForPkg(packages[i], mUser);
|
||||
mBgAllAppsList.updatePackage(context, packages[i], mUser);
|
||||
WidgetPreviewLoader.removePackageFromDb(
|
||||
mApp.getWidgetPreviewCacheDb(), packages[i]);
|
||||
mApp.getWidgetCache().removePackage(packages[i], mUser);
|
||||
}
|
||||
break;
|
||||
case OP_REMOVE:
|
||||
@@ -3034,8 +3036,7 @@ public class LauncherModel extends BroadcastReceiver
|
||||
for (int i=0; i<N; i++) {
|
||||
if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
|
||||
mBgAllAppsList.removePackage(packages[i], mUser);
|
||||
WidgetPreviewLoader.removePackageFromDb(
|
||||
mApp.getWidgetPreviewCacheDb(), packages[i]);
|
||||
mApp.getWidgetCache().removePackage(packages[i], mUser);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4,18 +4,10 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
// TODO: Remove this
|
||||
public class PackageChangedReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
final String packageName = intent.getData().getSchemeSpecificPart();
|
||||
|
||||
if (packageName == null || packageName.length() == 0) {
|
||||
// they sent us a bad intent
|
||||
return;
|
||||
}
|
||||
// in rare cases the receiver races with the application to set up LauncherAppState
|
||||
LauncherAppState.setApplicationContext(context.getApplicationContext());
|
||||
LauncherAppState app = LauncherAppState.getInstance();
|
||||
WidgetPreviewLoader.removePackageFromDb(app.getWidgetPreviewCacheDb(), packageName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,476 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewDebug;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* An abstraction of the original CellLayout which supports laying out items
|
||||
* which span multiple cells into a grid-like layout. Also supports dimming
|
||||
* to give a preview of its contents.
|
||||
*/
|
||||
public class PagedViewCellLayout extends ViewGroup implements Page {
|
||||
static final String TAG = "PagedViewCellLayout";
|
||||
|
||||
private int mCellCountX;
|
||||
private int mCellCountY;
|
||||
private int mOriginalCellWidth;
|
||||
private int mOriginalCellHeight;
|
||||
private int mCellWidth;
|
||||
private int mCellHeight;
|
||||
private int mOriginalWidthGap;
|
||||
private int mOriginalHeightGap;
|
||||
private int mWidthGap;
|
||||
private int mHeightGap;
|
||||
protected PagedViewCellLayoutChildren mChildren;
|
||||
|
||||
public PagedViewCellLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public PagedViewCellLayout(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public PagedViewCellLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
setAlwaysDrawnWithCacheEnabled(false);
|
||||
|
||||
// setup default cell parameters
|
||||
LauncherAppState app = LauncherAppState.getInstance();
|
||||
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
|
||||
mOriginalCellWidth = mCellWidth = grid.cellWidthPx;
|
||||
mOriginalCellHeight = mCellHeight = grid.cellHeightPx;
|
||||
mCellCountX = (int) grid.numColumns;
|
||||
mCellCountY = (int) grid.numRows;
|
||||
mOriginalWidthGap = mOriginalHeightGap = mWidthGap = mHeightGap = -1;
|
||||
|
||||
mChildren = new PagedViewCellLayoutChildren(context);
|
||||
mChildren.setCellDimensions(mCellWidth, mCellHeight);
|
||||
mChildren.setGap(mWidthGap, mHeightGap);
|
||||
|
||||
addView(mChildren);
|
||||
}
|
||||
|
||||
public int getCellWidth() {
|
||||
return mCellWidth;
|
||||
}
|
||||
|
||||
public int getCellHeight() {
|
||||
return mCellHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelLongPress() {
|
||||
super.cancelLongPress();
|
||||
|
||||
// Cancel long press for all children
|
||||
final int count = getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View child = getChildAt(i);
|
||||
child.cancelLongPress();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addViewToCellLayout(View child, int index, int childId,
|
||||
PagedViewCellLayout.LayoutParams params) {
|
||||
final PagedViewCellLayout.LayoutParams lp = params;
|
||||
|
||||
// Generate an id for each view, this assumes we have at most 256x256 cells
|
||||
// per workspace screen
|
||||
if (lp.cellX >= 0 && lp.cellX <= (mCellCountX - 1) &&
|
||||
lp.cellY >= 0 && (lp.cellY <= mCellCountY - 1)) {
|
||||
// If the horizontal or vertical span is set to -1, it is taken to
|
||||
// mean that it spans the extent of the CellLayout
|
||||
if (lp.cellHSpan < 0) lp.cellHSpan = mCellCountX;
|
||||
if (lp.cellVSpan < 0) lp.cellVSpan = mCellCountY;
|
||||
|
||||
child.setId(childId);
|
||||
mChildren.addView(child, index, lp);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllViewsOnPage() {
|
||||
mChildren.removeAllViews();
|
||||
setLayerType(LAYER_TYPE_NONE, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeViewOnPageAt(int index) {
|
||||
mChildren.removeViewAt(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all the key listeners for the individual icons.
|
||||
*/
|
||||
public void resetChildrenOnKeyListeners() {
|
||||
int childCount = mChildren.getChildCount();
|
||||
for (int j = 0; j < childCount; ++j) {
|
||||
mChildren.getChildAt(j).setOnKeyListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPageChildCount() {
|
||||
return mChildren.getChildCount();
|
||||
}
|
||||
|
||||
public PagedViewCellLayoutChildren getChildrenLayout() {
|
||||
return mChildren;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getChildOnPageAt(int i) {
|
||||
return mChildren.getChildAt(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOfChildOnPage(View v) {
|
||||
return mChildren.indexOfChild(v);
|
||||
}
|
||||
|
||||
public int getCellCountX() {
|
||||
return mCellCountX;
|
||||
}
|
||||
|
||||
public int getCellCountY() {
|
||||
return mCellCountY;
|
||||
}
|
||||
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
|
||||
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
|
||||
throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
|
||||
}
|
||||
|
||||
int numWidthGaps = mCellCountX - 1;
|
||||
int numHeightGaps = mCellCountY - 1;
|
||||
|
||||
if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
|
||||
int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
|
||||
int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
|
||||
int hFreeSpace = hSpace - (mCellCountX * mOriginalCellWidth);
|
||||
int vFreeSpace = vSpace - (mCellCountY * mOriginalCellHeight);
|
||||
mWidthGap = numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0;
|
||||
mHeightGap = numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0;
|
||||
|
||||
mChildren.setGap(mWidthGap, mHeightGap);
|
||||
} else {
|
||||
mWidthGap = mOriginalWidthGap;
|
||||
mHeightGap = mOriginalHeightGap;
|
||||
}
|
||||
|
||||
// Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
|
||||
int newWidth = widthSpecSize;
|
||||
int newHeight = heightSpecSize;
|
||||
if (widthSpecMode == MeasureSpec.AT_MOST) {
|
||||
newWidth = getPaddingLeft() + getPaddingRight() + (mCellCountX * mCellWidth) +
|
||||
((mCellCountX - 1) * mWidthGap);
|
||||
newHeight = getPaddingTop() + getPaddingBottom() + (mCellCountY * mCellHeight) +
|
||||
((mCellCountY - 1) * mHeightGap);
|
||||
setMeasuredDimension(newWidth, newHeight);
|
||||
}
|
||||
|
||||
final int count = getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = getChildAt(i);
|
||||
int childWidthMeasureSpec =
|
||||
MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
|
||||
getPaddingRight(), MeasureSpec.EXACTLY);
|
||||
int childheightMeasureSpec =
|
||||
MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
|
||||
getPaddingBottom(), MeasureSpec.EXACTLY);
|
||||
child.measure(childWidthMeasureSpec, childheightMeasureSpec);
|
||||
}
|
||||
|
||||
setMeasuredDimension(newWidth, newHeight);
|
||||
}
|
||||
|
||||
int getContentWidth() {
|
||||
return getWidthBeforeFirstLayout() + getPaddingLeft() + getPaddingRight();
|
||||
}
|
||||
|
||||
int getContentHeight() {
|
||||
if (mCellCountY > 0) {
|
||||
return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getWidthBeforeFirstLayout() {
|
||||
if (mCellCountX > 0) {
|
||||
return mCellCountX * mCellWidth + (mCellCountX - 1) * Math.max(0, mWidthGap);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int count = getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = getChildAt(i);
|
||||
child.layout(getPaddingLeft(), getPaddingTop(),
|
||||
r - l - getPaddingRight(), b - t - getPaddingBottom());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
boolean result = super.onTouchEvent(event);
|
||||
int count = getPageChildCount();
|
||||
if (count > 0) {
|
||||
// We only intercept the touch if we are tapping in empty space after the final row
|
||||
View child = getChildOnPageAt(count - 1);
|
||||
int bottom = child.getBottom();
|
||||
int numRows = (int) Math.ceil((float) getPageChildCount() / getCellCountX());
|
||||
if (numRows < getCellCountY()) {
|
||||
// Add a little bit of buffer if there is room for another row
|
||||
bottom += mCellHeight / 2;
|
||||
}
|
||||
result = result || (event.getY() < bottom);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void enableCenteredContent(boolean enabled) {
|
||||
mChildren.enableCenteredContent(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setChildrenDrawingCacheEnabled(boolean enabled) {
|
||||
mChildren.setChildrenDrawingCacheEnabled(enabled);
|
||||
}
|
||||
|
||||
public void setCellCount(int xCount, int yCount) {
|
||||
mCellCountX = xCount;
|
||||
mCellCountY = yCount;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public void setGap(int widthGap, int heightGap) {
|
||||
mOriginalWidthGap = mWidthGap = widthGap;
|
||||
mOriginalHeightGap = mHeightGap = heightGap;
|
||||
mChildren.setGap(widthGap, heightGap);
|
||||
}
|
||||
|
||||
public int[] getCellCountForDimensions(int width, int height) {
|
||||
// Always assume we're working with the smallest span to make sure we
|
||||
// reserve enough space in both orientations
|
||||
int smallerSize = Math.min(mCellWidth, mCellHeight);
|
||||
|
||||
// Always round up to next largest cell
|
||||
int spanX = (width + smallerSize) / smallerSize;
|
||||
int spanY = (height + smallerSize) / smallerSize;
|
||||
|
||||
return new int[] { spanX, spanY };
|
||||
}
|
||||
|
||||
/**
|
||||
* Start dragging the specified child
|
||||
*
|
||||
* @param child The child that is being dragged
|
||||
*/
|
||||
void onDragChild(View child) {
|
||||
PagedViewCellLayout.LayoutParams lp = (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
|
||||
lp.isDragging = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimates the number of cells that the specified width would take up.
|
||||
*/
|
||||
public int estimateCellHSpan(int width) {
|
||||
// We don't show the next/previous pages any more, so we use the full width, minus the
|
||||
// padding
|
||||
int availWidth = width - (getPaddingLeft() + getPaddingRight());
|
||||
|
||||
// We know that we have to fit N cells with N-1 width gaps, so we just juggle to solve for N
|
||||
int n = Math.max(1, (availWidth + mWidthGap) / (mCellWidth + mWidthGap));
|
||||
|
||||
// We don't do anything fancy to determine if we squeeze another row in.
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimates the number of cells that the specified height would take up.
|
||||
*/
|
||||
public int estimateCellVSpan(int height) {
|
||||
// The space for a page is the height - top padding (current page) - bottom padding (current
|
||||
// page)
|
||||
int availHeight = height - (getPaddingTop() + getPaddingBottom());
|
||||
|
||||
// We know that we have to fit N cells with N-1 height gaps, so we juggle to solve for N
|
||||
int n = Math.max(1, (availHeight + mHeightGap) / (mCellHeight + mHeightGap));
|
||||
|
||||
// We don't do anything fancy to determine if we squeeze another row in.
|
||||
return n;
|
||||
}
|
||||
|
||||
/** Returns an estimated center position of the cell at the specified index */
|
||||
public int[] estimateCellPosition(int x, int y) {
|
||||
return new int[] {
|
||||
getPaddingLeft() + (x * mCellWidth) + (x * mWidthGap) + (mCellWidth / 2),
|
||||
getPaddingTop() + (y * mCellHeight) + (y * mHeightGap) + (mCellHeight / 2)
|
||||
};
|
||||
}
|
||||
|
||||
public void calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY) {
|
||||
mCellCountX = Math.min(maxCellCountX, estimateCellHSpan(width));
|
||||
mCellCountY = Math.min(maxCellCountY, estimateCellVSpan(height));
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
|
||||
return new PagedViewCellLayout.LayoutParams(getContext(), attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
|
||||
return p instanceof PagedViewCellLayout.LayoutParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
|
||||
return new PagedViewCellLayout.LayoutParams(p);
|
||||
}
|
||||
|
||||
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
|
||||
/**
|
||||
* Horizontal location of the item in the grid.
|
||||
*/
|
||||
@ViewDebug.ExportedProperty
|
||||
public int cellX;
|
||||
|
||||
/**
|
||||
* Vertical location of the item in the grid.
|
||||
*/
|
||||
@ViewDebug.ExportedProperty
|
||||
public int cellY;
|
||||
|
||||
/**
|
||||
* Number of cells spanned horizontally by the item.
|
||||
*/
|
||||
@ViewDebug.ExportedProperty
|
||||
public int cellHSpan;
|
||||
|
||||
/**
|
||||
* Number of cells spanned vertically by the item.
|
||||
*/
|
||||
@ViewDebug.ExportedProperty
|
||||
public int cellVSpan;
|
||||
|
||||
/**
|
||||
* Is this item currently being dragged
|
||||
*/
|
||||
public boolean isDragging;
|
||||
|
||||
// a data object that you can bind to this layout params
|
||||
private Object mTag;
|
||||
|
||||
// X coordinate of the view in the layout.
|
||||
@ViewDebug.ExportedProperty
|
||||
int x;
|
||||
// Y coordinate of the view in the layout.
|
||||
@ViewDebug.ExportedProperty
|
||||
int y;
|
||||
|
||||
public LayoutParams() {
|
||||
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
||||
cellHSpan = 1;
|
||||
cellVSpan = 1;
|
||||
}
|
||||
|
||||
public LayoutParams(Context c, AttributeSet attrs) {
|
||||
super(c, attrs);
|
||||
cellHSpan = 1;
|
||||
cellVSpan = 1;
|
||||
}
|
||||
|
||||
public LayoutParams(ViewGroup.LayoutParams source) {
|
||||
super(source);
|
||||
cellHSpan = 1;
|
||||
cellVSpan = 1;
|
||||
}
|
||||
|
||||
public LayoutParams(LayoutParams source) {
|
||||
super(source);
|
||||
this.cellX = source.cellX;
|
||||
this.cellY = source.cellY;
|
||||
this.cellHSpan = source.cellHSpan;
|
||||
this.cellVSpan = source.cellVSpan;
|
||||
}
|
||||
|
||||
public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
|
||||
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
||||
this.cellX = cellX;
|
||||
this.cellY = cellY;
|
||||
this.cellHSpan = cellHSpan;
|
||||
this.cellVSpan = cellVSpan;
|
||||
}
|
||||
|
||||
public void setup(Context context,
|
||||
int cellWidth, int cellHeight, int widthGap, int heightGap,
|
||||
int hStartPadding, int vStartPadding) {
|
||||
|
||||
final int myCellHSpan = cellHSpan;
|
||||
final int myCellVSpan = cellVSpan;
|
||||
final int myCellX = cellX;
|
||||
final int myCellY = cellY;
|
||||
|
||||
width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
|
||||
leftMargin - rightMargin;
|
||||
height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
|
||||
topMargin - bottomMargin;
|
||||
|
||||
if (LauncherAppState.getInstance().isScreenLarge()) {
|
||||
x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
|
||||
y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
|
||||
} else {
|
||||
x = myCellX * (cellWidth + widthGap) + leftMargin;
|
||||
y = myCellY * (cellHeight + heightGap) + topMargin;
|
||||
}
|
||||
}
|
||||
|
||||
public Object getTag() {
|
||||
return mTag;
|
||||
}
|
||||
|
||||
public void setTag(Object tag) {
|
||||
mTag = tag;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "(" + this.cellX + ", " + this.cellY + ", " +
|
||||
this.cellHSpan + ", " + this.cellVSpan + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* An abstraction of the original CellLayout which supports laying out items
|
||||
* which span multiple cells into a grid-like layout. Also supports dimming
|
||||
* to give a preview of its contents.
|
||||
*/
|
||||
public class PagedViewCellLayoutChildren extends ViewGroup {
|
||||
static final String TAG = "PagedViewCellLayout";
|
||||
|
||||
private boolean mCenterContent;
|
||||
|
||||
private int mCellWidth;
|
||||
private int mCellHeight;
|
||||
private int mWidthGap;
|
||||
private int mHeightGap;
|
||||
|
||||
public PagedViewCellLayoutChildren(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelLongPress() {
|
||||
super.cancelLongPress();
|
||||
|
||||
// Cancel long press for all children
|
||||
final int count = getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View child = getChildAt(i);
|
||||
child.cancelLongPress();
|
||||
}
|
||||
}
|
||||
|
||||
public void setGap(int widthGap, int heightGap) {
|
||||
mWidthGap = widthGap;
|
||||
mHeightGap = heightGap;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public void setCellDimensions(int width, int height) {
|
||||
mCellWidth = width;
|
||||
mCellHeight = height;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestChildFocus(View child, View focused) {
|
||||
super.requestChildFocus(child, focused);
|
||||
if (child != null) {
|
||||
Rect r = new Rect();
|
||||
child.getDrawingRect(r);
|
||||
requestRectangleOnScreen(r);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
|
||||
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
|
||||
throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
|
||||
}
|
||||
|
||||
final int count = getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = getChildAt(i);
|
||||
PagedViewCellLayout.LayoutParams lp =
|
||||
(PagedViewCellLayout.LayoutParams) child.getLayoutParams();
|
||||
lp.setup(getContext(),
|
||||
mCellWidth, mCellHeight, mWidthGap, mHeightGap,
|
||||
getPaddingLeft(),
|
||||
getPaddingTop());
|
||||
|
||||
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
|
||||
MeasureSpec.EXACTLY);
|
||||
int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
|
||||
MeasureSpec.EXACTLY);
|
||||
|
||||
child.measure(childWidthMeasureSpec, childheightMeasureSpec);
|
||||
}
|
||||
|
||||
setMeasuredDimension(widthSpecSize, heightSpecSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int count = getChildCount();
|
||||
|
||||
int offsetX = 0;
|
||||
if (mCenterContent && count > 0) {
|
||||
// determine the max width of all the rows and center accordingly
|
||||
int maxRowX = 0;
|
||||
int minRowX = Integer.MAX_VALUE;
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = getChildAt(i);
|
||||
if (child.getVisibility() != GONE) {
|
||||
PagedViewCellLayout.LayoutParams lp =
|
||||
(PagedViewCellLayout.LayoutParams) child.getLayoutParams();
|
||||
minRowX = Math.min(minRowX, lp.x);
|
||||
maxRowX = Math.max(maxRowX, lp.x + lp.width);
|
||||
}
|
||||
}
|
||||
int maxRowWidth = maxRowX - minRowX;
|
||||
offsetX = (getMeasuredWidth() - maxRowWidth) / 2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = getChildAt(i);
|
||||
if (child.getVisibility() != GONE) {
|
||||
PagedViewCellLayout.LayoutParams lp =
|
||||
(PagedViewCellLayout.LayoutParams) child.getLayoutParams();
|
||||
|
||||
int childLeft = offsetX + lp.x;
|
||||
int childTop = lp.y;
|
||||
child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void enableCenteredContent(boolean enabled) {
|
||||
mCenterContent = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setChildrenDrawingCacheEnabled(boolean enabled) {
|
||||
final int count = getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View view = getChildAt(i);
|
||||
view.setDrawingCacheEnabled(enabled);
|
||||
// Update the drawing caches
|
||||
if (!view.isHardwareAccelerated()) {
|
||||
view.buildDrawingCache(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,35 +20,38 @@ import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnLayoutChangeListener;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest;
|
||||
import com.android.launcher3.compat.AppWidgetManagerCompat;
|
||||
|
||||
/**
|
||||
* The linear layout used strictly for the widget/wallpaper tab of the customization tray
|
||||
*/
|
||||
public class PagedViewWidget extends LinearLayout {
|
||||
static final String TAG = "PagedViewWidgetLayout";
|
||||
public class PagedViewWidget extends LinearLayout implements OnLayoutChangeListener {
|
||||
|
||||
private static boolean sDeletePreviewsWhenDetachedFromWindow = true;
|
||||
private static boolean sRecyclePreviewsWhenDetachedFromWindow = true;
|
||||
private static PagedViewWidget sShortpressTarget = null;
|
||||
|
||||
private final Rect mOriginalImagePadding = new Rect();
|
||||
|
||||
private String mDimensionsFormatString;
|
||||
CheckForShortPress mPendingCheckForShortPress = null;
|
||||
ShortPressListener mShortPressListener = null;
|
||||
boolean mShortPressTriggered = false;
|
||||
static PagedViewWidget sShortpressTarget = null;
|
||||
boolean mIsAppWidget;
|
||||
private final Rect mOriginalImagePadding = new Rect();
|
||||
private CheckForShortPress mPendingCheckForShortPress = null;
|
||||
private ShortPressListener mShortPressListener = null;
|
||||
private boolean mShortPressTriggered = false;
|
||||
private boolean mIsAppWidget;
|
||||
private Object mInfo;
|
||||
|
||||
private WidgetPreviewLoader mWidgetPreviewLoader;
|
||||
private PreviewLoadRequest mActiveRequest;
|
||||
|
||||
public PagedViewWidget(Context context) {
|
||||
this(context, null);
|
||||
@@ -92,29 +95,24 @@ public class PagedViewWidget extends LinearLayout {
|
||||
}
|
||||
}
|
||||
|
||||
public static void setDeletePreviewsWhenDetachedFromWindow(boolean value) {
|
||||
sDeletePreviewsWhenDetachedFromWindow = value;
|
||||
}
|
||||
|
||||
public static void setRecyclePreviewsWhenDetachedFromWindow(boolean value) {
|
||||
sRecyclePreviewsWhenDetachedFromWindow = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
deletePreview(true);
|
||||
}
|
||||
|
||||
if (sDeletePreviewsWhenDetachedFromWindow) {
|
||||
public void deletePreview(boolean recycleImage) {
|
||||
if (recycleImage) {
|
||||
final ImageView image = (ImageView) findViewById(R.id.widget_preview);
|
||||
if (image != null) {
|
||||
FastBitmapDrawable preview = (FastBitmapDrawable) image.getDrawable();
|
||||
if (sRecyclePreviewsWhenDetachedFromWindow &&
|
||||
mInfo != null && preview != null && preview.getBitmap() != null) {
|
||||
mWidgetPreviewLoader.recycleBitmap(mInfo, preview.getBitmap());
|
||||
}
|
||||
image.setImageDrawable(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (mActiveRequest != null) {
|
||||
mActiveRequest.cancel(recycleImage);
|
||||
mActiveRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info,
|
||||
@@ -161,7 +159,8 @@ public class PagedViewWidget extends LinearLayout {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
void applyPreview(FastBitmapDrawable preview, int index) {
|
||||
public void applyPreview(Bitmap bitmap) {
|
||||
FastBitmapDrawable preview = new FastBitmapDrawable(bitmap);
|
||||
final PagedViewWidgetImageView image =
|
||||
(PagedViewWidgetImageView) findViewById(R.id.widget_preview);
|
||||
if (preview != null) {
|
||||
@@ -259,4 +258,38 @@ public class PagedViewWidget extends LinearLayout {
|
||||
// we just always mark the touch event as handled.
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ensurePreview() {
|
||||
if (mActiveRequest != null) {
|
||||
return;
|
||||
}
|
||||
int[] size = getPreviewSize();
|
||||
|
||||
if (size[0] <= 0 || size[1] <= 0) {
|
||||
addOnLayoutChangeListener(this);
|
||||
return;
|
||||
}
|
||||
Bitmap[] immediateResult = new Bitmap[1];
|
||||
mActiveRequest = mWidgetPreviewLoader.getPreview(mInfo, size[0], size[1], this,
|
||||
immediateResult);
|
||||
if (immediateResult[0] != null) {
|
||||
applyPreview(immediateResult[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
|
||||
int oldTop, int oldRight, int oldBottom) {
|
||||
removeOnLayoutChangeListener(this);
|
||||
ensurePreview();
|
||||
}
|
||||
|
||||
public int getActualItemWidth() {
|
||||
ItemInfo info = (ItemInfo) getTag();
|
||||
int[] size = getPreviewSize();
|
||||
int cellWidth = LauncherAppState.getInstance()
|
||||
.getDynamicGrid().getDeviceProfile().cellWidthPx;
|
||||
|
||||
return Math.min(size[0], info.spanX * cellWidth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
|
||||
@@ -555,6 +557,25 @@ public final class Utilities {
|
||||
return defaultWidgetForSearchPackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses the bitmap to a byte array for serialization.
|
||||
*/
|
||||
public static byte[] flattenBitmap(Bitmap bitmap) {
|
||||
// Try go guesstimate how much space the icon will take when serialized
|
||||
// to avoid unnecessary allocations/copies during the write.
|
||||
int size = bitmap.getWidth() * bitmap.getHeight() * 4;
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(size);
|
||||
try {
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||
out.flush();
|
||||
out.close();
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Could not write bitmap");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final Comparator<ItemInfo> RANK_COMPARATOR = new Comparator<ItemInfo>() {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package com.android.launcher3;
|
||||
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteCantOpenDatabaseException;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteDiskIOException;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.database.sqlite.SQLiteReadOnlyDatabaseException;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.BitmapFactory;
|
||||
@@ -26,400 +24,332 @@ import android.graphics.RectF;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.util.LongSparseArray;
|
||||
|
||||
import com.android.launcher3.compat.AppWidgetManagerCompat;
|
||||
import com.android.launcher3.compat.UserHandleCompat;
|
||||
import com.android.launcher3.compat.UserManagerCompat;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class WidgetPreviewLoader {
|
||||
|
||||
private static final String TAG = "WidgetPreviewLoader";
|
||||
private static final String ANDROID_INCREMENTAL_VERSION_NAME_KEY = "android.incremental.version";
|
||||
|
||||
private static final float WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE = 0.25f;
|
||||
@Thunk static final HashSet<String> sInvalidPackages = new HashSet<String>();
|
||||
|
||||
private final HashMap<String, WeakReference<Bitmap>> mLoadedPreviews = new HashMap<>();
|
||||
private final ArrayList<SoftReference<Bitmap>> mUnusedBitmaps = new ArrayList<>();
|
||||
|
||||
private final int mAppIconSize;
|
||||
private final int mCellWidth;
|
||||
private final HashMap<String, long[]> mPackageVersions = new HashMap<>();
|
||||
private final HashMap<WidgetCacheKey, WeakReference<Bitmap>> mLoadedPreviews = new HashMap<>();
|
||||
private Set<Bitmap> mUnusedBitmaps = Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>());
|
||||
|
||||
private final Context mContext;
|
||||
private final IconCache mIconCache;
|
||||
private final UserManagerCompat mUserManager;
|
||||
private final AppWidgetManagerCompat mManager;
|
||||
|
||||
private int mPreviewBitmapWidth;
|
||||
private int mPreviewBitmapHeight;
|
||||
private String mSize;
|
||||
|
||||
private String mCachedSelectQuery;
|
||||
private CacheDb mDb;
|
||||
private final CacheDb mDb;
|
||||
|
||||
private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
|
||||
|
||||
public WidgetPreviewLoader(Context context) {
|
||||
LauncherAppState app = LauncherAppState.getInstance();
|
||||
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
|
||||
|
||||
public WidgetPreviewLoader(Context context, IconCache iconCache) {
|
||||
mContext = context;
|
||||
mAppIconSize = grid.iconSizePx;
|
||||
mCellWidth = grid.cellWidthPx;
|
||||
|
||||
mIconCache = app.getIconCache();
|
||||
mIconCache = iconCache;
|
||||
mManager = AppWidgetManagerCompat.getInstance(context);
|
||||
mDb = app.getWidgetPreviewCacheDb();
|
||||
|
||||
SharedPreferences sp = context.getSharedPreferences(
|
||||
LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
|
||||
final String lastVersionName = sp.getString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, null);
|
||||
final String versionName = android.os.Build.VERSION.INCREMENTAL;
|
||||
final boolean isLollipopOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
|
||||
if (!versionName.equals(lastVersionName)) {
|
||||
try {
|
||||
// clear all the previews whenever the system version changes, to ensure that
|
||||
// previews are up-to-date for any apps that might have been updated with the system
|
||||
clearDb();
|
||||
} catch (SQLiteReadOnlyDatabaseException e) {
|
||||
if (isLollipopOrGreater) {
|
||||
// Workaround for Bug. 18554839, if we fail to clear the db due to the read-only
|
||||
// issue, then ignore this error and leave the old previews
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
editor.putString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, versionName);
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
mUserManager = UserManagerCompat.getInstance(context);
|
||||
mDb = new CacheDb(context);
|
||||
}
|
||||
|
||||
public void recreateDb() {
|
||||
LauncherAppState app = LauncherAppState.getInstance();
|
||||
app.recreateWidgetPreviewDb();
|
||||
mDb = app.getWidgetPreviewCacheDb();
|
||||
}
|
||||
/**
|
||||
* Generates the widget preview on {@link AsyncTask#THREAD_POOL_EXECUTOR}. Must be
|
||||
* called on UI thread
|
||||
*
|
||||
* @param o either {@link LauncherAppWidgetProviderInfo} or {@link ResolveInfo}
|
||||
* @param immediateResult A bitmap array of size 1. If the result is already cached, it is
|
||||
* set to the final result.
|
||||
* @return a request id which can be used to cancel the request.
|
||||
*/
|
||||
public PreviewLoadRequest getPreview(final Object o, int previewWidth, int previewHeight,
|
||||
PagedViewWidget caller, Bitmap[] immediateResult) {
|
||||
String size = previewWidth + "x" + previewHeight;
|
||||
WidgetCacheKey key = getObjectKey(o, size);
|
||||
|
||||
public void setPreviewSize(int previewWidth, int previewHeight) {
|
||||
mPreviewBitmapWidth = previewWidth;
|
||||
mPreviewBitmapHeight = previewHeight;
|
||||
mSize = previewWidth + "x" + previewHeight;
|
||||
}
|
||||
|
||||
public Bitmap getPreview(final Object o) {
|
||||
final String name = getObjectName(o);
|
||||
final String packageName = getObjectPackage(o);
|
||||
// check if the package is valid
|
||||
synchronized(sInvalidPackages) {
|
||||
boolean packageValid = !sInvalidPackages.contains(packageName);
|
||||
if (!packageValid) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
synchronized(mLoadedPreviews) {
|
||||
// check if it exists in our existing cache
|
||||
if (mLoadedPreviews.containsKey(name)) {
|
||||
WeakReference<Bitmap> bitmapReference = mLoadedPreviews.get(name);
|
||||
Bitmap bitmap = bitmapReference.get();
|
||||
if (bitmap != null) {
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bitmap unusedBitmap = null;
|
||||
synchronized(mUnusedBitmaps) {
|
||||
// not in cache; we need to load it from the db
|
||||
while (unusedBitmap == null && mUnusedBitmaps.size() > 0) {
|
||||
Bitmap candidate = mUnusedBitmaps.remove(0).get();
|
||||
if (candidate != null && candidate.isMutable() &&
|
||||
candidate.getWidth() == mPreviewBitmapWidth &&
|
||||
candidate.getHeight() == mPreviewBitmapHeight) {
|
||||
unusedBitmap = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unusedBitmap == null) {
|
||||
unusedBitmap = Bitmap.createBitmap(mPreviewBitmapWidth, mPreviewBitmapHeight,
|
||||
Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
Bitmap preview = readFromDb(name, unusedBitmap);
|
||||
|
||||
if (preview != null) {
|
||||
synchronized(mLoadedPreviews) {
|
||||
mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview));
|
||||
}
|
||||
return preview;
|
||||
} else {
|
||||
// it's not in the db... we need to generate it
|
||||
final Bitmap generatedPreview = generatePreview(o, unusedBitmap);
|
||||
preview = generatedPreview;
|
||||
if (preview != unusedBitmap) {
|
||||
throw new RuntimeException("generatePreview is not recycling the bitmap " + o);
|
||||
}
|
||||
|
||||
synchronized(mLoadedPreviews) {
|
||||
mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview));
|
||||
}
|
||||
|
||||
// write to db on a thread pool... this can be done lazily and improves the performance
|
||||
// of the first time widget previews are loaded
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
public Void doInBackground(Void ... args) {
|
||||
writeToDb(o, generatedPreview);
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
|
||||
|
||||
return preview;
|
||||
}
|
||||
}
|
||||
|
||||
public void recycleBitmap(Object o, Bitmap bitmapToRecycle) {
|
||||
String name = getObjectName(o);
|
||||
// Check if we have the preview loaded or not.
|
||||
synchronized (mLoadedPreviews) {
|
||||
if (mLoadedPreviews.containsKey(name)) {
|
||||
Bitmap b = mLoadedPreviews.get(name).get();
|
||||
if (b == bitmapToRecycle) {
|
||||
mLoadedPreviews.remove(name);
|
||||
if (bitmapToRecycle.isMutable()) {
|
||||
synchronized (mUnusedBitmaps) {
|
||||
mUnusedBitmaps.add(new SoftReference<Bitmap>(b));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Bitmap passed in doesn't match up");
|
||||
}
|
||||
WeakReference<Bitmap> ref = mLoadedPreviews.get(key);
|
||||
if (ref != null && ref.get() != null) {
|
||||
immediateResult[0] = ref.get();
|
||||
return new PreviewLoadRequest(null, key);
|
||||
}
|
||||
}
|
||||
|
||||
PreviewLoadTask task = new PreviewLoadTask(key, o, previewWidth, previewHeight, caller);
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
return new PreviewLoadRequest(task, key);
|
||||
}
|
||||
|
||||
static class CacheDb extends SQLiteOpenHelper {
|
||||
final static int DB_VERSION = 2;
|
||||
final static String TABLE_NAME = "shortcut_and_widget_previews";
|
||||
final static String COLUMN_NAME = "name";
|
||||
final static String COLUMN_SIZE = "size";
|
||||
final static String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
|
||||
Context mContext;
|
||||
/**
|
||||
* The DB holds the generated previews for various components. Previews can also have different
|
||||
* sizes (landscape vs portrait).
|
||||
*/
|
||||
private static class CacheDb extends SQLiteOpenHelper {
|
||||
private static final int DB_VERSION = 3;
|
||||
|
||||
private static final String TABLE_NAME = "shortcut_and_widget_previews";
|
||||
private static final String COLUMN_COMPONENT = "componentName";
|
||||
private static final String COLUMN_USER = "profileId";
|
||||
private static final String COLUMN_SIZE = "size";
|
||||
private static final String COLUMN_PACKAGE = "packageName";
|
||||
private static final String COLUMN_LAST_UPDATED = "lastUpdated";
|
||||
private static final String COLUMN_VERSION = "version";
|
||||
private static final String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
|
||||
|
||||
public CacheDb(Context context) {
|
||||
super(context, new File(context.getCacheDir(),
|
||||
LauncherFiles.WIDGET_PREVIEWS_DB).getPath(), null, DB_VERSION);
|
||||
// Store the context for later use
|
||||
mContext = context;
|
||||
super(context, LauncherFiles.WIDGET_PREVIEWS_DB, null, DB_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase database) {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
|
||||
COLUMN_NAME + " TEXT NOT NULL, " +
|
||||
COLUMN_COMPONENT + " TEXT NOT NULL, " +
|
||||
COLUMN_USER + " INTEGER NOT NULL, " +
|
||||
COLUMN_SIZE + " TEXT NOT NULL, " +
|
||||
COLUMN_PREVIEW_BITMAP + " BLOB NOT NULL, " +
|
||||
"PRIMARY KEY (" + COLUMN_NAME + ", " + COLUMN_SIZE + ") " +
|
||||
COLUMN_PACKAGE + " TEXT NOT NULL, " +
|
||||
COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " +
|
||||
COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
|
||||
COLUMN_PREVIEW_BITMAP + " BLOB, " +
|
||||
"PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ", " + COLUMN_SIZE + ") " +
|
||||
");");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion != newVersion) {
|
||||
// Delete all the records; they'll be repopulated as this is a cache
|
||||
db.execSQL("DELETE FROM " + TABLE_NAME);
|
||||
clearDB(db);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion != newVersion) {
|
||||
clearDB(db);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDB(SQLiteDatabase db) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||
onCreate(db);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String WIDGET_PREFIX = "Widget:";
|
||||
private static final String SHORTCUT_PREFIX = "Shortcut:";
|
||||
|
||||
private static String getObjectName(Object o) {
|
||||
private WidgetCacheKey getObjectKey(Object o, String size) {
|
||||
// should cache the string builder
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String output;
|
||||
if (o instanceof AppWidgetProviderInfo) {
|
||||
sb.append(WIDGET_PREFIX);
|
||||
sb.append(((AppWidgetProviderInfo) o).toString());
|
||||
output = sb.toString();
|
||||
sb.setLength(0);
|
||||
} else {
|
||||
sb.append(SHORTCUT_PREFIX);
|
||||
ResolveInfo info = (ResolveInfo) o;
|
||||
sb.append(new ComponentName(info.activityInfo.packageName,
|
||||
info.activityInfo.name).flattenToString());
|
||||
output = sb.toString();
|
||||
sb.setLength(0);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
private String getObjectPackage(Object o) {
|
||||
if (o instanceof AppWidgetProviderInfo) {
|
||||
return ((AppWidgetProviderInfo) o).provider.getPackageName();
|
||||
if (o instanceof LauncherAppWidgetProviderInfo) {
|
||||
LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) o;
|
||||
return new WidgetCacheKey(info.provider, mManager.getUser(info), size);
|
||||
} else {
|
||||
ResolveInfo info = (ResolveInfo) o;
|
||||
return info.activityInfo.packageName;
|
||||
return new WidgetCacheKey(
|
||||
new ComponentName(info.activityInfo.packageName, info.activityInfo.name),
|
||||
UserHandleCompat.myUserHandle(), size);
|
||||
}
|
||||
}
|
||||
|
||||
@Thunk void writeToDb(Object o, Bitmap preview) {
|
||||
String name = getObjectName(o);
|
||||
SQLiteDatabase db = mDb.getWritableDatabase();
|
||||
@Thunk void writeToDb(WidgetCacheKey key, long[] versions, Bitmap preview) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(CacheDb.COLUMN_COMPONENT, key.componentName.flattenToShortString());
|
||||
values.put(CacheDb.COLUMN_USER, mUserManager.getSerialNumberForUser(key.user));
|
||||
values.put(CacheDb.COLUMN_SIZE, key.size);
|
||||
values.put(CacheDb.COLUMN_PACKAGE, key.componentName.getPackageName());
|
||||
values.put(CacheDb.COLUMN_VERSION, versions[0]);
|
||||
values.put(CacheDb.COLUMN_LAST_UPDATED, versions[1]);
|
||||
values.put(CacheDb.COLUMN_PREVIEW_BITMAP, Utilities.flattenBitmap(preview));
|
||||
|
||||
values.put(CacheDb.COLUMN_NAME, name);
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
preview.compress(Bitmap.CompressFormat.PNG, 100, stream);
|
||||
values.put(CacheDb.COLUMN_PREVIEW_BITMAP, stream.toByteArray());
|
||||
values.put(CacheDb.COLUMN_SIZE, mSize);
|
||||
try {
|
||||
db.insert(CacheDb.TABLE_NAME, null, values);
|
||||
} catch (SQLiteDiskIOException e) {
|
||||
recreateDb();
|
||||
} catch (SQLiteCantOpenDatabaseException e) {
|
||||
dumpOpenFiles();
|
||||
throw e;
|
||||
mDb.getWritableDatabase().insertWithOnConflict(CacheDb.TABLE_NAME, null, values,
|
||||
SQLiteDatabase.CONFLICT_REPLACE);
|
||||
} catch (SQLException e) {
|
||||
Log.e(TAG, "Error saving image to DB", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDb() {
|
||||
SQLiteDatabase db = mDb.getWritableDatabase();
|
||||
// Delete everything
|
||||
public void removePackage(String packageName, UserHandleCompat user) {
|
||||
removePackage(packageName, user, mUserManager.getSerialNumberForUser(user));
|
||||
}
|
||||
|
||||
private void removePackage(String packageName, UserHandleCompat user, long userSerial) {
|
||||
synchronized(mPackageVersions) {
|
||||
mPackageVersions.remove(packageName);
|
||||
}
|
||||
|
||||
synchronized (mLoadedPreviews) {
|
||||
Set<WidgetCacheKey> keysToRemove = new HashSet<>();
|
||||
for (WidgetCacheKey key : mLoadedPreviews.keySet()) {
|
||||
if (key.componentName.getPackageName().equals(packageName) && key.user.equals(user)) {
|
||||
keysToRemove.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (WidgetCacheKey key : keysToRemove) {
|
||||
WeakReference<Bitmap> req = mLoadedPreviews.remove(key);
|
||||
if (req != null && req.get() != null) {
|
||||
mUnusedBitmaps.add(req.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
db.delete(CacheDb.TABLE_NAME, null, null);
|
||||
} catch (SQLiteDiskIOException e) {
|
||||
} catch (SQLiteCantOpenDatabaseException e) {
|
||||
dumpOpenFiles();
|
||||
throw e;
|
||||
mDb.getWritableDatabase().delete(CacheDb.TABLE_NAME,
|
||||
CacheDb.COLUMN_PACKAGE + " = ? AND " + CacheDb.COLUMN_USER + " = ?",
|
||||
new String[] {packageName, Long.toString(userSerial)});
|
||||
} catch (SQLException e) {
|
||||
Log.e(TAG, "Unable to delete items from DB", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void removePackageFromDb(final CacheDb cacheDb, final String packageName) {
|
||||
synchronized(sInvalidPackages) {
|
||||
sInvalidPackages.add(packageName);
|
||||
/**
|
||||
* Updates the persistent DB:
|
||||
* 1. Any preview generated for an old package version is removed
|
||||
* 2. Any preview for an absent package is removed
|
||||
* This ensures that we remove entries for packages which changed while the launcher was dead.
|
||||
*/
|
||||
public void removeObsoletePreviews() {
|
||||
LongSparseArray<UserHandleCompat> userIdCache = new LongSparseArray<>();
|
||||
LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
|
||||
|
||||
for (Object obj : LauncherModel.getSortedWidgetsAndShortcuts(mContext, false)) {
|
||||
final UserHandleCompat user;
|
||||
final String pkg;
|
||||
if (obj instanceof ResolveInfo) {
|
||||
user = UserHandleCompat.myUserHandle();
|
||||
pkg = ((ResolveInfo) obj).activityInfo.packageName;
|
||||
} else {
|
||||
LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) obj;
|
||||
user = mManager.getUser(info);
|
||||
pkg = info.provider.getPackageName();
|
||||
}
|
||||
|
||||
int userIdIndex = userIdCache.indexOfValue(user);
|
||||
final long userId;
|
||||
if (userIdIndex < 0) {
|
||||
userId = mUserManager.getSerialNumberForUser(user);
|
||||
userIdCache.put(userId, user);
|
||||
} else {
|
||||
userId = userIdCache.keyAt(userIdIndex);
|
||||
}
|
||||
|
||||
HashSet<String> packages = validPackages.get(userId);
|
||||
if (packages == null) {
|
||||
packages = new HashSet<>();
|
||||
validPackages.put(userId, packages);
|
||||
}
|
||||
packages.add(pkg);
|
||||
}
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
public Void doInBackground(Void ... args) {
|
||||
SQLiteDatabase db = cacheDb.getWritableDatabase();
|
||||
|
||||
LongSparseArray<HashSet<String>> packagesToDelete = new LongSparseArray<>();
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = mDb.getReadableDatabase().query(CacheDb.TABLE_NAME,
|
||||
new String[] {CacheDb.COLUMN_USER, CacheDb.COLUMN_PACKAGE,
|
||||
CacheDb.COLUMN_LAST_UPDATED, CacheDb.COLUMN_VERSION},
|
||||
null, null, null, null, null);
|
||||
while (c.moveToNext()) {
|
||||
long userId = c.getLong(0);
|
||||
String pkg = c.getString(1);
|
||||
long lastUpdated = c.getLong(2);
|
||||
long version = c.getLong(3);
|
||||
|
||||
HashSet<String> packages = validPackages.get(userId);
|
||||
if (packages != null && packages.contains(pkg)) {
|
||||
long[] versions = getPackageVersion(pkg);
|
||||
if (versions[0] == version && versions[1] == lastUpdated) {
|
||||
// Every thing checks out
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to delete this package.
|
||||
packages = packagesToDelete.get(userId);
|
||||
if (packages == null) {
|
||||
packages = new HashSet<>();
|
||||
packagesToDelete.put(userId, packages);
|
||||
}
|
||||
packages.add(pkg);
|
||||
}
|
||||
|
||||
for (int i = 0; i < packagesToDelete.size(); i++) {
|
||||
long userId = packagesToDelete.keyAt(i);
|
||||
UserHandleCompat user = mUserManager.getUserForSerialNumber(userId);
|
||||
for (String pkg : packagesToDelete.valueAt(i)) {
|
||||
removePackage(pkg, user, userId);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
Log.e(TAG, "Error updatating widget previews", e);
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap readFromDb(WidgetCacheKey key, Bitmap recycle) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = mDb.getReadableDatabase().query(
|
||||
CacheDb.TABLE_NAME,
|
||||
new String[] { CacheDb.COLUMN_PREVIEW_BITMAP },
|
||||
CacheDb.COLUMN_COMPONENT + " = ? AND " + CacheDb.COLUMN_USER + " = ? AND " + CacheDb.COLUMN_SIZE + " = ?",
|
||||
new String[] {
|
||||
key.componentName.flattenToString(),
|
||||
Long.toString(mUserManager.getSerialNumberForUser(key.user)),
|
||||
key.size
|
||||
},
|
||||
null, null, null);
|
||||
if (cursor.moveToNext()) {
|
||||
byte[] blob = cursor.getBlob(0);
|
||||
BitmapFactory.Options opts = new BitmapFactory.Options();
|
||||
opts.inBitmap = recycle;
|
||||
try {
|
||||
db.delete(CacheDb.TABLE_NAME,
|
||||
CacheDb.COLUMN_NAME + " LIKE ? OR " +
|
||||
CacheDb.COLUMN_NAME + " LIKE ?", // SELECT query
|
||||
new String[] {
|
||||
WIDGET_PREFIX + packageName + "/%",
|
||||
SHORTCUT_PREFIX + packageName + "/%"
|
||||
} // args to SELECT query
|
||||
);
|
||||
} catch (SQLiteDiskIOException e) {
|
||||
} catch (SQLiteCantOpenDatabaseException e) {
|
||||
dumpOpenFiles();
|
||||
throw e;
|
||||
return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
synchronized(sInvalidPackages) {
|
||||
sInvalidPackages.remove(packageName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
|
||||
} catch (SQLException e) {
|
||||
Log.w(TAG, "Error loading preview from DB", e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void removeItemFromDb(final CacheDb cacheDb, final String objectName) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
public Void doInBackground(Void ... args) {
|
||||
SQLiteDatabase db = cacheDb.getWritableDatabase();
|
||||
try {
|
||||
db.delete(CacheDb.TABLE_NAME,
|
||||
CacheDb.COLUMN_NAME + " = ? ", // SELECT query
|
||||
new String[] { objectName }); // args to SELECT query
|
||||
} catch (SQLiteDiskIOException e) {
|
||||
} catch (SQLiteCantOpenDatabaseException e) {
|
||||
dumpOpenFiles();
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
|
||||
}
|
||||
|
||||
private Bitmap readFromDb(String name, Bitmap b) {
|
||||
if (mCachedSelectQuery == null) {
|
||||
mCachedSelectQuery = CacheDb.COLUMN_NAME + " = ? AND " +
|
||||
CacheDb.COLUMN_SIZE + " = ?";
|
||||
}
|
||||
SQLiteDatabase db = mDb.getReadableDatabase();
|
||||
Cursor result;
|
||||
try {
|
||||
result = db.query(CacheDb.TABLE_NAME,
|
||||
new String[] { CacheDb.COLUMN_PREVIEW_BITMAP }, // cols to return
|
||||
mCachedSelectQuery, // select query
|
||||
new String[] { name, mSize }, // args to select query
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
} catch (SQLiteDiskIOException e) {
|
||||
recreateDb();
|
||||
return null;
|
||||
} catch (SQLiteCantOpenDatabaseException e) {
|
||||
dumpOpenFiles();
|
||||
throw e;
|
||||
}
|
||||
if (result.getCount() > 0) {
|
||||
result.moveToFirst();
|
||||
byte[] blob = result.getBlob(0);
|
||||
result.close();
|
||||
final BitmapFactory.Options opts = new BitmapFactory.Options();
|
||||
opts.inBitmap = b;
|
||||
opts.inSampleSize = 1;
|
||||
try {
|
||||
return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
|
||||
} catch (IllegalArgumentException e) {
|
||||
removeItemFromDb(mDb, name);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
result.close();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap generatePreview(Object info, Bitmap preview) {
|
||||
if (preview != null &&
|
||||
(preview.getWidth() != mPreviewBitmapWidth ||
|
||||
preview.getHeight() != mPreviewBitmapHeight)) {
|
||||
throw new RuntimeException("Improperly sized bitmap passed as argument");
|
||||
}
|
||||
private Bitmap generatePreview(Object info, Bitmap recycle, int previewWidth, int previewHeight) {
|
||||
if (info instanceof LauncherAppWidgetProviderInfo) {
|
||||
return generateWidgetPreview((LauncherAppWidgetProviderInfo) info, preview);
|
||||
return generateWidgetPreview((LauncherAppWidgetProviderInfo) info, previewWidth, recycle);
|
||||
} else {
|
||||
return generateShortcutPreview(
|
||||
(ResolveInfo) info, mPreviewBitmapWidth, mPreviewBitmapHeight, preview);
|
||||
(ResolveInfo) info, previewWidth, previewHeight, recycle);
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info, Bitmap preview) {
|
||||
int maxWidth = maxWidthForWidgetPreview(info.spanX);
|
||||
public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info,
|
||||
int previewWidth, Bitmap preview) {
|
||||
int maxWidth = Math.min(previewWidth, info.spanX
|
||||
* LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile().cellWidthPx);
|
||||
return generateWidgetPreview(info, maxWidth, preview, null);
|
||||
}
|
||||
|
||||
public int maxWidthForWidgetPreview(int spanX) {
|
||||
return Math.min(mPreviewBitmapWidth, spanX * mCellWidth);
|
||||
}
|
||||
|
||||
public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info,
|
||||
int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) {
|
||||
// Load the preview image if possible
|
||||
@@ -488,6 +418,8 @@ public class WidgetPreviewLoader {
|
||||
} else {
|
||||
final Paint p = new Paint();
|
||||
p.setFilterBitmap(true);
|
||||
int appIconSize = LauncherAppState.getInstance().getDynamicGrid()
|
||||
.getDeviceProfile().iconSizePx;
|
||||
|
||||
// draw the spanX x spanY tiles
|
||||
final Rect src = new Rect(0, 0, tileBitmap.getWidth(), tileBitmap.getHeight());
|
||||
@@ -507,18 +439,18 @@ public class WidgetPreviewLoader {
|
||||
|
||||
// Draw the icon in the top left corner
|
||||
// TODO: use top right for RTL
|
||||
int minOffset = (int) (mAppIconSize * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE);
|
||||
int minOffset = (int) (appIconSize * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE);
|
||||
int smallestSide = Math.min(previewWidth, previewHeight);
|
||||
float iconScale = Math.min((float) smallestSide / (mAppIconSize + 2 * minOffset), scale);
|
||||
float iconScale = Math.min((float) smallestSide / (appIconSize + 2 * minOffset), scale);
|
||||
|
||||
try {
|
||||
Drawable icon = mutateOnMainThread(mManager.loadIcon(info, mIconCache));
|
||||
if (icon != null) {
|
||||
int hoffset = (int) ((tileW - mAppIconSize * iconScale) / 2) + x;
|
||||
int yoffset = (int) ((tileH - mAppIconSize * iconScale) / 2);
|
||||
int hoffset = (int) ((tileW - appIconSize * iconScale) / 2) + x;
|
||||
int yoffset = (int) ((tileH - appIconSize * iconScale) / 2);
|
||||
icon.setBounds(hoffset, yoffset,
|
||||
hoffset + (int) (mAppIconSize * iconScale),
|
||||
yoffset + (int) (mAppIconSize * iconScale));
|
||||
hoffset + (int) (appIconSize * iconScale),
|
||||
yoffset + (int) (appIconSize * iconScale));
|
||||
icon.draw(c);
|
||||
}
|
||||
} catch (Resources.NotFoundException e) { }
|
||||
@@ -561,9 +493,11 @@ public class WidgetPreviewLoader {
|
||||
|
||||
// Draw the final icon at top left corner.
|
||||
// TODO: use top right for RTL
|
||||
int appIconSize = LauncherAppState.getInstance().getDynamicGrid()
|
||||
.getDeviceProfile().iconSizePx;
|
||||
icon.setAlpha(255);
|
||||
icon.setColorFilter(null);
|
||||
icon.setBounds(0, 0, mAppIconSize, mAppIconSize);
|
||||
icon.setBounds(0, 0, appIconSize, appIconSize);
|
||||
icon.draw(c);
|
||||
|
||||
c.setBitmap(null);
|
||||
@@ -586,82 +520,144 @@ public class WidgetPreviewLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private static final int MAX_OPEN_FILES = 1024;
|
||||
private static final int SAMPLE_RATE = 23;
|
||||
/**
|
||||
* Dumps all files that are open in this process without allocating a file descriptor.
|
||||
* @return an array of containing versionCode and lastUpdatedTime for the package.
|
||||
*/
|
||||
@Thunk static void dumpOpenFiles() {
|
||||
try {
|
||||
Log.i(TAG, "DUMP OF OPEN FILES (sample rate: 1 every " + SAMPLE_RATE + "):");
|
||||
final String TYPE_APK = "apk";
|
||||
final String TYPE_JAR = "jar";
|
||||
final String TYPE_PIPE = "pipe";
|
||||
final String TYPE_SOCKET = "socket";
|
||||
final String TYPE_DB = "db";
|
||||
final String TYPE_ANON_INODE = "anon_inode";
|
||||
final String TYPE_DEV = "dev";
|
||||
final String TYPE_NON_FS = "non-fs";
|
||||
final String TYPE_OTHER = "other";
|
||||
List<String> types = Arrays.asList(TYPE_APK, TYPE_JAR, TYPE_PIPE, TYPE_SOCKET, TYPE_DB,
|
||||
TYPE_ANON_INODE, TYPE_DEV, TYPE_NON_FS, TYPE_OTHER);
|
||||
int[] count = new int[types.size()];
|
||||
int[] duplicates = new int[types.size()];
|
||||
HashSet<String> files = new HashSet<String>();
|
||||
int total = 0;
|
||||
for (int i = 0; i < MAX_OPEN_FILES; i++) {
|
||||
// This is a gigantic hack but unfortunately the only way to resolve an fd
|
||||
// to a file name. Note that we have to loop over all possible fds because
|
||||
// reading the directory would require allocating a new fd. The kernel is
|
||||
// currently implemented such that no fd is larger then the current rlimit,
|
||||
// which is why it's safe to loop over them in such a way.
|
||||
String fd = "/proc/self/fd/" + i;
|
||||
private long[] getPackageVersion(String packageName) {
|
||||
synchronized (mPackageVersions) {
|
||||
long[] versions = mPackageVersions.get(packageName);
|
||||
if (versions == null) {
|
||||
versions = new long[2];
|
||||
try {
|
||||
// getCanonicalPath() uses readlink behind the scene which doesn't require
|
||||
// a file descriptor.
|
||||
String resolved = new File(fd).getCanonicalPath();
|
||||
int type = types.indexOf(TYPE_OTHER);
|
||||
if (resolved.startsWith("/dev/")) {
|
||||
type = types.indexOf(TYPE_DEV);
|
||||
} else if (resolved.endsWith(".apk")) {
|
||||
type = types.indexOf(TYPE_APK);
|
||||
} else if (resolved.endsWith(".jar")) {
|
||||
type = types.indexOf(TYPE_JAR);
|
||||
} else if (resolved.contains("/fd/pipe:")) {
|
||||
type = types.indexOf(TYPE_PIPE);
|
||||
} else if (resolved.contains("/fd/socket:")) {
|
||||
type = types.indexOf(TYPE_SOCKET);
|
||||
} else if (resolved.contains("/fd/anon_inode:")) {
|
||||
type = types.indexOf(TYPE_ANON_INODE);
|
||||
} else if (resolved.endsWith(".db") || resolved.contains("/databases/")) {
|
||||
type = types.indexOf(TYPE_DB);
|
||||
} else if (resolved.startsWith("/proc/") && resolved.contains("/fd/")) {
|
||||
// Those are the files that don't point anywhere on the file system.
|
||||
// getCanonicalPath() wrongly interprets these as relative symlinks and
|
||||
// resolves them within /proc/<pid>/fd/.
|
||||
type = types.indexOf(TYPE_NON_FS);
|
||||
PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0);
|
||||
versions[0] = info.versionCode;
|
||||
versions[1] = info.lastUpdateTime;
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.e(TAG, "PackageInfo not found", e);
|
||||
}
|
||||
mPackageVersions.put(packageName, versions);
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A request Id which can be used by the client to cancel any request.
|
||||
*/
|
||||
public class PreviewLoadRequest {
|
||||
|
||||
private final PreviewLoadTask mTask;
|
||||
private final WidgetCacheKey mKey;
|
||||
|
||||
public PreviewLoadRequest(PreviewLoadTask task, WidgetCacheKey key) {
|
||||
mTask = task;
|
||||
mKey = key;
|
||||
}
|
||||
|
||||
public void cancel(boolean recycleImage) {
|
||||
if (mTask != null) {
|
||||
mTask.cancel(true);
|
||||
}
|
||||
|
||||
if (recycleImage) {
|
||||
synchronized(mLoadedPreviews) {
|
||||
WeakReference<Bitmap> result = mLoadedPreviews.remove(mKey);
|
||||
if (result != null && result.get() != null) {
|
||||
mUnusedBitmaps.add(result.get());
|
||||
}
|
||||
count[type]++;
|
||||
total++;
|
||||
if (files.contains(resolved)) {
|
||||
duplicates[type]++;
|
||||
}
|
||||
files.add(resolved);
|
||||
if (total % SAMPLE_RATE == 0) {
|
||||
Log.i(TAG, " fd " + i + ": " + resolved
|
||||
+ " (" + types.get(type) + ")");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignoring exceptions for non-existing file descriptors.
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < types.size(); i++) {
|
||||
Log.i(TAG, String.format("Open %10s files: %4d total, %4d duplicates",
|
||||
types.get(i), count[i], duplicates[i]));
|
||||
}
|
||||
}
|
||||
|
||||
public class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap> {
|
||||
|
||||
private final WidgetCacheKey mKey;
|
||||
private final Object mInfo;
|
||||
private final int mPreviewHeight;
|
||||
private final int mPreviewWidth;
|
||||
private final PagedViewWidget mCaller;
|
||||
|
||||
PreviewLoadTask(WidgetCacheKey key, Object info, int previewWidth,
|
||||
int previewHeight, PagedViewWidget caller) {
|
||||
mKey = key;
|
||||
mInfo = info;
|
||||
mPreviewHeight = previewHeight;
|
||||
mPreviewWidth = previewWidth;
|
||||
mCaller = caller;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Bitmap doInBackground(Void... params) {
|
||||
Bitmap unusedBitmap = null;
|
||||
synchronized (mUnusedBitmaps) {
|
||||
// Check if we can use a bitmap
|
||||
for (Bitmap candidate : mUnusedBitmaps) {
|
||||
if (candidate != null && candidate.isMutable() &&
|
||||
candidate.getWidth() == mPreviewWidth &&
|
||||
candidate.getHeight() == mPreviewHeight) {
|
||||
unusedBitmap = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (unusedBitmap == null) {
|
||||
unusedBitmap = Bitmap.createBitmap(mPreviewWidth, mPreviewHeight, Config.ARGB_8888);
|
||||
} else {
|
||||
mUnusedBitmaps.remove(unusedBitmap);
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// Catch everything. This is called from an exception handler that we shouldn't upset.
|
||||
Log.e(TAG, "Unable to log open files.", t);
|
||||
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
Bitmap preview = readFromDb(mKey, unusedBitmap);
|
||||
if (!isCancelled() && preview == null) {
|
||||
// Fetch the version info before we generate the preview, so that, in-case the
|
||||
// app was updated while we are generating the preview, we use the old version info,
|
||||
// which would gets re-written next time.
|
||||
long[] versions = getPackageVersion(mKey.componentName.getPackageName());
|
||||
|
||||
// it's not in the db... we need to generate it
|
||||
preview = generatePreview(mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight);
|
||||
|
||||
if (!isCancelled()) {
|
||||
writeToDb(mKey, versions, preview);
|
||||
}
|
||||
}
|
||||
|
||||
return preview;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Bitmap result) {
|
||||
synchronized(mLoadedPreviews) {
|
||||
mLoadedPreviews.put(mKey, new WeakReference<Bitmap>(result));
|
||||
}
|
||||
|
||||
mCaller.applyPreview(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class WidgetCacheKey extends ComponentKey {
|
||||
|
||||
// TODO: remove dependency on size
|
||||
private final String size;
|
||||
|
||||
public WidgetCacheKey(ComponentName componentName, UserHandleCompat user, String size) {
|
||||
super(componentName, user);
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode() ^ size.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return super.equals(o) && ((WidgetCacheKey) o).size.equals(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,13 +366,12 @@ public class Workspace extends SmoothPagedView
|
||||
|
||||
// estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
|
||||
// dimension if unsuccessful
|
||||
public int[] estimateItemSize(int hSpan, int vSpan,
|
||||
ItemInfo itemInfo, boolean springLoaded) {
|
||||
public int[] estimateItemSize(ItemInfo itemInfo, boolean springLoaded) {
|
||||
int[] size = new int[2];
|
||||
if (getChildCount() > 0) {
|
||||
// Use the first non-custom page to estimate the child position
|
||||
CellLayout cl = (CellLayout) getChildAt(numCustomPages());
|
||||
Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
|
||||
Rect r = estimateItemPosition(cl, itemInfo, 0, 0, itemInfo.spanX, itemInfo.spanY);
|
||||
size[0] = r.width();
|
||||
size[1] = r.height();
|
||||
if (springLoaded) {
|
||||
@@ -2065,7 +2064,7 @@ public class Workspace extends SmoothPagedView
|
||||
}
|
||||
|
||||
public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
|
||||
int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
|
||||
int[] size = estimateItemSize(info, false);
|
||||
|
||||
// The outline is used to visualize where the item will land if dropped
|
||||
mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha);
|
||||
@@ -4032,8 +4031,7 @@ public class Workspace extends SmoothPagedView
|
||||
}
|
||||
|
||||
public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
|
||||
int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
|
||||
widgetInfo.spanY, widgetInfo, false);
|
||||
int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo, false);
|
||||
int visibility = layout.getVisibility();
|
||||
layout.setVisibility(VISIBLE);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user