summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--protos/backup.proto13
-rw-r--r--src/com/android/launcher3/LauncherBackupAgent.java187
-rw-r--r--src/com/android/launcher3/WidgetPreviewLoader.java7
3 files changed, 195 insertions, 12 deletions
diff --git a/protos/backup.proto b/protos/backup.proto
index f43f338e5..7ba293702 100644
--- a/protos/backup.proto
+++ b/protos/backup.proto
@@ -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;
}
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<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);
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<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,