backup launcher icons
Only allow a small number of icons to be backed up in any given pass. Also refactor common code out of favorite and screen backups. Bug: 10778984 Change-Id: I54bc769c1d1c1c9087ea4bc58f258bd15c167aea
This commit is contained in:
parent
044db335ab
commit
22e130d401
|
@ -23,7 +23,7 @@ message Key {
|
|||
enum Type {
|
||||
FAVORITE = 1;
|
||||
SCREEN = 2;
|
||||
IMAGE = 3;
|
||||
ICON = 3;
|
||||
}
|
||||
required Type type = 1;
|
||||
optional string name = 2; // keep this short
|
||||
|
|
|
@ -19,7 +19,6 @@ package com.android.launcher3;
|
|||
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
|
||||
import com.google.protobuf.nano.MessageNano;
|
||||
|
||||
import com.android.launcher3.LauncherSettings.ChangeLogColumns;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.LauncherSettings.WorkspaceScreens;
|
||||
import com.android.launcher3.backup.BackupProtos;
|
||||
|
@ -27,88 +26,110 @@ import com.android.launcher3.backup.BackupProtos.CheckedMessage;
|
|||
import com.android.launcher3.backup.BackupProtos.Favorite;
|
||||
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 android.app.backup.BackupAgent;
|
||||
import android.app.backup.BackupDataInput;
|
||||
import android.app.backup.BackupDataOutput;
|
||||
import android.app.backup.BackupManager;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.BaseColumns;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
import static android.graphics.Bitmap.CompressFormat.WEBP;
|
||||
|
||||
/**
|
||||
* Persist the launcher home state across calamities.
|
||||
*/
|
||||
public class LauncherBackupAgent extends BackupAgent {
|
||||
|
||||
private static final String TAG = "LauncherBackupAgent";
|
||||
private static final boolean DEBUG = false;
|
||||
private static final boolean DEBUG = true;
|
||||
|
||||
private static final int MAX_JOURNAL_SIZE = 1000000;
|
||||
|
||||
private static final int MAX_ICONS_PER_PASS = 10;
|
||||
|
||||
private static BackupManager sBackupManager;
|
||||
|
||||
private static final String[] FAVORITE_PROJECTION = {
|
||||
Favorites._ID, // 0
|
||||
Favorites.APPWIDGET_ID, // 1
|
||||
Favorites.APPWIDGET_PROVIDER, // 2
|
||||
Favorites.CELLX, // 3
|
||||
Favorites.CELLY, // 4
|
||||
Favorites.CONTAINER, // 5
|
||||
Favorites.ICON, // 6
|
||||
Favorites.ICON_PACKAGE, // 7
|
||||
Favorites.ICON_RESOURCE, // 8
|
||||
Favorites.ICON_TYPE, // 9
|
||||
Favorites.ITEM_TYPE, // 10
|
||||
Favorites.INTENT, // 11
|
||||
Favorites.SCREEN, // 12
|
||||
Favorites.SPANX, // 13
|
||||
Favorites.SPANY, // 14
|
||||
Favorites.TITLE, // 15
|
||||
Favorites.MODIFIED, // 1
|
||||
Favorites.INTENT, // 2
|
||||
Favorites.APPWIDGET_PROVIDER, // 3
|
||||
Favorites.APPWIDGET_ID, // 4
|
||||
Favorites.CELLX, // 5
|
||||
Favorites.CELLY, // 6
|
||||
Favorites.CONTAINER, // 7
|
||||
Favorites.ICON, // 8
|
||||
Favorites.ICON_PACKAGE, // 9
|
||||
Favorites.ICON_RESOURCE, // 10
|
||||
Favorites.ICON_TYPE, // 11
|
||||
Favorites.ITEM_TYPE, // 12
|
||||
Favorites.SCREEN, // 13
|
||||
Favorites.SPANX, // 14
|
||||
Favorites.SPANY, // 15
|
||||
Favorites.TITLE, // 16
|
||||
};
|
||||
|
||||
private static final int ID_INDEX = 0;
|
||||
private static final int APPWIDGET_ID_INDEX = 1;
|
||||
private static final int APPWIDGET_PROVIDER_INDEX = 2;
|
||||
private static final int CELLX_INDEX = 3;
|
||||
private static final int CELLY_INDEX = 4;
|
||||
private static final int CONTAINER_INDEX = 5;
|
||||
private static final int ICON_INDEX = 6;
|
||||
private static final int ICON_PACKAGE_INDEX = 7;
|
||||
private static final int ICON_RESOURCE_INDEX = 8;
|
||||
private static final int ICON_TYPE_INDEX = 9;
|
||||
private static final int ITEM_TYPE_INDEX = 10;
|
||||
private static final int INTENT_INDEX = 11;
|
||||
private static final int SCREEN_INDEX = 12;
|
||||
private static final int SPANX_INDEX = 13 ;
|
||||
private static final int SPANY_INDEX = 14;
|
||||
private static final int TITLE_INDEX = 15;
|
||||
private static final int ID_MODIFIED = 1;
|
||||
private static final int INTENT_INDEX = 2;
|
||||
private static final int APPWIDGET_PROVIDER_INDEX = 3;
|
||||
private static final int APPWIDGET_ID_INDEX = 4;
|
||||
private static final int CELLX_INDEX = 5;
|
||||
private static final int CELLY_INDEX = 6;
|
||||
private static final int CONTAINER_INDEX = 7;
|
||||
private static final int ICON_INDEX = 8;
|
||||
private static final int ICON_PACKAGE_INDEX = 9;
|
||||
private static final int ICON_RESOURCE_INDEX = 10;
|
||||
private static final int ICON_TYPE_INDEX = 11;
|
||||
private static final int ITEM_TYPE_INDEX = 12;
|
||||
private static final int SCREEN_INDEX = 13;
|
||||
private static final int SPANX_INDEX = 14;
|
||||
private static final int SPANY_INDEX = 15;
|
||||
private static final int TITLE_INDEX = 16;
|
||||
|
||||
private static final String[] SCREEN_PROJECTION = {
|
||||
WorkspaceScreens._ID, // 0
|
||||
WorkspaceScreens.SCREEN_RANK // 1
|
||||
WorkspaceScreens.MODIFIED, // 1
|
||||
WorkspaceScreens.SCREEN_RANK // 2
|
||||
};
|
||||
|
||||
private static final int SCREEN_RANK_INDEX = 1;
|
||||
private static final int SCREEN_RANK_INDEX = 2;
|
||||
|
||||
private static final String[] ID_ONLY_PROJECTION = {
|
||||
BaseColumns._ID
|
||||
|
||||
private static final String[] ICON_PROJECTION = {
|
||||
Favorites._ID, // 0
|
||||
Favorites.MODIFIED, // 1
|
||||
Favorites.INTENT // 2
|
||||
};
|
||||
|
||||
private HashMap<ComponentName, AppWidgetProviderInfo> mWidgetMap;
|
||||
|
||||
|
||||
/**
|
||||
* Notify the backup manager that out database is dirty.
|
||||
|
@ -155,12 +176,11 @@ public class LauncherBackupAgent extends BackupAgent {
|
|||
ArrayList<Key> keys = new ArrayList<Key>();
|
||||
backupFavorites(in, data, out, keys);
|
||||
backupScreens(in, data, out, keys);
|
||||
backupIcons(in, data, out, keys);
|
||||
|
||||
out.key = keys.toArray(BackupProtos.Key.EMPTY_ARRAY);
|
||||
writeJournal(newState, out);
|
||||
Log.v(TAG, "onBackup: wrote " + out.bytes + "b in " + out.rows + " rows.");
|
||||
|
||||
Log.v(TAG, "onBackup: finished");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -205,6 +225,10 @@ public class LauncherBackupAgent extends BackupAgent {
|
|||
restoreScreen(key, buffer, dataSize, keys);
|
||||
break;
|
||||
|
||||
case Key.ICON:
|
||||
restoreIcon(key, buffer, dataSize, keys);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.w(TAG, "unknown restore entity type: " + key.type);
|
||||
break;
|
||||
|
@ -236,70 +260,35 @@ public class LauncherBackupAgent extends BackupAgent {
|
|||
ArrayList<Key> keys)
|
||||
throws IOException {
|
||||
// read the old ID set
|
||||
Set<String> savedIds = new HashSet<String>();
|
||||
for(int i = 0; i < in.key.length; i++) {
|
||||
Key key = in.key[i];
|
||||
if (key.type == Key.FAVORITE) {
|
||||
savedIds.add(keyToBackupKey(key));
|
||||
}
|
||||
}
|
||||
Set<String> savedIds = getSavedIdsByType(Key.FAVORITE, in);
|
||||
if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
|
||||
|
||||
// persist things that have changed since the last backup
|
||||
ContentResolver cr = getContentResolver();
|
||||
String where = ChangeLogColumns.MODIFIED + " > ?";
|
||||
String[] args = {Long.toString(in.t)};
|
||||
String updateOrder = ChangeLogColumns.MODIFIED;
|
||||
Cursor updated = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
|
||||
where, args, updateOrder);
|
||||
if (DEBUG) Log.d(TAG, "favorite updated.getCount()=" + updated.getCount());
|
||||
Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
|
||||
null, null, null);
|
||||
Set<String> currentIds = new HashSet<String>(cursor.getCount());
|
||||
try {
|
||||
updated.moveToPosition(-1);
|
||||
while(updated.moveToNext()) {
|
||||
final long id = updated.getLong(ID_INDEX);
|
||||
cursor.moveToPosition(-1);
|
||||
while(cursor.moveToNext()) {
|
||||
final long id = cursor.getLong(ID_INDEX);
|
||||
final long updateTime = cursor.getLong(ID_MODIFIED);
|
||||
Key key = getKey(Key.FAVORITE, id);
|
||||
byte[] blob = packFavorite(updated);
|
||||
String backupKey = keyToBackupKey(key);
|
||||
data.writeEntityHeader(backupKey, blob.length);
|
||||
data.writeEntityData(blob, blob.length);
|
||||
out.rows++;
|
||||
out.bytes += blob.length;
|
||||
Log.v(TAG, "saving favorite " + backupKey + ": " + id + "/" + blob.length);
|
||||
if(DEBUG) Log.d(TAG, "wrote " +
|
||||
Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP));
|
||||
// remember that is was a new column, so we don't delete it.
|
||||
savedIds.add(backupKey);
|
||||
}
|
||||
} finally {
|
||||
updated.close();
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
|
||||
|
||||
// build the current ID set
|
||||
String idOrder = BaseColumns._ID;
|
||||
Cursor idCursor = cr.query(Favorites.CONTENT_URI, ID_ONLY_PROJECTION,
|
||||
null, null, idOrder);
|
||||
Set<String> currentIds = new HashSet<String>(idCursor.getCount());
|
||||
try {
|
||||
idCursor.moveToPosition(-1);
|
||||
while(idCursor.moveToNext()) {
|
||||
Key key = getKey(Key.FAVORITE, idCursor.getLong(ID_INDEX));
|
||||
currentIds.add(keyToBackupKey(key));
|
||||
// save the IDs for next time
|
||||
keys.add(key);
|
||||
currentIds.add(keyToBackupKey(key));
|
||||
if (updateTime > in.t) {
|
||||
byte[] blob = packFavorite(cursor);
|
||||
writeRowToBackup(key, blob, out, data);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
idCursor.close();
|
||||
cursor.close();
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "favorite currentIds.size()=" + currentIds.size());
|
||||
|
||||
// these IDs must have been deleted
|
||||
savedIds.removeAll(currentIds);
|
||||
for (String deleted : savedIds) {
|
||||
Log.v(TAG, "dropping favorite " + deleted);
|
||||
data.writeEntityHeader(deleted, -1);
|
||||
out.rows++;
|
||||
}
|
||||
out.rows += removeDeletedKeysFromBackup(savedIds, data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -332,76 +321,42 @@ public class LauncherBackupAgent extends BackupAgent {
|
|||
* @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
|
||||
* @param keys keys to mark as clean in the notes for next backup
|
||||
* @throws IOException
|
||||
*/
|
||||
private void backupScreens(Journal in, BackupDataOutput data, Journal out,
|
||||
ArrayList<Key> keys)
|
||||
throws IOException {
|
||||
// read the old ID set
|
||||
Set<String> savedIds = new HashSet<String>();
|
||||
for(int i = 0; i < in.key.length; i++) {
|
||||
Key key = in.key[i];
|
||||
if (key.type == Key.SCREEN) {
|
||||
savedIds.add(keyToBackupKey(key));
|
||||
}
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "screens savedIds.size()=" + savedIds.size());
|
||||
Set<String> savedIds = getSavedIdsByType(Key.SCREEN, in);
|
||||
if (DEBUG) Log.d(TAG, "screen savedIds.size()=" + savedIds.size());
|
||||
|
||||
// persist things that have changed since the last backup
|
||||
ContentResolver cr = getContentResolver();
|
||||
String where = ChangeLogColumns.MODIFIED + " > ?";
|
||||
String[] args = {Long.toString(in.t)};
|
||||
String updateOrder = ChangeLogColumns.MODIFIED;
|
||||
Cursor updated = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
|
||||
where, args, updateOrder);
|
||||
updated.moveToPosition(-1);
|
||||
if (DEBUG) Log.d(TAG, "screens updated.getCount()=" + updated.getCount());
|
||||
Cursor cursor = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
|
||||
null, null, null);
|
||||
Set<String> currentIds = new HashSet<String>(cursor.getCount());
|
||||
try {
|
||||
while(updated.moveToNext()) {
|
||||
final long id = updated.getLong(ID_INDEX);
|
||||
cursor.moveToPosition(-1);
|
||||
while(cursor.moveToNext()) {
|
||||
final long id = cursor.getLong(ID_INDEX);
|
||||
final long updateTime = cursor.getLong(ID_MODIFIED);
|
||||
Key key = getKey(Key.SCREEN, id);
|
||||
byte[] blob = packScreen(updated);
|
||||
String backupKey = keyToBackupKey(key);
|
||||
data.writeEntityHeader(backupKey, blob.length);
|
||||
data.writeEntityData(blob, blob.length);
|
||||
out.rows++;
|
||||
out.bytes += blob.length;
|
||||
Log.v(TAG, "saving screen " + backupKey + ": " + id + "/" + blob.length);
|
||||
if(DEBUG) Log.d(TAG, "wrote " +
|
||||
Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP));
|
||||
// remember that is was a new column, so we don't delete it.
|
||||
savedIds.add(backupKey);
|
||||
}
|
||||
} finally {
|
||||
updated.close();
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "screen savedIds.size()=" + savedIds.size());
|
||||
|
||||
// build the current ID set
|
||||
String idOrder = BaseColumns._ID;
|
||||
Cursor idCursor = cr.query(WorkspaceScreens.CONTENT_URI, ID_ONLY_PROJECTION,
|
||||
null, null, idOrder);
|
||||
idCursor.moveToPosition(-1);
|
||||
Set<String> currentIds = new HashSet<String>(idCursor.getCount());
|
||||
try {
|
||||
while(idCursor.moveToNext()) {
|
||||
Key key = getKey(Key.SCREEN, idCursor.getLong(ID_INDEX));
|
||||
currentIds.add(keyToBackupKey(key));
|
||||
// save the IDs for next time
|
||||
keys.add(key);
|
||||
currentIds.add(keyToBackupKey(key));
|
||||
if (updateTime > in.t) {
|
||||
byte[] blob = packScreen(cursor);
|
||||
writeRowToBackup(key, blob, out, data);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
idCursor.close();
|
||||
cursor.close();
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "screen currentIds.size()=" + currentIds.size());
|
||||
|
||||
// these IDs must have been deleted
|
||||
savedIds.removeAll(currentIds);
|
||||
for(String deleted: savedIds) {
|
||||
Log.v(TAG, "dropping screen " + deleted);
|
||||
data.writeEntityHeader(deleted, -1);
|
||||
out.rows++;
|
||||
}
|
||||
out.rows += removeDeletedKeysFromBackup(savedIds, data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -426,7 +381,118 @@ public class LauncherBackupAgent extends BackupAgent {
|
|||
}
|
||||
}
|
||||
|
||||
/** create a new key object.
|
||||
/**
|
||||
* Write all the static icon 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 backupIcons(Journal in, BackupDataOutput data, Journal out,
|
||||
ArrayList<Key> keys) throws IOException {
|
||||
// persist icons for new shortcuts since the last backup
|
||||
final ContentResolver cr = getContentResolver();
|
||||
final IconCache iconCache = new IconCache(this);
|
||||
final int dpi = getResources().getDisplayMetrics().densityDpi;
|
||||
|
||||
// read the old ID set
|
||||
Set<String> savedIds = getSavedIdsByType(Key.ICON, in);
|
||||
if (DEBUG) Log.d(TAG, "icon savedIds.size()=" + savedIds.size());
|
||||
|
||||
int startRows = out.rows;
|
||||
if (DEBUG) Log.d(TAG, "starting here: " + startRows);
|
||||
String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION;
|
||||
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 intentDescription = cursor.getString(INTENT_INDEX);
|
||||
try {
|
||||
Intent intent = Intent.parseUri(intentDescription, 0);
|
||||
ComponentName cn = intent.getComponent();
|
||||
Key key = null;
|
||||
String backupKey = null;
|
||||
if (cn != null) {
|
||||
key = getKey(Key.ICON, cn.flattenToShortString());
|
||||
backupKey = keyToBackupKey(key);
|
||||
currentIds.add(backupKey);
|
||||
} else {
|
||||
Log.w(TAG, "empty intent on application favorite: " + id);
|
||||
}
|
||||
if (savedIds.contains(backupKey)) {
|
||||
if (DEBUG) Log.d(TAG, "already saved icon " + 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_ICONS_PER_PASS) {
|
||||
if (DEBUG) Log.d(TAG, "saving icon " + backupKey);
|
||||
Bitmap icon = iconCache.getIcon(intent);
|
||||
keys.add(key);
|
||||
if (icon != null && !iconCache.isDefaultIcon(icon)) {
|
||||
byte[] blob = packIcon(dpi, icon);
|
||||
writeRowToBackup(key, blob, out, data);
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) Log.d(TAG, "scheduling another rtun for icon " + backupKey);
|
||||
// too many icons for this pass, request another.
|
||||
dataChanged(this);
|
||||
}
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
Log.w(TAG, "invalid URI on application favorite: " + id);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "unable to save application icon for favorite: " + id);
|
||||
}
|
||||
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "icon currentIds.size()=" + currentIds.size());
|
||||
|
||||
// these IDs must have been deleted
|
||||
savedIds.removeAll(currentIds);
|
||||
out.rows += removeDeletedKeysFromBackup(savedIds, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an icon from the stream.
|
||||
*
|
||||
* <P>Keys arrive in any order, so shortcuts that use this screen 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 restoreIcon(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
|
||||
Log.v(TAG, "unpacking icon " + key.id);
|
||||
if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
|
||||
Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
|
||||
try {
|
||||
Resource res = unpackIcon(buffer, 0, dataSize);
|
||||
if (DEBUG) Log.d(TAG, "unpacked " + res.dpi);
|
||||
if (DEBUG) Log.d(TAG, "read " +
|
||||
Base64.encodeToString(res.data, 0, res.data.length,
|
||||
Base64.NO_WRAP));
|
||||
Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
|
||||
if (icon == null) {
|
||||
Log.w(TAG, "failed to unpack 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
|
||||
* the heavy-weight CheckedMessage wrapper.
|
||||
|
@ -439,6 +505,19 @@ public class LauncherBackupAgent extends BackupAgent {
|
|||
return key;
|
||||
}
|
||||
|
||||
/** create a new key for a named object.
|
||||
*
|
||||
* <P> Keys contain their own checksum instead of using
|
||||
* the heavy-weight CheckedMessage wrapper.
|
||||
*/
|
||||
private Key getKey(int type, String name) {
|
||||
Key key = new Key();
|
||||
key.type = type;
|
||||
key.name = name;
|
||||
key.checksum = checkKey(key);
|
||||
return key;
|
||||
}
|
||||
|
||||
/** keys need to be strings, serialize and encode. */
|
||||
private String keyToBackupKey(Key key) {
|
||||
return Base64.encodeToString(Key.toByteArray(key), Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
|
@ -460,6 +539,28 @@ public class LauncherBackupAgent extends BackupAgent {
|
|||
}
|
||||
}
|
||||
|
||||
private String getKeyName(Key key) {
|
||||
if (TextUtils.isEmpty(key.name)) {
|
||||
return Long.toString(key.id);
|
||||
} else {
|
||||
return key.name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String geKeyType(Key key) {
|
||||
switch (key.type) {
|
||||
case Key.FAVORITE:
|
||||
return "favorite";
|
||||
case Key.SCREEN:
|
||||
return "screen";
|
||||
case Key.ICON:
|
||||
return "icon";
|
||||
default:
|
||||
return "anonymous";
|
||||
}
|
||||
}
|
||||
|
||||
/** Compute the checksum over the important bits of a key. */
|
||||
private long checkKey(Key key) {
|
||||
CRC32 checksum = new CRC32();
|
||||
|
@ -544,6 +645,25 @@ public class LauncherBackupAgent extends BackupAgent {
|
|||
return screen;
|
||||
}
|
||||
|
||||
/** Serialize an icon Resource for persistence, including a checksum wrapper. */
|
||||
private byte[] packIcon(int dpi, Bitmap icon) {
|
||||
Resource res = new Resource();
|
||||
res.dpi = dpi;
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
if (icon.compress(WEBP, 100, os)) {
|
||||
res.data = os.toByteArray();
|
||||
}
|
||||
return writeCheckedBytes(res);
|
||||
}
|
||||
|
||||
/** Deserialize an icon resource from persistence, after verifying checksum wrapper. */
|
||||
private Resource unpackIcon(byte[] buffer, int offset, int dataSize)
|
||||
throws InvalidProtocolBufferNanoException {
|
||||
Resource res = new Resource();
|
||||
MessageNano.mergeFrom(res, readCheckedBytes(buffer, offset, dataSize));
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the old journal from the input file.
|
||||
*
|
||||
|
@ -600,6 +720,41 @@ public class LauncherBackupAgent extends BackupAgent {
|
|||
return journal;
|
||||
}
|
||||
|
||||
private void writeRowToBackup(Key key, byte[] blob, Journal out,
|
||||
BackupDataOutput data) throws IOException {
|
||||
String backupKey = keyToBackupKey(key);
|
||||
data.writeEntityHeader(backupKey, blob.length);
|
||||
data.writeEntityData(blob, blob.length);
|
||||
out.rows++;
|
||||
out.bytes += blob.length;
|
||||
Log.v(TAG, "saving " + geKeyType(key) + " " + backupKey + ": " +
|
||||
getKeyName(key) + "/" + blob.length);
|
||||
if(DEBUG) Log.d(TAG, "wrote " +
|
||||
Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP));
|
||||
}
|
||||
|
||||
private Set<String> getSavedIdsByType(int type, Journal in) {
|
||||
Set<String> savedIds = new HashSet<String>();
|
||||
for(int i = 0; i < in.key.length; i++) {
|
||||
Key key = in.key[i];
|
||||
if (key.type == type) {
|
||||
savedIds.add(keyToBackupKey(key));
|
||||
}
|
||||
}
|
||||
return savedIds;
|
||||
}
|
||||
|
||||
private int removeDeletedKeysFromBackup(Set<String> deletedIds, BackupDataOutput data)
|
||||
throws IOException {
|
||||
int rows = 0;
|
||||
for(String deleted: deletedIds) {
|
||||
Log.v(TAG, "dropping icon " + deleted);
|
||||
data.writeEntityHeader(deleted, -1);
|
||||
rows++;
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the new journal to the output file.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue