blob: a45f2931bea014b5b3baaed5d477f09b7e03dce2 [file] [log] [blame]
Chris Wren1ada10d2013-09-13 18:01:38 -04001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Chris Wren1ada10d2013-09-13 18:01:38 -040016package com.android.launcher3;
17
18import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
19import com.google.protobuf.nano.MessageNano;
20
Chris Wren1ada10d2013-09-13 18:01:38 -040021import com.android.launcher3.LauncherSettings.Favorites;
22import com.android.launcher3.LauncherSettings.WorkspaceScreens;
23import com.android.launcher3.backup.BackupProtos;
24import com.android.launcher3.backup.BackupProtos.CheckedMessage;
25import com.android.launcher3.backup.BackupProtos.Favorite;
26import com.android.launcher3.backup.BackupProtos.Journal;
27import com.android.launcher3.backup.BackupProtos.Key;
Chris Wren22e130d2013-09-23 18:25:57 -040028import com.android.launcher3.backup.BackupProtos.Resource;
Chris Wren1ada10d2013-09-13 18:01:38 -040029import com.android.launcher3.backup.BackupProtos.Screen;
Chris Wrenfd13c712013-09-27 15:45:19 -040030import com.android.launcher3.backup.BackupProtos.Widget;
Chris Wren1ada10d2013-09-13 18:01:38 -040031
Chris Wren92aa4232013-10-04 11:29:36 -040032import android.app.backup.BackupDataInputStream;
Chris Wren1ada10d2013-09-13 18:01:38 -040033import android.app.backup.BackupDataOutput;
Chris Wren4d89e2a2013-10-09 17:03:50 -040034import android.app.backup.BackupHelper;
Chris Wren1ada10d2013-09-13 18:01:38 -040035import android.app.backup.BackupManager;
Chris Wren22e130d2013-09-23 18:25:57 -040036import android.appwidget.AppWidgetManager;
37import android.appwidget.AppWidgetProviderInfo;
38import android.content.ComponentName;
Chris Wren1ada10d2013-09-13 18:01:38 -040039import android.content.ContentResolver;
Chris Wren5dee7af2013-12-20 17:22:11 -050040import android.content.ContentValues;
Chris Wren1ada10d2013-09-13 18:01:38 -040041import android.content.Context;
Chris Wren22e130d2013-09-23 18:25:57 -040042import android.content.Intent;
Chris Wren1ada10d2013-09-13 18:01:38 -040043import android.database.Cursor;
Chris Wren22e130d2013-09-23 18:25:57 -040044import android.graphics.Bitmap;
45import android.graphics.BitmapFactory;
Chris Wrenfd13c712013-09-27 15:45:19 -040046import android.graphics.drawable.Drawable;
Chris Wren1ada10d2013-09-13 18:01:38 -040047import android.os.ParcelFileDescriptor;
Chris Wren1ada10d2013-09-13 18:01:38 -040048import android.text.TextUtils;
49import android.util.Base64;
50import android.util.Log;
51
Chris Wren22e130d2013-09-23 18:25:57 -040052import java.io.ByteArrayOutputStream;
Chris Wren1ada10d2013-09-13 18:01:38 -040053import java.io.FileInputStream;
54import java.io.FileOutputStream;
55import java.io.IOException;
Chris Wren22e130d2013-09-23 18:25:57 -040056import java.net.URISyntaxException;
Chris Wren1ada10d2013-09-13 18:01:38 -040057import java.util.ArrayList;
Chris Wren22e130d2013-09-23 18:25:57 -040058import java.util.HashMap;
Chris Wren1ada10d2013-09-13 18:01:38 -040059import java.util.HashSet;
Chris Wren22e130d2013-09-23 18:25:57 -040060import java.util.List;
Chris Wren1ada10d2013-09-13 18:01:38 -040061import java.util.Set;
62import java.util.zip.CRC32;
63
64/**
65 * Persist the launcher home state across calamities.
66 */
Chris Wren92aa4232013-10-04 11:29:36 -040067public class LauncherBackupHelper implements BackupHelper {
Chris Wren1ada10d2013-09-13 18:01:38 -040068
Chris Wren92aa4232013-10-04 11:29:36 -040069 private static final String TAG = "LauncherBackupHelper";
Chris Wren50c8f422014-01-15 16:10:39 -050070 private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
71 private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
Chris Wren92aa4232013-10-04 11:29:36 -040072 private static final boolean DEBUG_PAYLOAD = false;
Chris Wren1ada10d2013-09-13 18:01:38 -040073
74 private static final int MAX_JOURNAL_SIZE = 1000000;
75
Chris Wrenfd13c712013-09-27 15:45:19 -040076 /** icons are large, dribble them out */
Chris Wren22e130d2013-09-23 18:25:57 -040077 private static final int MAX_ICONS_PER_PASS = 10;
78
Chris Wrenfd13c712013-09-27 15:45:19 -040079 /** widgets contain previews, which are very large, dribble them out */
80 private static final int MAX_WIDGETS_PER_PASS = 5;
81
82 public static final int IMAGE_COMPRESSION_QUALITY = 75;
83
Chris Wren92aa4232013-10-04 11:29:36 -040084 public static final String LAUNCHER_PREFIX = "L";
85
Chris Wren45297f82013-10-17 15:16:48 -040086 public static final String LAUNCHER_PREFS_PREFIX = "LP";
87
Chris Wrenb86f0762013-10-04 10:10:21 -040088 private static final Bitmap.CompressFormat IMAGE_FORMAT =
89 android.graphics.Bitmap.CompressFormat.PNG;
90
Chris Wren1ada10d2013-09-13 18:01:38 -040091 private static BackupManager sBackupManager;
92
93 private static final String[] FAVORITE_PROJECTION = {
94 Favorites._ID, // 0
Chris Wren22e130d2013-09-23 18:25:57 -040095 Favorites.MODIFIED, // 1
96 Favorites.INTENT, // 2
97 Favorites.APPWIDGET_PROVIDER, // 3
98 Favorites.APPWIDGET_ID, // 4
99 Favorites.CELLX, // 5
100 Favorites.CELLY, // 6
101 Favorites.CONTAINER, // 7
102 Favorites.ICON, // 8
103 Favorites.ICON_PACKAGE, // 9
104 Favorites.ICON_RESOURCE, // 10
105 Favorites.ICON_TYPE, // 11
106 Favorites.ITEM_TYPE, // 12
107 Favorites.SCREEN, // 13
108 Favorites.SPANX, // 14
109 Favorites.SPANY, // 15
110 Favorites.TITLE, // 16
Chris Wren1ada10d2013-09-13 18:01:38 -0400111 };
112
113 private static final int ID_INDEX = 0;
Chris Wren22e130d2013-09-23 18:25:57 -0400114 private static final int ID_MODIFIED = 1;
115 private static final int INTENT_INDEX = 2;
116 private static final int APPWIDGET_PROVIDER_INDEX = 3;
117 private static final int APPWIDGET_ID_INDEX = 4;
118 private static final int CELLX_INDEX = 5;
119 private static final int CELLY_INDEX = 6;
120 private static final int CONTAINER_INDEX = 7;
121 private static final int ICON_INDEX = 8;
122 private static final int ICON_PACKAGE_INDEX = 9;
123 private static final int ICON_RESOURCE_INDEX = 10;
124 private static final int ICON_TYPE_INDEX = 11;
125 private static final int ITEM_TYPE_INDEX = 12;
126 private static final int SCREEN_INDEX = 13;
127 private static final int SPANX_INDEX = 14;
128 private static final int SPANY_INDEX = 15;
129 private static final int TITLE_INDEX = 16;
Chris Wren1ada10d2013-09-13 18:01:38 -0400130
131 private static final String[] SCREEN_PROJECTION = {
132 WorkspaceScreens._ID, // 0
Chris Wren22e130d2013-09-23 18:25:57 -0400133 WorkspaceScreens.MODIFIED, // 1
134 WorkspaceScreens.SCREEN_RANK // 2
Chris Wren1ada10d2013-09-13 18:01:38 -0400135 };
136
Chris Wren22e130d2013-09-23 18:25:57 -0400137 private static final int SCREEN_RANK_INDEX = 2;
Chris Wren1ada10d2013-09-13 18:01:38 -0400138
Chris Wren92aa4232013-10-04 11:29:36 -0400139 private final Context mContext;
Chris Wren1ada10d2013-09-13 18:01:38 -0400140
Chris Wren4b171362014-01-13 17:39:02 -0500141 private final boolean mRestoreEnabled;
142
Chris Wren22e130d2013-09-23 18:25:57 -0400143 private HashMap<ComponentName, AppWidgetProviderInfo> mWidgetMap;
144
Chris Wren92aa4232013-10-04 11:29:36 -0400145 private ArrayList<Key> mKeys;
Chris Wren1ada10d2013-09-13 18:01:38 -0400146
Chris Wren4b171362014-01-13 17:39:02 -0500147 public LauncherBackupHelper(Context context, boolean restoreEnabled) {
Chris Wren92aa4232013-10-04 11:29:36 -0400148 mContext = context;
Chris Wren4b171362014-01-13 17:39:02 -0500149 mRestoreEnabled = restoreEnabled;
Chris Wren92aa4232013-10-04 11:29:36 -0400150 }
151
152 private void dataChanged() {
Chris Wren1ada10d2013-09-13 18:01:38 -0400153 if (sBackupManager == null) {
Chris Wren92aa4232013-10-04 11:29:36 -0400154 sBackupManager = new BackupManager(mContext);
Chris Wren1ada10d2013-09-13 18:01:38 -0400155 }
156 sBackupManager.dataChanged();
157 }
158
159 /**
160 * Back up launcher data so we can restore the user's state on a new device.
161 *
162 * <P>The journal is a timestamp and a list of keys that were saved as of that time.
163 *
164 * <P>Keys may come back in any order, so each key/value is one complete row of the database.
165 *
166 * @param oldState notes from the last backup
167 * @param data incremental key/value pairs to persist off-device
168 * @param newState notes for the next backup
169 * @throws IOException
170 */
171 @Override
Chris Wren92aa4232013-10-04 11:29:36 -0400172 public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
173 ParcelFileDescriptor newState) {
Chris Wren50c8f422014-01-15 16:10:39 -0500174 if (VERBOSE) Log.v(TAG, "onBackup");
Chris Wren1ada10d2013-09-13 18:01:38 -0400175
176 Journal in = readJournal(oldState);
177 Journal out = new Journal();
178
179 long lastBackupTime = in.t;
180 out.t = System.currentTimeMillis();
181 out.rows = 0;
182 out.bytes = 0;
183
Chris Wren50c8f422014-01-15 16:10:39 -0500184 Log.v(TAG, "lastBackupTime = " + lastBackupTime);
Chris Wren1ada10d2013-09-13 18:01:38 -0400185
186 ArrayList<Key> keys = new ArrayList<Key>();
Chris Wren92aa4232013-10-04 11:29:36 -0400187 try {
188 backupFavorites(in, data, out, keys);
189 backupScreens(in, data, out, keys);
190 backupIcons(in, data, out, keys);
191 backupWidgets(in, data, out, keys);
192 } catch (IOException e) {
193 Log.e(TAG, "launcher backup has failed", e);
194 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400195
Mac Duy Hai22dc65e2014-02-05 10:52:07 +0000196 out.key = keys.toArray(new BackupProtos.Key[keys.size()]);
Chris Wren1ada10d2013-09-13 18:01:38 -0400197 writeJournal(newState, out);
198 Log.v(TAG, "onBackup: wrote " + out.bytes + "b in " + out.rows + " rows.");
Chris Wren1ada10d2013-09-13 18:01:38 -0400199 }
200
201 /**
Chris Wren92aa4232013-10-04 11:29:36 -0400202 * Restore launcher configuration from the restored data stream.
Chris Wren1ada10d2013-09-13 18:01:38 -0400203 *
204 * <P>Keys may arrive in any order.
205 *
Chris Wren92aa4232013-10-04 11:29:36 -0400206 * @param data the key/value pair from the server
Chris Wren1ada10d2013-09-13 18:01:38 -0400207 */
208 @Override
Chris Wren92aa4232013-10-04 11:29:36 -0400209 public void restoreEntity(BackupDataInputStream data) {
Chris Wren50c8f422014-01-15 16:10:39 -0500210 if (VERBOSE) Log.v(TAG, "restoreEntity");
Chris Wren92aa4232013-10-04 11:29:36 -0400211 if (mKeys == null) {
212 mKeys = new ArrayList<Key>();
213 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400214 byte[] buffer = new byte[512];
Chris Wren1ada10d2013-09-13 18:01:38 -0400215 String backupKey = data.getKey();
Chris Wren92aa4232013-10-04 11:29:36 -0400216 int dataSize = data.size();
Chris Wren1ada10d2013-09-13 18:01:38 -0400217 if (buffer.length < dataSize) {
218 buffer = new byte[dataSize];
219 }
220 Key key = null;
Chris Wren92aa4232013-10-04 11:29:36 -0400221 int bytesRead = 0;
222 try {
223 bytesRead = data.read(buffer, 0, dataSize);
224 if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
225 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500226 Log.e(TAG, "failed to read entity from restore data", e);
Chris Wren92aa4232013-10-04 11:29:36 -0400227 }
228 try {
229 key = backupKeyToKey(backupKey);
Chris Wren50c8f422014-01-15 16:10:39 -0500230 mKeys.add(key);
Chris Wren92aa4232013-10-04 11:29:36 -0400231 switch (key.type) {
232 case Key.FAVORITE:
233 restoreFavorite(key, buffer, dataSize, mKeys);
234 break;
235
236 case Key.SCREEN:
237 restoreScreen(key, buffer, dataSize, mKeys);
238 break;
239
240 case Key.ICON:
241 restoreIcon(key, buffer, dataSize, mKeys);
242 break;
243
244 case Key.WIDGET:
245 restoreWidget(key, buffer, dataSize, mKeys);
246 break;
247
248 default:
249 Log.w(TAG, "unknown restore entity type: " + key.type);
250 break;
Chris Wren1ada10d2013-09-13 18:01:38 -0400251 }
Chris Wren92aa4232013-10-04 11:29:36 -0400252 } catch (KeyParsingException e) {
253 Log.w(TAG, "ignoring unparsable backup key: " + backupKey);
Chris Wren1ada10d2013-09-13 18:01:38 -0400254 }
255
Chris Wren92aa4232013-10-04 11:29:36 -0400256 }
257
258 /**
259 * Record the restore state for the next backup.
260 *
261 * @param newState notes about the backup state after restore.
262 */
263 @Override
264 public void writeNewStateDescription(ParcelFileDescriptor newState) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400265 // clear the output journal time, to force a full backup to
266 // will catch any changes the restore process might have made
Chris Wren92aa4232013-10-04 11:29:36 -0400267 Journal out = new Journal();
Chris Wren1ada10d2013-09-13 18:01:38 -0400268 out.t = 0;
Mac Duy Hai22dc65e2014-02-05 10:52:07 +0000269 out.key = mKeys.toArray(new BackupProtos.Key[mKeys.size()]);
Chris Wren1ada10d2013-09-13 18:01:38 -0400270 writeJournal(newState, out);
Chris Wren92aa4232013-10-04 11:29:36 -0400271 Log.v(TAG, "onRestore: read " + mKeys.size() + " rows");
272 mKeys.clear();
Chris Wren1ada10d2013-09-13 18:01:38 -0400273 }
274
275 /**
276 * Write all modified favorites to the data stream.
277 *
278 *
279 * @param in notes from last backup
280 * @param data output stream for key/value pairs
281 * @param out notes about this backup
282 * @param keys keys to mark as clean in the notes for next backup
283 * @throws IOException
284 */
285 private void backupFavorites(Journal in, BackupDataOutput data, Journal out,
286 ArrayList<Key> keys)
287 throws IOException {
288 // read the old ID set
Chris Wren22e130d2013-09-23 18:25:57 -0400289 Set<String> savedIds = getSavedIdsByType(Key.FAVORITE, in);
Chris Wren1ada10d2013-09-13 18:01:38 -0400290 if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
291
292 // persist things that have changed since the last backup
Chris Wren92aa4232013-10-04 11:29:36 -0400293 ContentResolver cr = mContext.getContentResolver();
Chris Wren22e130d2013-09-23 18:25:57 -0400294 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
295 null, null, null);
296 Set<String> currentIds = new HashSet<String>(cursor.getCount());
Chris Wren1ada10d2013-09-13 18:01:38 -0400297 try {
Chris Wren22e130d2013-09-23 18:25:57 -0400298 cursor.moveToPosition(-1);
299 while(cursor.moveToNext()) {
300 final long id = cursor.getLong(ID_INDEX);
301 final long updateTime = cursor.getLong(ID_MODIFIED);
Chris Wren1ada10d2013-09-13 18:01:38 -0400302 Key key = getKey(Key.FAVORITE, id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400303 keys.add(key);
Chris Wren5743aa92014-01-10 18:02:06 -0500304 final String backupKey = keyToBackupKey(key);
305 currentIds.add(backupKey);
306 if (!savedIds.contains(backupKey) || updateTime >= in.t) {
Chris Wren22e130d2013-09-23 18:25:57 -0400307 byte[] blob = packFavorite(cursor);
308 writeRowToBackup(key, blob, out, data);
Chris Wren50c8f422014-01-15 16:10:39 -0500309 } else {
310 if (VERBOSE) Log.v(TAG, "favorite " + id + " was too old: " + updateTime);
Chris Wren22e130d2013-09-23 18:25:57 -0400311 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400312 }
313 } finally {
Chris Wren22e130d2013-09-23 18:25:57 -0400314 cursor.close();
Chris Wren1ada10d2013-09-13 18:01:38 -0400315 }
316 if (DEBUG) Log.d(TAG, "favorite currentIds.size()=" + currentIds.size());
317
318 // these IDs must have been deleted
319 savedIds.removeAll(currentIds);
Chris Wren22e130d2013-09-23 18:25:57 -0400320 out.rows += removeDeletedKeysFromBackup(savedIds, data);
Chris Wren1ada10d2013-09-13 18:01:38 -0400321 }
322
323 /**
324 * Read a favorite from the stream.
325 *
326 * <P>Keys arrive in any order, so screens and containers may not exist yet.
327 *
328 * @param key identifier for the row
329 * @param buffer the serialized proto from the stream, may be larger than dataSize
330 * @param dataSize the size of the proto from the stream
331 * @param keys keys to mark as clean in the notes for next backup
332 */
333 private void restoreFavorite(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
Chris Wren50c8f422014-01-15 16:10:39 -0500334 if (VERBOSE) Log.v(TAG, "unpacking favorite " + key.id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400335 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
336 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
337
Chris Wren5dee7af2013-12-20 17:22:11 -0500338 if (!mRestoreEnabled) {
Chris Wren50c8f422014-01-15 16:10:39 -0500339 if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
Chris Wren5dee7af2013-12-20 17:22:11 -0500340 return;
341 }
342
Chris Wren1ada10d2013-09-13 18:01:38 -0400343 try {
Chris Wren5dee7af2013-12-20 17:22:11 -0500344 ContentResolver cr = mContext.getContentResolver();
345 ContentValues values = unpackFavorite(buffer, 0, dataSize);
346 cr.insert(Favorites.CONTENT_URI, values);
Chris Wren1ada10d2013-09-13 18:01:38 -0400347 } catch (InvalidProtocolBufferNanoException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500348 Log.e(TAG, "failed to decode favorite", e);
Chris Wren1ada10d2013-09-13 18:01:38 -0400349 }
350 }
351
352 /**
353 * Write all modified screens to the data stream.
354 *
355 *
356 * @param in notes from last backup
357 * @param data output stream for key/value pairs
358 * @param out notes about this backup
Chris Wren22e130d2013-09-23 18:25:57 -0400359 * @param keys keys to mark as clean in the notes for next backup
360 * @throws IOException
Chris Wren1ada10d2013-09-13 18:01:38 -0400361 */
362 private void backupScreens(Journal in, BackupDataOutput data, Journal out,
363 ArrayList<Key> keys)
364 throws IOException {
365 // read the old ID set
Chris Wren22e130d2013-09-23 18:25:57 -0400366 Set<String> savedIds = getSavedIdsByType(Key.SCREEN, in);
367 if (DEBUG) Log.d(TAG, "screen savedIds.size()=" + savedIds.size());
Chris Wren1ada10d2013-09-13 18:01:38 -0400368
369 // persist things that have changed since the last backup
Chris Wren92aa4232013-10-04 11:29:36 -0400370 ContentResolver cr = mContext.getContentResolver();
Chris Wren22e130d2013-09-23 18:25:57 -0400371 Cursor cursor = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
372 null, null, null);
373 Set<String> currentIds = new HashSet<String>(cursor.getCount());
Chris Wren1ada10d2013-09-13 18:01:38 -0400374 try {
Chris Wren22e130d2013-09-23 18:25:57 -0400375 cursor.moveToPosition(-1);
Chris Wren50c8f422014-01-15 16:10:39 -0500376 if (DEBUG) Log.d(TAG, "dumping screens after: " + in.t);
Chris Wren22e130d2013-09-23 18:25:57 -0400377 while(cursor.moveToNext()) {
378 final long id = cursor.getLong(ID_INDEX);
379 final long updateTime = cursor.getLong(ID_MODIFIED);
Chris Wren1ada10d2013-09-13 18:01:38 -0400380 Key key = getKey(Key.SCREEN, id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400381 keys.add(key);
Chris Wren5743aa92014-01-10 18:02:06 -0500382 final String backupKey = keyToBackupKey(key);
383 currentIds.add(backupKey);
384 if (!savedIds.contains(backupKey) || updateTime >= in.t) {
Chris Wren22e130d2013-09-23 18:25:57 -0400385 byte[] blob = packScreen(cursor);
386 writeRowToBackup(key, blob, out, data);
Chris Wren5dee7af2013-12-20 17:22:11 -0500387 } else {
Chris Wren50c8f422014-01-15 16:10:39 -0500388 if (VERBOSE) Log.v(TAG, "screen " + id + " was too old: " + updateTime);
Chris Wren22e130d2013-09-23 18:25:57 -0400389 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400390 }
391 } finally {
Chris Wren22e130d2013-09-23 18:25:57 -0400392 cursor.close();
Chris Wren1ada10d2013-09-13 18:01:38 -0400393 }
394 if (DEBUG) Log.d(TAG, "screen currentIds.size()=" + currentIds.size());
395
396 // these IDs must have been deleted
397 savedIds.removeAll(currentIds);
Chris Wren22e130d2013-09-23 18:25:57 -0400398 out.rows += removeDeletedKeysFromBackup(savedIds, data);
Chris Wren1ada10d2013-09-13 18:01:38 -0400399 }
400
401 /**
402 * Read a screen from the stream.
403 *
404 * <P>Keys arrive in any order, so children of this screen may already exist.
405 *
406 * @param key identifier for the row
407 * @param buffer the serialized proto from the stream, may be larger than dataSize
408 * @param dataSize the size of the proto from the stream
409 * @param keys keys to mark as clean in the notes for next backup
410 */
411 private void restoreScreen(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
Chris Wren50c8f422014-01-15 16:10:39 -0500412 if (VERBOSE) Log.v(TAG, "unpacking screen " + key.id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400413 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
414 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
Chris Wren5dee7af2013-12-20 17:22:11 -0500415
416 if (!mRestoreEnabled) {
Chris Wren50c8f422014-01-15 16:10:39 -0500417 if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
Chris Wren5dee7af2013-12-20 17:22:11 -0500418 return;
419 }
420
Chris Wren1ada10d2013-09-13 18:01:38 -0400421 try {
Chris Wren5dee7af2013-12-20 17:22:11 -0500422 ContentResolver cr = mContext.getContentResolver();
423 ContentValues values = unpackScreen(buffer, 0, dataSize);
424 cr.insert(WorkspaceScreens.CONTENT_URI, values);
425
Chris Wren1ada10d2013-09-13 18:01:38 -0400426 } catch (InvalidProtocolBufferNanoException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500427 Log.e(TAG, "failed to decode screen", e);
Chris Wren1ada10d2013-09-13 18:01:38 -0400428 }
429 }
430
Chris Wren22e130d2013-09-23 18:25:57 -0400431 /**
432 * Write all the static icon resources we need to render placeholders
433 * for a package that is not installed.
434 *
435 * @param in notes from last backup
436 * @param data output stream for key/value pairs
437 * @param out notes about this backup
438 * @param keys keys to mark as clean in the notes for next backup
439 * @throws IOException
440 */
441 private void backupIcons(Journal in, BackupDataOutput data, Journal out,
442 ArrayList<Key> keys) throws IOException {
Chris Wrenfd13c712013-09-27 15:45:19 -0400443 // persist icons that haven't been persisted yet
Chris Wren92aa4232013-10-04 11:29:36 -0400444 final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
445 if (appState == null) {
446 dataChanged(); // try again later
Chris Wrend8fe6de2013-10-04 10:42:14 -0400447 if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying icon backup");
448 return;
449 }
Chris Wren92aa4232013-10-04 11:29:36 -0400450 final ContentResolver cr = mContext.getContentResolver();
451 final IconCache iconCache = appState.getIconCache();
452 final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
Chris Wren22e130d2013-09-23 18:25:57 -0400453
454 // read the old ID set
455 Set<String> savedIds = getSavedIdsByType(Key.ICON, in);
456 if (DEBUG) Log.d(TAG, "icon savedIds.size()=" + savedIds.size());
457
458 int startRows = out.rows;
459 if (DEBUG) Log.d(TAG, "starting here: " + startRows);
460 String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION;
461 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
462 where, null, null);
463 Set<String> currentIds = new HashSet<String>(cursor.getCount());
464 try {
465 cursor.moveToPosition(-1);
466 while(cursor.moveToNext()) {
467 final long id = cursor.getLong(ID_INDEX);
468 final String intentDescription = cursor.getString(INTENT_INDEX);
469 try {
470 Intent intent = Intent.parseUri(intentDescription, 0);
471 ComponentName cn = intent.getComponent();
472 Key key = null;
473 String backupKey = null;
474 if (cn != null) {
475 key = getKey(Key.ICON, cn.flattenToShortString());
476 backupKey = keyToBackupKey(key);
477 currentIds.add(backupKey);
478 } else {
479 Log.w(TAG, "empty intent on application favorite: " + id);
480 }
481 if (savedIds.contains(backupKey)) {
Chris Wren50c8f422014-01-15 16:10:39 -0500482 if (VERBOSE) Log.v(TAG, "already saved icon " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -0400483
484 // remember that we already backed this up previously
485 keys.add(key);
486 } else if (backupKey != null) {
487 if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
488 if ((out.rows - startRows) < MAX_ICONS_PER_PASS) {
Chris Wren50c8f422014-01-15 16:10:39 -0500489 if (VERBOSE) Log.v(TAG, "saving icon " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -0400490 Bitmap icon = iconCache.getIcon(intent);
491 keys.add(key);
492 if (icon != null && !iconCache.isDefaultIcon(icon)) {
493 byte[] blob = packIcon(dpi, icon);
494 writeRowToBackup(key, blob, out, data);
495 }
496 } else {
Chris Wren50c8f422014-01-15 16:10:39 -0500497 if (VERBOSE) Log.d(TAG, "deferring icon backup " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -0400498 // too many icons for this pass, request another.
Chris Wren92aa4232013-10-04 11:29:36 -0400499 dataChanged();
Chris Wren22e130d2013-09-23 18:25:57 -0400500 }
501 }
502 } catch (URISyntaxException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500503 Log.e(TAG, "invalid URI on application favorite: " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400504 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500505 Log.e(TAG, "unable to save application icon for favorite: " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400506 }
507
508 }
509 } finally {
510 cursor.close();
511 }
512 if (DEBUG) Log.d(TAG, "icon currentIds.size()=" + currentIds.size());
513
514 // these IDs must have been deleted
515 savedIds.removeAll(currentIds);
516 out.rows += removeDeletedKeysFromBackup(savedIds, data);
517 }
518
519 /**
520 * Read an icon from the stream.
521 *
Chris Wrenfd13c712013-09-27 15:45:19 -0400522 * <P>Keys arrive in any order, so shortcuts that use this icon may already exist.
Chris Wren22e130d2013-09-23 18:25:57 -0400523 *
524 * @param key identifier for the row
525 * @param buffer the serialized proto from the stream, may be larger than dataSize
526 * @param dataSize the size of the proto from the stream
527 * @param keys keys to mark as clean in the notes for next backup
528 */
529 private void restoreIcon(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
Chris Wren50c8f422014-01-15 16:10:39 -0500530 if (VERBOSE) Log.v(TAG, "unpacking icon " + key.id);
Chris Wren22e130d2013-09-23 18:25:57 -0400531 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
532 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
533 try {
534 Resource res = unpackIcon(buffer, 0, dataSize);
Chris Wren50c8f422014-01-15 16:10:39 -0500535 if (DEBUG) Log.d(TAG, "unpacked " + res.dpi + " dpi icon");
536 if (DEBUG_PAYLOAD) Log.d(TAG, "read " +
Chris Wren22e130d2013-09-23 18:25:57 -0400537 Base64.encodeToString(res.data, 0, res.data.length,
538 Base64.NO_WRAP));
539 Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
540 if (icon == null) {
541 Log.w(TAG, "failed to unpack icon for " + key.name);
542 }
Chris Wren5dee7af2013-12-20 17:22:11 -0500543
544 if (!mRestoreEnabled) {
Chris Wren50c8f422014-01-15 16:10:39 -0500545 if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
Chris Wren5dee7af2013-12-20 17:22:11 -0500546 return;
547 } else {
548 // future site of icon cache mutation
549 }
Chris Wren22e130d2013-09-23 18:25:57 -0400550 } catch (InvalidProtocolBufferNanoException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500551 Log.e(TAG, "failed to decode icon", e);
Chris Wren22e130d2013-09-23 18:25:57 -0400552 }
553 }
554
Chris Wrenfd13c712013-09-27 15:45:19 -0400555 /**
556 * Write all the static widget resources we need to render placeholders
557 * for a package that is not installed.
558 *
559 * @param in notes from last backup
560 * @param data output stream for key/value pairs
561 * @param out notes about this backup
562 * @param keys keys to mark as clean in the notes for next backup
563 * @throws IOException
564 */
565 private void backupWidgets(Journal in, BackupDataOutput data, Journal out,
566 ArrayList<Key> keys) throws IOException {
567 // persist static widget info that hasn't been persisted yet
Chris Wrend8fe6de2013-10-04 10:42:14 -0400568 final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
569 if (appState == null) {
Chris Wren92aa4232013-10-04 11:29:36 -0400570 dataChanged(); // try again later
Chris Wrend8fe6de2013-10-04 10:42:14 -0400571 if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying widget backup");
572 return;
573 }
Chris Wren92aa4232013-10-04 11:29:36 -0400574 final ContentResolver cr = mContext.getContentResolver();
575 final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext);
576 final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(mContext);
Chris Wrenfd13c712013-09-27 15:45:19 -0400577 final IconCache iconCache = appState.getIconCache();
Chris Wren92aa4232013-10-04 11:29:36 -0400578 final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
Chris Wrenfd13c712013-09-27 15:45:19 -0400579 final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
580 if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
581
582 // read the old ID set
583 Set<String> savedIds = getSavedIdsByType(Key.WIDGET, in);
584 if (DEBUG) Log.d(TAG, "widgets savedIds.size()=" + savedIds.size());
585
586 int startRows = out.rows;
587 if (DEBUG) Log.d(TAG, "starting here: " + startRows);
588 String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPWIDGET;
589 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
590 where, null, null);
591 Set<String> currentIds = new HashSet<String>(cursor.getCount());
592 try {
593 cursor.moveToPosition(-1);
594 while(cursor.moveToNext()) {
595 final long id = cursor.getLong(ID_INDEX);
596 final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX);
597 final int spanX = cursor.getInt(SPANX_INDEX);
598 final int spanY = cursor.getInt(SPANY_INDEX);
599 final ComponentName provider = ComponentName.unflattenFromString(providerName);
600 Key key = null;
601 String backupKey = null;
602 if (provider != null) {
603 key = getKey(Key.WIDGET, providerName);
604 backupKey = keyToBackupKey(key);
605 currentIds.add(backupKey);
606 } else {
607 Log.w(TAG, "empty intent on appwidget: " + id);
608 }
609 if (savedIds.contains(backupKey)) {
Chris Wren50c8f422014-01-15 16:10:39 -0500610 if (VERBOSE) Log.v(TAG, "already saved widget " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400611
612 // remember that we already backed this up previously
613 keys.add(key);
614 } else if (backupKey != null) {
615 if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
616 if ((out.rows - startRows) < MAX_WIDGETS_PER_PASS) {
Chris Wren50c8f422014-01-15 16:10:39 -0500617 if (VERBOSE) Log.v(TAG, "saving widget " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400618 previewLoader.setPreviewSize(spanX * profile.cellWidthPx,
619 spanY * profile.cellHeightPx, widgetSpacingLayout);
620 byte[] blob = packWidget(dpi, previewLoader, iconCache, provider);
Chris Wrenb1fd63b2013-10-03 15:43:58 -0400621 keys.add(key);
Chris Wrenfd13c712013-09-27 15:45:19 -0400622 writeRowToBackup(key, blob, out, data);
623
624 } else {
Chris Wren50c8f422014-01-15 16:10:39 -0500625 if (VERBOSE) Log.d(TAG, "deferring widget backup " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400626 // too many widgets for this pass, request another.
Chris Wren92aa4232013-10-04 11:29:36 -0400627 dataChanged();
Chris Wrenfd13c712013-09-27 15:45:19 -0400628 }
629 }
630 }
631 } finally {
632 cursor.close();
633 }
634 if (DEBUG) Log.d(TAG, "widget currentIds.size()=" + currentIds.size());
635
636 // these IDs must have been deleted
637 savedIds.removeAll(currentIds);
638 out.rows += removeDeletedKeysFromBackup(savedIds, data);
639 }
640
641 /**
642 * Read a widget from the stream.
643 *
644 * <P>Keys arrive in any order, so widgets that use this data may already exist.
645 *
646 * @param key identifier for the row
647 * @param buffer the serialized proto from the stream, may be larger than dataSize
648 * @param dataSize the size of the proto from the stream
649 * @param keys keys to mark as clean in the notes for next backup
650 */
651 private void restoreWidget(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
Chris Wren50c8f422014-01-15 16:10:39 -0500652 if (VERBOSE) Log.v(TAG, "unpacking widget " + key.id);
Chris Wrenfd13c712013-09-27 15:45:19 -0400653 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
654 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
655 try {
656 Widget widget = unpackWidget(buffer, 0, dataSize);
657 if (DEBUG) Log.d(TAG, "unpacked " + widget.provider);
658 if (widget.icon.data != null) {
659 Bitmap icon = BitmapFactory
660 .decodeByteArray(widget.icon.data, 0, widget.icon.data.length);
661 if (icon == null) {
662 Log.w(TAG, "failed to unpack widget icon for " + key.name);
663 }
664 }
Chris Wren5dee7af2013-12-20 17:22:11 -0500665
666 if (!mRestoreEnabled) {
Chris Wren50c8f422014-01-15 16:10:39 -0500667 if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
Chris Wren5dee7af2013-12-20 17:22:11 -0500668 return;
669 } else {
670 // future site of widget table mutation
671 }
Chris Wrenfd13c712013-09-27 15:45:19 -0400672 } catch (InvalidProtocolBufferNanoException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500673 Log.e(TAG, "failed to decode widget", e);
Chris Wrenfd13c712013-09-27 15:45:19 -0400674 }
675 }
676
Chris Wren22e130d2013-09-23 18:25:57 -0400677 /** create a new key, with an integer ID.
Chris Wren1ada10d2013-09-13 18:01:38 -0400678 *
679 * <P> Keys contain their own checksum instead of using
680 * the heavy-weight CheckedMessage wrapper.
681 */
682 private Key getKey(int type, long id) {
683 Key key = new Key();
684 key.type = type;
685 key.id = id;
686 key.checksum = checkKey(key);
687 return key;
688 }
689
Chris Wren22e130d2013-09-23 18:25:57 -0400690 /** create a new key for a named object.
691 *
692 * <P> Keys contain their own checksum instead of using
693 * the heavy-weight CheckedMessage wrapper.
694 */
695 private Key getKey(int type, String name) {
696 Key key = new Key();
697 key.type = type;
698 key.name = name;
699 key.checksum = checkKey(key);
700 return key;
701 }
702
Chris Wren1ada10d2013-09-13 18:01:38 -0400703 /** keys need to be strings, serialize and encode. */
704 private String keyToBackupKey(Key key) {
Chris Wren978194c2013-10-03 17:47:22 -0400705 return Base64.encodeToString(Key.toByteArray(key), Base64.NO_WRAP);
Chris Wren1ada10d2013-09-13 18:01:38 -0400706 }
707
708 /** keys need to be strings, decode and parse. */
709 private Key backupKeyToKey(String backupKey) throws KeyParsingException {
710 try {
711 Key key = Key.parseFrom(Base64.decode(backupKey, Base64.DEFAULT));
712 if (key.checksum != checkKey(key)) {
713 key = null;
714 throw new KeyParsingException("invalid key read from stream" + backupKey);
715 }
716 return key;
717 } catch (InvalidProtocolBufferNanoException e) {
718 throw new KeyParsingException(e);
719 } catch (IllegalArgumentException e) {
720 throw new KeyParsingException(e);
721 }
722 }
723
Chris Wren22e130d2013-09-23 18:25:57 -0400724 private String getKeyName(Key key) {
725 if (TextUtils.isEmpty(key.name)) {
726 return Long.toString(key.id);
727 } else {
728 return key.name;
729 }
730
731 }
732
733 private String geKeyType(Key key) {
734 switch (key.type) {
735 case Key.FAVORITE:
736 return "favorite";
737 case Key.SCREEN:
738 return "screen";
739 case Key.ICON:
740 return "icon";
Chris Wrenfd13c712013-09-27 15:45:19 -0400741 case Key.WIDGET:
742 return "widget";
Chris Wren22e130d2013-09-23 18:25:57 -0400743 default:
744 return "anonymous";
745 }
746 }
747
Chris Wren1ada10d2013-09-13 18:01:38 -0400748 /** Compute the checksum over the important bits of a key. */
749 private long checkKey(Key key) {
750 CRC32 checksum = new CRC32();
751 checksum.update(key.type);
752 checksum.update((int) (key.id & 0xffff));
753 checksum.update((int) ((key.id >> 32) & 0xffff));
754 if (!TextUtils.isEmpty(key.name)) {
755 checksum.update(key.name.getBytes());
756 }
757 return checksum.getValue();
758 }
759
760 /** Serialize a Favorite for persistence, including a checksum wrapper. */
761 private byte[] packFavorite(Cursor c) {
762 Favorite favorite = new Favorite();
763 favorite.id = c.getLong(ID_INDEX);
764 favorite.screen = c.getInt(SCREEN_INDEX);
765 favorite.container = c.getInt(CONTAINER_INDEX);
766 favorite.cellX = c.getInt(CELLX_INDEX);
767 favorite.cellY = c.getInt(CELLY_INDEX);
768 favorite.spanX = c.getInt(SPANX_INDEX);
769 favorite.spanY = c.getInt(SPANY_INDEX);
770 favorite.iconType = c.getInt(ICON_TYPE_INDEX);
771 if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
772 String iconPackage = c.getString(ICON_PACKAGE_INDEX);
773 if (!TextUtils.isEmpty(iconPackage)) {
774 favorite.iconPackage = iconPackage;
775 }
776 String iconResource = c.getString(ICON_RESOURCE_INDEX);
777 if (!TextUtils.isEmpty(iconResource)) {
778 favorite.iconResource = iconResource;
779 }
780 }
781 if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
782 byte[] blob = c.getBlob(ICON_INDEX);
783 if (blob != null && blob.length > 0) {
784 favorite.icon = blob;
785 }
786 }
787 String title = c.getString(TITLE_INDEX);
788 if (!TextUtils.isEmpty(title)) {
789 favorite.title = title;
790 }
791 String intent = c.getString(INTENT_INDEX);
792 if (!TextUtils.isEmpty(intent)) {
793 favorite.intent = intent;
794 }
795 favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
796 if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
797 favorite.appWidgetId = c.getInt(APPWIDGET_ID_INDEX);
798 String appWidgetProvider = c.getString(APPWIDGET_PROVIDER_INDEX);
799 if (!TextUtils.isEmpty(appWidgetProvider)) {
800 favorite.appWidgetProvider = appWidgetProvider;
801 }
802 }
803
804 return writeCheckedBytes(favorite);
805 }
806
807 /** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
Chris Wren5dee7af2013-12-20 17:22:11 -0500808 private ContentValues unpackFavorite(byte[] buffer, int offset, int dataSize)
Chris Wren1ada10d2013-09-13 18:01:38 -0400809 throws InvalidProtocolBufferNanoException {
810 Favorite favorite = new Favorite();
811 MessageNano.mergeFrom(favorite, readCheckedBytes(buffer, offset, dataSize));
Chris Wren50c8f422014-01-15 16:10:39 -0500812 if (VERBOSE) Log.v(TAG, "unpacked favorite " + favorite.itemType + ", " +
813 (TextUtils.isEmpty(favorite.title) ? favorite.id : favorite.title));
Chris Wren5dee7af2013-12-20 17:22:11 -0500814 ContentValues values = new ContentValues();
815 values.put(Favorites._ID, favorite.id);
816 values.put(Favorites.SCREEN, favorite.screen);
817 values.put(Favorites.CONTAINER, favorite.container);
818 values.put(Favorites.CELLX, favorite.cellX);
819 values.put(Favorites.CELLY, favorite.cellY);
820 values.put(Favorites.SPANX, favorite.spanX);
821 values.put(Favorites.SPANY, favorite.spanY);
822 values.put(Favorites.ICON_TYPE, favorite.iconType);
823 if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
824 values.put(Favorites.ICON_PACKAGE, favorite.iconPackage);
825 values.put(Favorites.ICON_RESOURCE, favorite.iconResource);
826 }
827 if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
828 values.put(Favorites.ICON, favorite.icon);
829 }
830 if (!TextUtils.isEmpty(favorite.title)) {
831 values.put(Favorites.TITLE, favorite.title);
832 } else {
833 values.put(Favorites.TITLE, "");
834 }
835 if (!TextUtils.isEmpty(favorite.intent)) {
836 values.put(Favorites.INTENT, favorite.intent);
837 }
838 values.put(Favorites.ITEM_TYPE, favorite.itemType);
839 if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
840 if (!TextUtils.isEmpty(favorite.appWidgetProvider)) {
841 values.put(Favorites.APPWIDGET_PROVIDER, favorite.appWidgetProvider);
842 }
843 values.put(Favorites.APPWIDGET_ID, favorite.appWidgetId);
844 }
Chris Wrenf4d08112014-01-16 18:13:56 -0500845
846 // Let LauncherModel know we've been here.
847 values.put(LauncherSettings.Favorites.RESTORED, 1);
848
Chris Wren5dee7af2013-12-20 17:22:11 -0500849 return values;
Chris Wren1ada10d2013-09-13 18:01:38 -0400850 }
851
852 /** Serialize a Screen for persistence, including a checksum wrapper. */
853 private byte[] packScreen(Cursor c) {
854 Screen screen = new Screen();
855 screen.id = c.getLong(ID_INDEX);
856 screen.rank = c.getInt(SCREEN_RANK_INDEX);
857
858 return writeCheckedBytes(screen);
859 }
860
861 /** Deserialize a Screen from persistence, after verifying checksum wrapper. */
Chris Wren5dee7af2013-12-20 17:22:11 -0500862 private ContentValues unpackScreen(byte[] buffer, int offset, int dataSize)
Chris Wren1ada10d2013-09-13 18:01:38 -0400863 throws InvalidProtocolBufferNanoException {
864 Screen screen = new Screen();
865 MessageNano.mergeFrom(screen, readCheckedBytes(buffer, offset, dataSize));
Chris Wren50c8f422014-01-15 16:10:39 -0500866 if (VERBOSE) Log.v(TAG, "unpacked screen " + screen.id + "/" + screen.rank);
Chris Wren5dee7af2013-12-20 17:22:11 -0500867 ContentValues values = new ContentValues();
868 values.put(WorkspaceScreens._ID, screen.id);
869 values.put(WorkspaceScreens.SCREEN_RANK, screen.rank);
870 return values;
Chris Wren1ada10d2013-09-13 18:01:38 -0400871 }
872
Chris Wren22e130d2013-09-23 18:25:57 -0400873 /** Serialize an icon Resource for persistence, including a checksum wrapper. */
874 private byte[] packIcon(int dpi, Bitmap icon) {
875 Resource res = new Resource();
876 res.dpi = dpi;
877 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400878 if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wren22e130d2013-09-23 18:25:57 -0400879 res.data = os.toByteArray();
880 }
881 return writeCheckedBytes(res);
882 }
883
884 /** Deserialize an icon resource from persistence, after verifying checksum wrapper. */
885 private Resource unpackIcon(byte[] buffer, int offset, int dataSize)
886 throws InvalidProtocolBufferNanoException {
887 Resource res = new Resource();
888 MessageNano.mergeFrom(res, readCheckedBytes(buffer, offset, dataSize));
Chris Wren50c8f422014-01-15 16:10:39 -0500889 if (VERBOSE) Log.v(TAG, "unpacked icon " + res.dpi + "/" + res.data.length);
Chris Wren22e130d2013-09-23 18:25:57 -0400890 return res;
891 }
892
Chris Wrenfd13c712013-09-27 15:45:19 -0400893 /** Serialize a widget for persistence, including a checksum wrapper. */
894 private byte[] packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache,
895 ComponentName provider) {
896 final AppWidgetProviderInfo info = findAppWidgetProviderInfo(provider);
897 Widget widget = new Widget();
898 widget.provider = provider.flattenToShortString();
899 widget.label = info.label;
900 widget.configure = info.configure != null;
901 if (info.icon != 0) {
902 widget.icon = new Resource();
903 Drawable fullResIcon = iconCache.getFullResIcon(provider.getPackageName(), info.icon);
Chris Wren92aa4232013-10-04 11:29:36 -0400904 Bitmap icon = Utilities.createIconBitmap(fullResIcon, mContext);
Chris Wrenfd13c712013-09-27 15:45:19 -0400905 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400906 if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wrenfd13c712013-09-27 15:45:19 -0400907 widget.icon.data = os.toByteArray();
908 widget.icon.dpi = dpi;
909 }
910 }
911 if (info.previewImage != 0) {
912 widget.preview = new Resource();
913 Bitmap preview = previewLoader.generateWidgetPreview(info, null);
914 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400915 if (preview.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wrenfd13c712013-09-27 15:45:19 -0400916 widget.preview.data = os.toByteArray();
917 widget.preview.dpi = dpi;
918 }
919 }
920 return writeCheckedBytes(widget);
921 }
922
923 /** Deserialize a widget from persistence, after verifying checksum wrapper. */
924 private Widget unpackWidget(byte[] buffer, int offset, int dataSize)
925 throws InvalidProtocolBufferNanoException {
926 Widget widget = new Widget();
927 MessageNano.mergeFrom(widget, readCheckedBytes(buffer, offset, dataSize));
Chris Wren50c8f422014-01-15 16:10:39 -0500928 if (VERBOSE) Log.v(TAG, "unpacked widget " + widget.provider);
Chris Wrenfd13c712013-09-27 15:45:19 -0400929 return widget;
930 }
931
Chris Wren1ada10d2013-09-13 18:01:38 -0400932 /**
933 * Read the old journal from the input file.
934 *
935 * In the event of any error, just pretend we didn't have a journal,
936 * in that case, do a full backup.
937 *
938 * @param oldState the read-0only file descriptor pointing to the old journal
Chris Wren65b6a602014-01-10 14:11:25 -0500939 * @return a Journal protocol buffer
Chris Wren1ada10d2013-09-13 18:01:38 -0400940 */
941 private Journal readJournal(ParcelFileDescriptor oldState) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400942 Journal journal = new Journal();
Chris Wren92aa4232013-10-04 11:29:36 -0400943 if (oldState == null) {
944 return journal;
945 }
946 FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor());
947 try {
Chris Wren65b6a602014-01-10 14:11:25 -0500948 int availableBytes = inStream.available();
949 if (DEBUG) Log.d(TAG, "available " + availableBytes);
950 if (availableBytes < MAX_JOURNAL_SIZE) {
951 byte[] buffer = new byte[availableBytes];
Chris Wren1ada10d2013-09-13 18:01:38 -0400952 int bytesRead = 0;
Chris Wren65b6a602014-01-10 14:11:25 -0500953 boolean valid = false;
Chris Wren50c8f422014-01-15 16:10:39 -0500954 InvalidProtocolBufferNanoException lastProtoException = null;
Chris Wren65b6a602014-01-10 14:11:25 -0500955 while (availableBytes > 0) {
Chris Wren92aa4232013-10-04 11:29:36 -0400956 try {
Chris Wren65b6a602014-01-10 14:11:25 -0500957 // OMG what are you doing? This is crazy inefficient!
958 // If we read a byte that is not ours, we will cause trouble: b/12491813
959 // However, we don't know how many bytes to expect (oops).
960 // So we have to step through *slowly*, watching for the end.
961 int result = inStream.read(buffer, bytesRead, 1);
Chris Wren92aa4232013-10-04 11:29:36 -0400962 if (result > 0) {
Chris Wren65b6a602014-01-10 14:11:25 -0500963 availableBytes -= result;
Chris Wren92aa4232013-10-04 11:29:36 -0400964 bytesRead += result;
Chris Wren65b6a602014-01-10 14:11:25 -0500965 if (DEBUG && (bytesRead % 100 == 0)) {
966 Log.d(TAG, "read some bytes: " + bytesRead);
967 }
Chris Wren92aa4232013-10-04 11:29:36 -0400968 } else {
Chris Wren65b6a602014-01-10 14:11:25 -0500969 Log.w(TAG, "unexpected end of file while reading journal.");
970 // stop reading and see what there is to parse
971 availableBytes = 0;
Chris Wren92aa4232013-10-04 11:29:36 -0400972 }
973 } catch (IOException e) {
Chris Wren92aa4232013-10-04 11:29:36 -0400974 buffer = null;
Chris Wren65b6a602014-01-10 14:11:25 -0500975 availableBytes = 0;
976 }
977
978 // check the buffer to see if we have a valid journal
979 try {
980 MessageNano.mergeFrom(journal, readCheckedBytes(buffer, 0, bytesRead));
981 // if we are here, then we have read a valid, checksum-verified journal
982 valid = true;
983 availableBytes = 0;
Chris Wren50c8f422014-01-15 16:10:39 -0500984 if (VERBOSE) Log.v(TAG, "read " + bytesRead + " bytes of journal");
Chris Wren65b6a602014-01-10 14:11:25 -0500985 } catch (InvalidProtocolBufferNanoException e) {
986 // if we don't have the whole journal yet, mergeFrom will throw. keep going.
Chris Wren50c8f422014-01-15 16:10:39 -0500987 lastProtoException = e;
Chris Wren65b6a602014-01-10 14:11:25 -0500988 journal.clear();
Chris Wren92aa4232013-10-04 11:29:36 -0400989 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400990 }
Chris Wren92aa4232013-10-04 11:29:36 -0400991 if (DEBUG) Log.d(TAG, "journal bytes read: " + bytesRead);
Chris Wren65b6a602014-01-10 14:11:25 -0500992 if (!valid) {
Chris Wren50c8f422014-01-15 16:10:39 -0500993 Log.w(TAG, "could not find a valid journal", lastProtoException);
Chris Wren1ada10d2013-09-13 18:01:38 -0400994 }
995 }
Chris Wren92aa4232013-10-04 11:29:36 -0400996 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500997 Log.w(TAG, "failed to close the journal", e);
Chris Wren92aa4232013-10-04 11:29:36 -0400998 } finally {
Chris Wren1ada10d2013-09-13 18:01:38 -0400999 try {
1000 inStream.close();
1001 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -05001002 Log.w(TAG, "failed to close the journal", e);
Chris Wren1ada10d2013-09-13 18:01:38 -04001003 }
1004 }
1005 return journal;
1006 }
1007
Chris Wren22e130d2013-09-23 18:25:57 -04001008 private void writeRowToBackup(Key key, byte[] blob, Journal out,
1009 BackupDataOutput data) throws IOException {
1010 String backupKey = keyToBackupKey(key);
1011 data.writeEntityHeader(backupKey, blob.length);
1012 data.writeEntityData(blob, blob.length);
1013 out.rows++;
1014 out.bytes += blob.length;
Chris Wren50c8f422014-01-15 16:10:39 -05001015 if (VERBOSE) Log.v(TAG, "saving " + geKeyType(key) + " " + backupKey + ": " +
Chris Wren22e130d2013-09-23 18:25:57 -04001016 getKeyName(key) + "/" + blob.length);
Chris Wren92aa4232013-10-04 11:29:36 -04001017 if(DEBUG_PAYLOAD) {
Chris Wren2b6c21d2013-10-02 14:16:04 -04001018 String encoded = Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP);
1019 final int chunkSize = 1024;
1020 for (int offset = 0; offset < encoded.length(); offset += chunkSize) {
1021 int end = offset + chunkSize;
1022 end = Math.min(end, encoded.length());
Chris Wren50c8f422014-01-15 16:10:39 -05001023 Log.w(TAG, "wrote " + encoded.substring(offset, end));
Chris Wren2b6c21d2013-10-02 14:16:04 -04001024 }
1025 }
Chris Wren22e130d2013-09-23 18:25:57 -04001026 }
1027
1028 private Set<String> getSavedIdsByType(int type, Journal in) {
1029 Set<String> savedIds = new HashSet<String>();
1030 for(int i = 0; i < in.key.length; i++) {
1031 Key key = in.key[i];
1032 if (key.type == type) {
1033 savedIds.add(keyToBackupKey(key));
1034 }
1035 }
1036 return savedIds;
1037 }
1038
1039 private int removeDeletedKeysFromBackup(Set<String> deletedIds, BackupDataOutput data)
1040 throws IOException {
1041 int rows = 0;
1042 for(String deleted: deletedIds) {
Chris Wren50c8f422014-01-15 16:10:39 -05001043 if (VERBOSE) Log.v(TAG, "dropping deleted item " + deleted);
Chris Wren22e130d2013-09-23 18:25:57 -04001044 data.writeEntityHeader(deleted, -1);
1045 rows++;
1046 }
1047 return rows;
1048 }
1049
Chris Wren1ada10d2013-09-13 18:01:38 -04001050 /**
1051 * Write the new journal to the output file.
1052 *
1053 * In the event of any error, just pretend we didn't have a journal,
1054 * in that case, do a full backup.
1055
1056 * @param newState the write-only file descriptor pointing to the new journal
1057 * @param journal a Journal protocol buffer
1058 */
1059 private void writeJournal(ParcelFileDescriptor newState, Journal journal) {
1060 FileOutputStream outStream = null;
1061 try {
1062 outStream = new FileOutputStream(newState.getFileDescriptor());
Chris Wren65b6a602014-01-10 14:11:25 -05001063 final byte[] journalBytes = writeCheckedBytes(journal);
Chris Wren65b6a602014-01-10 14:11:25 -05001064 outStream.write(journalBytes);
Chris Wren1ada10d2013-09-13 18:01:38 -04001065 outStream.close();
Chris Wren50c8f422014-01-15 16:10:39 -05001066 if (VERBOSE) Log.v(TAG, "wrote " + journalBytes.length + " bytes of journal");
Chris Wren1ada10d2013-09-13 18:01:38 -04001067 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -05001068 Log.w(TAG, "failed to write backup journal", e);
Chris Wren1ada10d2013-09-13 18:01:38 -04001069 }
1070 }
1071
1072 /** Wrap a proto in a CheckedMessage and compute the checksum. */
1073 private byte[] writeCheckedBytes(MessageNano proto) {
1074 CheckedMessage wrapper = new CheckedMessage();
1075 wrapper.payload = MessageNano.toByteArray(proto);
1076 CRC32 checksum = new CRC32();
1077 checksum.update(wrapper.payload);
1078 wrapper.checksum = checksum.getValue();
1079 return MessageNano.toByteArray(wrapper);
1080 }
1081
1082 /** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
1083 private byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
1084 throws InvalidProtocolBufferNanoException {
1085 CheckedMessage wrapper = new CheckedMessage();
1086 MessageNano.mergeFrom(wrapper, buffer, offset, dataSize);
1087 CRC32 checksum = new CRC32();
1088 checksum.update(wrapper.payload);
1089 if (wrapper.checksum != checksum.getValue()) {
1090 throw new InvalidProtocolBufferNanoException("checksum does not match");
1091 }
1092 return wrapper.payload;
1093 }
1094
Chris Wrenfd13c712013-09-27 15:45:19 -04001095 private AppWidgetProviderInfo findAppWidgetProviderInfo(ComponentName component) {
1096 if (mWidgetMap == null) {
1097 List<AppWidgetProviderInfo> widgets =
Chris Wren92aa4232013-10-04 11:29:36 -04001098 AppWidgetManager.getInstance(mContext).getInstalledProviders();
Chris Wrenfd13c712013-09-27 15:45:19 -04001099 mWidgetMap = new HashMap<ComponentName, AppWidgetProviderInfo>(widgets.size());
1100 for (AppWidgetProviderInfo info : widgets) {
1101 mWidgetMap.put(info.provider, info);
1102 }
1103 }
1104 return mWidgetMap.get(component);
1105 }
1106
Chris Wren1ada10d2013-09-13 18:01:38 -04001107 private class KeyParsingException extends Throwable {
1108 private KeyParsingException(Throwable cause) {
1109 super(cause);
1110 }
1111
1112 public KeyParsingException(String reason) {
1113 super(reason);
1114 }
1115 }
1116}