Merge "Rewrite: Widget preview loader logic" into ub-launcher3-burnaby

This commit is contained in:
Sunny Goyal
2015-03-21 01:19:25 +00:00
committed by Android (Google) Code Review
15 changed files with 535 additions and 1474 deletions
-9
View File
@@ -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);
}
/*
+2 -2
View File
@@ -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));
+1 -20
View File
@@ -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();
+5 -4
View File
@@ -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);
}
}
}
}
+58 -25
View File
@@ -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);
}
}
+21
View File
@@ -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
+388 -392
View File
@@ -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);
}
}
}
+4 -6
View File
@@ -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);