From fd13c714f412dfa8130fa7a664cb1692a565c539 Mon Sep 17 00:00:00 2001 From: Chris Wren Date: Fri, 27 Sep 2013 15:45:19 -0400 Subject: backup widget assets. Bug: 10778984 Change-Id: I842a88c517401fa4eb3798fd03e0fbc3634e3144 --- src/com/android/launcher3/LauncherBackupAgent.java | 187 ++++++++++++++++++++- src/com/android/launcher3/WidgetPreviewLoader.java | 7 +- 2 files changed, 184 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/com/android/launcher3/LauncherBackupAgent.java b/src/com/android/launcher3/LauncherBackupAgent.java index cbef36b73..95c1b843f 100644 --- a/src/com/android/launcher3/LauncherBackupAgent.java +++ b/src/com/android/launcher3/LauncherBackupAgent.java @@ -28,6 +28,7 @@ import com.android.launcher3.backup.BackupProtos.Journal; import com.android.launcher3.backup.BackupProtos.Key; import com.android.launcher3.backup.BackupProtos.Resource; import com.android.launcher3.backup.BackupProtos.Screen; +import com.android.launcher3.backup.BackupProtos.Widget; import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; @@ -42,6 +43,7 @@ import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import android.os.ParcelFileDescriptor; import android.text.TextUtils; import android.util.Base64; @@ -67,12 +69,18 @@ import static android.graphics.Bitmap.CompressFormat.WEBP; public class LauncherBackupAgent extends BackupAgent { private static final String TAG = "LauncherBackupAgent"; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final int MAX_JOURNAL_SIZE = 1000000; + /** icons are large, dribble them out */ private static final int MAX_ICONS_PER_PASS = 10; + /** widgets contain previews, which are very large, dribble them out */ + private static final int MAX_WIDGETS_PER_PASS = 5; + + public static final int IMAGE_COMPRESSION_QUALITY = 75; + private static BackupManager sBackupManager; private static final String[] FAVORITE_PROJECTION = { @@ -177,6 +185,7 @@ public class LauncherBackupAgent extends BackupAgent { backupFavorites(in, data, out, keys); backupScreens(in, data, out, keys); backupIcons(in, data, out, keys); + backupWidgets(in, data, out, keys); out.key = keys.toArray(BackupProtos.Key.EMPTY_ARRAY); writeJournal(newState, out); @@ -229,6 +238,10 @@ public class LauncherBackupAgent extends BackupAgent { restoreIcon(key, buffer, dataSize, keys); break; + case Key.WIDGET: + restoreWidget(key, buffer, dataSize, keys); + break; + default: Log.w(TAG, "unknown restore entity type: " + key.type); break; @@ -393,9 +406,10 @@ public class LauncherBackupAgent extends BackupAgent { */ private void backupIcons(Journal in, BackupDataOutput data, Journal out, ArrayList keys) throws IOException { - // persist icons for new shortcuts since the last backup + // persist icons that haven't been persisted yet final ContentResolver cr = getContentResolver(); - final IconCache iconCache = new IconCache(this); + final LauncherAppState app = LauncherAppState.getInstance(); + final IconCache iconCache = app.getIconCache(); final int dpi = getResources().getDisplayMetrics().densityDpi; // read the old ID set @@ -441,7 +455,7 @@ public class LauncherBackupAgent extends BackupAgent { writeRowToBackup(key, blob, out, data); } } else { - if (DEBUG) Log.d(TAG, "scheduling another rtun for icon " + backupKey); + if (DEBUG) Log.d(TAG, "scheduling another run for icon " + backupKey); // too many icons for this pass, request another. dataChanged(this); } @@ -466,7 +480,7 @@ public class LauncherBackupAgent extends BackupAgent { /** * Read an icon from the stream. * - *

Keys arrive in any order, so shortcuts that use this screen may already exist. + *

Keys arrive in any order, so shortcuts that use this icon may already exist. * * @param key identifier for the row * @param buffer the serialized proto from the stream, may be larger than dataSize @@ -492,6 +506,115 @@ public class LauncherBackupAgent extends BackupAgent { } } + /** + * Write all the static widget resources we need to render placeholders + * for a package that is not installed. + * + * @param in notes from last backup + * @param data output stream for key/value pairs + * @param out notes about this backup + * @param keys keys to mark as clean in the notes for next backup + * @throws IOException + */ + private void backupWidgets(Journal in, BackupDataOutput data, Journal out, + ArrayList keys) throws IOException { + // persist static widget info that hasn't been persisted yet + final ContentResolver cr = getContentResolver(); + final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(this); + final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(this); + final LauncherAppState appState = LauncherAppState.getInstance(); + final IconCache iconCache = appState.getIconCache(); + final int dpi = getResources().getDisplayMetrics().densityDpi; + final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile(); + if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx); + + // read the old ID set + Set savedIds = getSavedIdsByType(Key.WIDGET, in); + if (DEBUG) Log.d(TAG, "widgets savedIds.size()=" + savedIds.size()); + + int startRows = out.rows; + if (DEBUG) Log.d(TAG, "starting here: " + startRows); + String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPWIDGET; + Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION, + where, null, null); + Set currentIds = new HashSet(cursor.getCount()); + try { + cursor.moveToPosition(-1); + while(cursor.moveToNext()) { + 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; + if (provider != null) { + key = getKey(Key.WIDGET, providerName); + backupKey = keyToBackupKey(key); + currentIds.add(backupKey); + } else { + Log.w(TAG, "empty intent on appwidget: " + id); + } + if (savedIds.contains(backupKey)) { + if (DEBUG) Log.d(TAG, "already saved widget " + backupKey); + + // remember that we already backed this up previously + keys.add(key); + } else if (backupKey != null) { + if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows); + if ((out.rows - startRows) < MAX_WIDGETS_PER_PASS) { + if (DEBUG) Log.d(TAG, "saving widget " + backupKey); + previewLoader.setPreviewSize(spanX * profile.cellWidthPx, + spanY * profile.cellHeightPx, widgetSpacingLayout); + byte[] blob = packWidget(dpi, previewLoader, iconCache, provider); + writeRowToBackup(key, blob, out, data); + + } else { + if (DEBUG) Log.d(TAG, "scheduling another run for widget " + backupKey); + // too many widgets for this pass, request another. + dataChanged(this); + } + } + } + } finally { + cursor.close(); + } + if (DEBUG) Log.d(TAG, "widget currentIds.size()=" + currentIds.size()); + + // these IDs must have been deleted + savedIds.removeAll(currentIds); + out.rows += removeDeletedKeysFromBackup(savedIds, data); + } + + /** + * Read a widget from the stream. + * + *

Keys arrive in any order, so widgets that use this data may already exist. + * + * @param key identifier for the row + * @param buffer the serialized proto from the stream, may be larger than dataSize + * @param dataSize the size of the proto from the stream + * @param keys keys to mark as clean in the notes for next backup + */ + private void restoreWidget(Key key, byte[] buffer, int dataSize, ArrayList keys) { + Log.v(TAG, "unpacking widget " + key.id); + if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " + + Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP)); + try { + Widget widget = unpackWidget(buffer, 0, dataSize); + if (DEBUG) Log.d(TAG, "unpacked " + widget.provider); + if (widget.icon.data != null) { + Bitmap icon = BitmapFactory + .decodeByteArray(widget.icon.data, 0, widget.icon.data.length); + if (icon == null) { + Log.w(TAG, "failed to unpack widget icon for " + key.name); + } + } + } catch (InvalidProtocolBufferNanoException e) { + Log.w(TAG, "failed to decode proto", e); + } + } + /** create a new key, with an integer ID. * *

Keys contain their own checksum instead of using @@ -556,6 +679,8 @@ public class LauncherBackupAgent extends BackupAgent { return "screen"; case Key.ICON: return "icon"; + case Key.WIDGET: + return "widget"; default: return "anonymous"; } @@ -650,7 +775,7 @@ public class LauncherBackupAgent extends BackupAgent { Resource res = new Resource(); res.dpi = dpi; ByteArrayOutputStream os = new ByteArrayOutputStream(); - if (icon.compress(WEBP, 100, os)) { + if (icon.compress(WEBP, IMAGE_COMPRESSION_QUALITY, os)) { res.data = os.toByteArray(); } return writeCheckedBytes(res); @@ -664,6 +789,44 @@ public class LauncherBackupAgent extends BackupAgent { return res; } + /** Serialize a widget for persistence, including a checksum wrapper. */ + private byte[] packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache, + ComponentName provider) { + final AppWidgetProviderInfo info = findAppWidgetProviderInfo(provider); + Widget widget = new Widget(); + widget.provider = provider.flattenToShortString(); + widget.label = info.label; + widget.configure = info.configure != null; + if (info.icon != 0) { + widget.icon = new Resource(); + Drawable fullResIcon = iconCache.getFullResIcon(provider.getPackageName(), info.icon); + Bitmap icon = Utilities.createIconBitmap(fullResIcon, this); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + if (icon.compress(WEBP, IMAGE_COMPRESSION_QUALITY, os)) { + widget.icon.data = os.toByteArray(); + widget.icon.dpi = dpi; + } + } + if (info.previewImage != 0) { + widget.preview = new Resource(); + Bitmap preview = previewLoader.generateWidgetPreview(info, null); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + if (preview.compress(WEBP, IMAGE_COMPRESSION_QUALITY, os)) { + widget.preview.data = os.toByteArray(); + widget.preview.dpi = dpi; + } + } + return writeCheckedBytes(widget); + } + + /** Deserialize a widget from persistence, after verifying checksum wrapper. */ + private Widget unpackWidget(byte[] buffer, int offset, int dataSize) + throws InvalidProtocolBufferNanoException { + Widget widget = new Widget(); + MessageNano.mergeFrom(widget, readCheckedBytes(buffer, offset, dataSize)); + return widget; + } + /** * Read the old journal from the input file. * @@ -798,6 +961,18 @@ public class LauncherBackupAgent extends BackupAgent { return wrapper.payload; } + private AppWidgetProviderInfo findAppWidgetProviderInfo(ComponentName component) { + if (mWidgetMap == null) { + List widgets = + AppWidgetManager.getInstance(this).getInstalledProviders(); + mWidgetMap = new HashMap(widgets.size()); + for (AppWidgetProviderInfo info : widgets) { + mWidgetMap.put(info.provider, info); + } + } + return mWidgetMap.get(component); + } + private class KeyParsingException extends Throwable { private KeyParsingException(Throwable cause) { super(cause); diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index 956fd99cd..07b4f6f0a 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -105,7 +105,6 @@ public class WidgetPreviewLoader { private int mPreviewBitmapHeight; private String mSize; private Context mContext; - private Launcher mLauncher; private PackageManager mPackageManager; private PagedViewCellLayout mWidgetSpacingLayout; @@ -137,11 +136,11 @@ public class WidgetPreviewLoader { sInvalidPackages = new HashSet(); } - public WidgetPreviewLoader(Launcher launcher) { + public WidgetPreviewLoader(Context context) { LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); - mContext = mLauncher = launcher; + mContext = context; mPackageManager = mContext.getPackageManager(); mAppIconSize = grid.iconSizePx; mIconCache = app.getIconCache(); @@ -417,7 +416,7 @@ public class WidgetPreviewLoader { } public Bitmap generateWidgetPreview(AppWidgetProviderInfo info, Bitmap preview) { - int[] cellSpans = Launcher.getSpanForWidget(mLauncher, info); + int[] cellSpans = Launcher.getSpanForWidget(mContext, info); int maxWidth = maxWidthForWidgetPreview(cellSpans[0]); int maxHeight = maxHeightForWidgetPreview(cellSpans[1]); return generateWidgetPreview(info.provider, info.previewImage, info.icon, -- cgit v1.2.3