Merge "backup widget assets." into jb-ub-now-indigo-rose

This commit is contained in:
Chris Wren
2013-10-03 19:39:31 +00:00
committed by Android (Google) Code Review
3 changed files with 195 additions and 12 deletions
+11 -2
View File
@@ -24,6 +24,7 @@ message Key {
FAVORITE = 1;
SCREEN = 2;
ICON = 3;
WIDGET = 4;
}
required Type type = 1;
optional string name = 2; // keep this short
@@ -71,6 +72,14 @@ message Screen {
}
message Resource {
required int32 dpi = 2;
required bytes data = 3;
required int32 dpi = 1;
required bytes data = 2;
}
message Widget {
required string provider = 1;
optional string label = 2;
optional bool configure = 3;
optional Resource icon = 4;
optional Resource preview = 5;
}
@@ -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<Key> 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.
*
* <P>Keys arrive in any order, so shortcuts that use this screen may already exist.
* <P>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<Key> 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<String> 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<String> currentIds = new HashSet<String>(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.
*
* <P>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<Key> 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.
*
* <P> 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<AppWidgetProviderInfo> widgets =
AppWidgetManager.getInstance(this).getInstalledProviders();
mWidgetMap = new HashMap<ComponentName, AppWidgetProviderInfo>(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);
@@ -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<String>();
}
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,