blob: cab55c79e04bac37bc89dbbf02bd15f07c33f6ef [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 Wren6d0dde02014-02-10 12:16:54 -0500139 private static IconCache mIconCache;
140
Chris Wren92aa4232013-10-04 11:29:36 -0400141 private final Context mContext;
Chris Wren1ada10d2013-09-13 18:01:38 -0400142
Chris Wren4b171362014-01-13 17:39:02 -0500143 private final boolean mRestoreEnabled;
144
Chris Wren22e130d2013-09-23 18:25:57 -0400145 private HashMap<ComponentName, AppWidgetProviderInfo> mWidgetMap;
146
Chris Wren92aa4232013-10-04 11:29:36 -0400147 private ArrayList<Key> mKeys;
Chris Wren1ada10d2013-09-13 18:01:38 -0400148
Chris Wren4b171362014-01-13 17:39:02 -0500149 public LauncherBackupHelper(Context context, boolean restoreEnabled) {
Chris Wren92aa4232013-10-04 11:29:36 -0400150 mContext = context;
Chris Wren4b171362014-01-13 17:39:02 -0500151 mRestoreEnabled = restoreEnabled;
Chris Wren92aa4232013-10-04 11:29:36 -0400152 }
153
154 private void dataChanged() {
Chris Wren1ada10d2013-09-13 18:01:38 -0400155 if (sBackupManager == null) {
Chris Wren92aa4232013-10-04 11:29:36 -0400156 sBackupManager = new BackupManager(mContext);
Chris Wren1ada10d2013-09-13 18:01:38 -0400157 }
158 sBackupManager.dataChanged();
159 }
160
161 /**
162 * Back up launcher data so we can restore the user's state on a new device.
163 *
164 * <P>The journal is a timestamp and a list of keys that were saved as of that time.
165 *
166 * <P>Keys may come back in any order, so each key/value is one complete row of the database.
167 *
168 * @param oldState notes from the last backup
169 * @param data incremental key/value pairs to persist off-device
170 * @param newState notes for the next backup
171 * @throws IOException
172 */
173 @Override
Chris Wren92aa4232013-10-04 11:29:36 -0400174 public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
175 ParcelFileDescriptor newState) {
Chris Wren50c8f422014-01-15 16:10:39 -0500176 if (VERBOSE) Log.v(TAG, "onBackup");
Chris Wren1ada10d2013-09-13 18:01:38 -0400177
178 Journal in = readJournal(oldState);
179 Journal out = new Journal();
180
181 long lastBackupTime = in.t;
182 out.t = System.currentTimeMillis();
183 out.rows = 0;
184 out.bytes = 0;
185
Chris Wren50c8f422014-01-15 16:10:39 -0500186 Log.v(TAG, "lastBackupTime = " + lastBackupTime);
Chris Wren1ada10d2013-09-13 18:01:38 -0400187
188 ArrayList<Key> keys = new ArrayList<Key>();
Chris Wren71144262014-02-27 15:49:39 -0500189 if (launcherIsReady()) {
190 try {
191 backupFavorites(in, data, out, keys);
192 backupScreens(in, data, out, keys);
193 backupIcons(in, data, out, keys);
194 backupWidgets(in, data, out, keys);
195 } catch (IOException e) {
196 Log.e(TAG, "launcher backup has failed", e);
197 }
198 out.key = keys.toArray(new BackupProtos.Key[keys.size()]);
199 } else {
200 out = in;
Chris Wren92aa4232013-10-04 11:29:36 -0400201 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400202
Chris Wren1ada10d2013-09-13 18:01:38 -0400203 writeJournal(newState, out);
204 Log.v(TAG, "onBackup: wrote " + out.bytes + "b in " + out.rows + " rows.");
Chris Wren1ada10d2013-09-13 18:01:38 -0400205 }
206
207 /**
Chris Wren92aa4232013-10-04 11:29:36 -0400208 * Restore launcher configuration from the restored data stream.
Chris Wren1ada10d2013-09-13 18:01:38 -0400209 *
210 * <P>Keys may arrive in any order.
211 *
Chris Wren92aa4232013-10-04 11:29:36 -0400212 * @param data the key/value pair from the server
Chris Wren1ada10d2013-09-13 18:01:38 -0400213 */
214 @Override
Chris Wren92aa4232013-10-04 11:29:36 -0400215 public void restoreEntity(BackupDataInputStream data) {
Chris Wren50c8f422014-01-15 16:10:39 -0500216 if (VERBOSE) Log.v(TAG, "restoreEntity");
Chris Wren92aa4232013-10-04 11:29:36 -0400217 if (mKeys == null) {
218 mKeys = new ArrayList<Key>();
219 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400220 byte[] buffer = new byte[512];
Chris Wren1ada10d2013-09-13 18:01:38 -0400221 String backupKey = data.getKey();
Chris Wren92aa4232013-10-04 11:29:36 -0400222 int dataSize = data.size();
Chris Wren1ada10d2013-09-13 18:01:38 -0400223 if (buffer.length < dataSize) {
224 buffer = new byte[dataSize];
225 }
226 Key key = null;
Chris Wren92aa4232013-10-04 11:29:36 -0400227 int bytesRead = 0;
228 try {
229 bytesRead = data.read(buffer, 0, dataSize);
230 if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
231 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500232 Log.e(TAG, "failed to read entity from restore data", e);
Chris Wren92aa4232013-10-04 11:29:36 -0400233 }
234 try {
235 key = backupKeyToKey(backupKey);
Chris Wren50c8f422014-01-15 16:10:39 -0500236 mKeys.add(key);
Chris Wren92aa4232013-10-04 11:29:36 -0400237 switch (key.type) {
238 case Key.FAVORITE:
239 restoreFavorite(key, buffer, dataSize, mKeys);
240 break;
241
242 case Key.SCREEN:
243 restoreScreen(key, buffer, dataSize, mKeys);
244 break;
245
246 case Key.ICON:
247 restoreIcon(key, buffer, dataSize, mKeys);
248 break;
249
250 case Key.WIDGET:
251 restoreWidget(key, buffer, dataSize, mKeys);
252 break;
253
254 default:
255 Log.w(TAG, "unknown restore entity type: " + key.type);
256 break;
Chris Wren1ada10d2013-09-13 18:01:38 -0400257 }
Chris Wren92aa4232013-10-04 11:29:36 -0400258 } catch (KeyParsingException e) {
259 Log.w(TAG, "ignoring unparsable backup key: " + backupKey);
Chris Wren1ada10d2013-09-13 18:01:38 -0400260 }
261
Chris Wren92aa4232013-10-04 11:29:36 -0400262 }
263
264 /**
265 * Record the restore state for the next backup.
266 *
267 * @param newState notes about the backup state after restore.
268 */
269 @Override
270 public void writeNewStateDescription(ParcelFileDescriptor newState) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400271 // clear the output journal time, to force a full backup to
272 // will catch any changes the restore process might have made
Chris Wren92aa4232013-10-04 11:29:36 -0400273 Journal out = new Journal();
Chris Wren1ada10d2013-09-13 18:01:38 -0400274 out.t = 0;
Mac Duy Hai22dc65e2014-02-05 10:52:07 +0000275 out.key = mKeys.toArray(new BackupProtos.Key[mKeys.size()]);
Chris Wren1ada10d2013-09-13 18:01:38 -0400276 writeJournal(newState, out);
Chris Wren92aa4232013-10-04 11:29:36 -0400277 Log.v(TAG, "onRestore: read " + mKeys.size() + " rows");
278 mKeys.clear();
Chris Wren1ada10d2013-09-13 18:01:38 -0400279 }
280
281 /**
282 * Write all modified favorites to the data stream.
283 *
284 *
285 * @param in notes from last backup
286 * @param data output stream for key/value pairs
287 * @param out notes about this backup
288 * @param keys keys to mark as clean in the notes for next backup
289 * @throws IOException
290 */
291 private void backupFavorites(Journal in, BackupDataOutput data, Journal out,
292 ArrayList<Key> keys)
293 throws IOException {
294 // read the old ID set
Chris Wren22e130d2013-09-23 18:25:57 -0400295 Set<String> savedIds = getSavedIdsByType(Key.FAVORITE, in);
Chris Wren1ada10d2013-09-13 18:01:38 -0400296 if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
297
298 // persist things that have changed since the last backup
Chris Wren92aa4232013-10-04 11:29:36 -0400299 ContentResolver cr = mContext.getContentResolver();
Chris Wren22e130d2013-09-23 18:25:57 -0400300 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
301 null, null, null);
302 Set<String> currentIds = new HashSet<String>(cursor.getCount());
Chris Wren1ada10d2013-09-13 18:01:38 -0400303 try {
Chris Wren22e130d2013-09-23 18:25:57 -0400304 cursor.moveToPosition(-1);
305 while(cursor.moveToNext()) {
306 final long id = cursor.getLong(ID_INDEX);
307 final long updateTime = cursor.getLong(ID_MODIFIED);
Chris Wren1ada10d2013-09-13 18:01:38 -0400308 Key key = getKey(Key.FAVORITE, id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400309 keys.add(key);
Chris Wren5743aa92014-01-10 18:02:06 -0500310 final String backupKey = keyToBackupKey(key);
311 currentIds.add(backupKey);
312 if (!savedIds.contains(backupKey) || updateTime >= in.t) {
Chris Wren22e130d2013-09-23 18:25:57 -0400313 byte[] blob = packFavorite(cursor);
314 writeRowToBackup(key, blob, out, data);
Chris Wren50c8f422014-01-15 16:10:39 -0500315 } else {
316 if (VERBOSE) Log.v(TAG, "favorite " + id + " was too old: " + updateTime);
Chris Wren22e130d2013-09-23 18:25:57 -0400317 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400318 }
319 } finally {
Chris Wren22e130d2013-09-23 18:25:57 -0400320 cursor.close();
Chris Wren1ada10d2013-09-13 18:01:38 -0400321 }
322 if (DEBUG) Log.d(TAG, "favorite currentIds.size()=" + currentIds.size());
323
324 // these IDs must have been deleted
325 savedIds.removeAll(currentIds);
Chris Wren22e130d2013-09-23 18:25:57 -0400326 out.rows += removeDeletedKeysFromBackup(savedIds, data);
Chris Wren1ada10d2013-09-13 18:01:38 -0400327 }
328
329 /**
330 * Read a favorite from the stream.
331 *
332 * <P>Keys arrive in any order, so screens and containers may not exist yet.
333 *
334 * @param key identifier for the row
335 * @param buffer the serialized proto from the stream, may be larger than dataSize
336 * @param dataSize the size of the proto from the stream
337 * @param keys keys to mark as clean in the notes for next backup
338 */
339 private void restoreFavorite(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
Chris Wren50c8f422014-01-15 16:10:39 -0500340 if (VERBOSE) Log.v(TAG, "unpacking favorite " + key.id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400341 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
342 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
343
Chris Wren5dee7af2013-12-20 17:22:11 -0500344 if (!mRestoreEnabled) {
Chris Wren50c8f422014-01-15 16:10:39 -0500345 if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
Chris Wren5dee7af2013-12-20 17:22:11 -0500346 return;
347 }
348
Chris Wren1ada10d2013-09-13 18:01:38 -0400349 try {
Chris Wren5dee7af2013-12-20 17:22:11 -0500350 ContentResolver cr = mContext.getContentResolver();
351 ContentValues values = unpackFavorite(buffer, 0, dataSize);
352 cr.insert(Favorites.CONTENT_URI, values);
Chris Wren1ada10d2013-09-13 18:01:38 -0400353 } catch (InvalidProtocolBufferNanoException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500354 Log.e(TAG, "failed to decode favorite", e);
Chris Wren1ada10d2013-09-13 18:01:38 -0400355 }
356 }
357
358 /**
359 * Write all modified screens to the data stream.
360 *
361 *
362 * @param in notes from last backup
363 * @param data output stream for key/value pairs
364 * @param out notes about this backup
Chris Wren22e130d2013-09-23 18:25:57 -0400365 * @param keys keys to mark as clean in the notes for next backup
366 * @throws IOException
Chris Wren1ada10d2013-09-13 18:01:38 -0400367 */
368 private void backupScreens(Journal in, BackupDataOutput data, Journal out,
369 ArrayList<Key> keys)
370 throws IOException {
371 // read the old ID set
Chris Wren22e130d2013-09-23 18:25:57 -0400372 Set<String> savedIds = getSavedIdsByType(Key.SCREEN, in);
373 if (DEBUG) Log.d(TAG, "screen savedIds.size()=" + savedIds.size());
Chris Wren1ada10d2013-09-13 18:01:38 -0400374
375 // persist things that have changed since the last backup
Chris Wren92aa4232013-10-04 11:29:36 -0400376 ContentResolver cr = mContext.getContentResolver();
Chris Wren22e130d2013-09-23 18:25:57 -0400377 Cursor cursor = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
378 null, null, null);
379 Set<String> currentIds = new HashSet<String>(cursor.getCount());
Chris Wren1ada10d2013-09-13 18:01:38 -0400380 try {
Chris Wren22e130d2013-09-23 18:25:57 -0400381 cursor.moveToPosition(-1);
Chris Wren50c8f422014-01-15 16:10:39 -0500382 if (DEBUG) Log.d(TAG, "dumping screens after: " + in.t);
Chris Wren22e130d2013-09-23 18:25:57 -0400383 while(cursor.moveToNext()) {
384 final long id = cursor.getLong(ID_INDEX);
385 final long updateTime = cursor.getLong(ID_MODIFIED);
Chris Wren1ada10d2013-09-13 18:01:38 -0400386 Key key = getKey(Key.SCREEN, id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400387 keys.add(key);
Chris Wren5743aa92014-01-10 18:02:06 -0500388 final String backupKey = keyToBackupKey(key);
389 currentIds.add(backupKey);
390 if (!savedIds.contains(backupKey) || updateTime >= in.t) {
Chris Wren22e130d2013-09-23 18:25:57 -0400391 byte[] blob = packScreen(cursor);
392 writeRowToBackup(key, blob, out, data);
Chris Wren5dee7af2013-12-20 17:22:11 -0500393 } else {
Chris Wren50c8f422014-01-15 16:10:39 -0500394 if (VERBOSE) Log.v(TAG, "screen " + id + " was too old: " + updateTime);
Chris Wren22e130d2013-09-23 18:25:57 -0400395 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400396 }
397 } finally {
Chris Wren22e130d2013-09-23 18:25:57 -0400398 cursor.close();
Chris Wren1ada10d2013-09-13 18:01:38 -0400399 }
400 if (DEBUG) Log.d(TAG, "screen currentIds.size()=" + currentIds.size());
401
402 // these IDs must have been deleted
403 savedIds.removeAll(currentIds);
Chris Wren22e130d2013-09-23 18:25:57 -0400404 out.rows += removeDeletedKeysFromBackup(savedIds, data);
Chris Wren1ada10d2013-09-13 18:01:38 -0400405 }
406
407 /**
408 * Read a screen from the stream.
409 *
410 * <P>Keys arrive in any order, so children of this screen may already exist.
411 *
412 * @param key identifier for the row
413 * @param buffer the serialized proto from the stream, may be larger than dataSize
414 * @param dataSize the size of the proto from the stream
415 * @param keys keys to mark as clean in the notes for next backup
416 */
417 private void restoreScreen(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
Chris Wren50c8f422014-01-15 16:10:39 -0500418 if (VERBOSE) Log.v(TAG, "unpacking screen " + key.id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400419 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
420 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
Chris Wren5dee7af2013-12-20 17:22:11 -0500421
422 if (!mRestoreEnabled) {
Chris Wren50c8f422014-01-15 16:10:39 -0500423 if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
Chris Wren5dee7af2013-12-20 17:22:11 -0500424 return;
425 }
426
Chris Wren1ada10d2013-09-13 18:01:38 -0400427 try {
Chris Wren5dee7af2013-12-20 17:22:11 -0500428 ContentResolver cr = mContext.getContentResolver();
429 ContentValues values = unpackScreen(buffer, 0, dataSize);
430 cr.insert(WorkspaceScreens.CONTENT_URI, values);
431
Chris Wren1ada10d2013-09-13 18:01:38 -0400432 } catch (InvalidProtocolBufferNanoException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500433 Log.e(TAG, "failed to decode screen", e);
Chris Wren1ada10d2013-09-13 18:01:38 -0400434 }
435 }
436
Chris Wren22e130d2013-09-23 18:25:57 -0400437 /**
438 * Write all the static icon resources we need to render placeholders
439 * for a package that is not installed.
440 *
441 * @param in notes from last backup
442 * @param data output stream for key/value pairs
443 * @param out notes about this backup
444 * @param keys keys to mark as clean in the notes for next backup
445 * @throws IOException
446 */
447 private void backupIcons(Journal in, BackupDataOutput data, Journal out,
448 ArrayList<Key> keys) throws IOException {
Chris Wrenfd13c712013-09-27 15:45:19 -0400449 // persist icons that haven't been persisted yet
Chris Wren6d0dde02014-02-10 12:16:54 -0500450 if (!initializeIconCache()) {
Chris Wren92aa4232013-10-04 11:29:36 -0400451 dataChanged(); // try again later
Chris Wrend8fe6de2013-10-04 10:42:14 -0400452 if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying icon backup");
453 return;
454 }
Chris Wren92aa4232013-10-04 11:29:36 -0400455 final ContentResolver cr = mContext.getContentResolver();
Chris Wren92aa4232013-10-04 11:29:36 -0400456 final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
Chris Wren22e130d2013-09-23 18:25:57 -0400457
458 // read the old ID set
459 Set<String> savedIds = getSavedIdsByType(Key.ICON, in);
460 if (DEBUG) Log.d(TAG, "icon savedIds.size()=" + savedIds.size());
461
462 int startRows = out.rows;
463 if (DEBUG) Log.d(TAG, "starting here: " + startRows);
Chris Wren5160e962014-03-13 11:41:59 -0400464 String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION + " OR " +
465 Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT;
Chris Wren22e130d2013-09-23 18:25:57 -0400466 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
467 where, null, null);
468 Set<String> currentIds = new HashSet<String>(cursor.getCount());
469 try {
470 cursor.moveToPosition(-1);
471 while(cursor.moveToNext()) {
472 final long id = cursor.getLong(ID_INDEX);
473 final String intentDescription = cursor.getString(INTENT_INDEX);
474 try {
475 Intent intent = Intent.parseUri(intentDescription, 0);
476 ComponentName cn = intent.getComponent();
477 Key key = null;
478 String backupKey = null;
479 if (cn != null) {
480 key = getKey(Key.ICON, cn.flattenToShortString());
481 backupKey = keyToBackupKey(key);
482 currentIds.add(backupKey);
483 } else {
484 Log.w(TAG, "empty intent on application favorite: " + id);
485 }
486 if (savedIds.contains(backupKey)) {
Chris Wren50c8f422014-01-15 16:10:39 -0500487 if (VERBOSE) Log.v(TAG, "already saved icon " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -0400488
489 // remember that we already backed this up previously
490 keys.add(key);
491 } else if (backupKey != null) {
492 if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
493 if ((out.rows - startRows) < MAX_ICONS_PER_PASS) {
Chris Wren50c8f422014-01-15 16:10:39 -0500494 if (VERBOSE) Log.v(TAG, "saving icon " + backupKey);
Chris Wren6d0dde02014-02-10 12:16:54 -0500495 Bitmap icon = mIconCache.getIcon(intent);
Chris Wren22e130d2013-09-23 18:25:57 -0400496 keys.add(key);
Chris Wren6d0dde02014-02-10 12:16:54 -0500497 if (icon != null && !mIconCache.isDefaultIcon(icon)) {
Chris Wren22e130d2013-09-23 18:25:57 -0400498 byte[] blob = packIcon(dpi, icon);
499 writeRowToBackup(key, blob, out, data);
500 }
501 } else {
Chris Wren50c8f422014-01-15 16:10:39 -0500502 if (VERBOSE) Log.d(TAG, "deferring icon backup " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -0400503 // too many icons for this pass, request another.
Chris Wren92aa4232013-10-04 11:29:36 -0400504 dataChanged();
Chris Wren22e130d2013-09-23 18:25:57 -0400505 }
506 }
507 } catch (URISyntaxException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500508 Log.e(TAG, "invalid URI on application favorite: " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400509 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500510 Log.e(TAG, "unable to save application icon for favorite: " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400511 }
512
513 }
514 } finally {
515 cursor.close();
516 }
517 if (DEBUG) Log.d(TAG, "icon currentIds.size()=" + currentIds.size());
518
519 // these IDs must have been deleted
520 savedIds.removeAll(currentIds);
521 out.rows += removeDeletedKeysFromBackup(savedIds, data);
522 }
523
524 /**
525 * Read an icon from the stream.
526 *
Chris Wrenfd13c712013-09-27 15:45:19 -0400527 * <P>Keys arrive in any order, so shortcuts that use this icon may already exist.
Chris Wren22e130d2013-09-23 18:25:57 -0400528 *
529 * @param key identifier for the row
530 * @param buffer the serialized proto from the stream, may be larger than dataSize
531 * @param dataSize the size of the proto from the stream
532 * @param keys keys to mark as clean in the notes for next backup
533 */
534 private void restoreIcon(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
Chris Wren50c8f422014-01-15 16:10:39 -0500535 if (VERBOSE) Log.v(TAG, "unpacking icon " + key.id);
Chris Wren22e130d2013-09-23 18:25:57 -0400536 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
537 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
Chris Wren6d0dde02014-02-10 12:16:54 -0500538
Chris Wren22e130d2013-09-23 18:25:57 -0400539 try {
540 Resource res = unpackIcon(buffer, 0, dataSize);
Chris Wren6d0dde02014-02-10 12:16:54 -0500541 if (DEBUG) {
542 Log.d(TAG, "unpacked " + res.dpi + " dpi icon");
543 }
544 if (DEBUG_PAYLOAD) {
545 Log.d(TAG, "read " +
546 Base64.encodeToString(res.data, 0, res.data.length,
547 Base64.NO_WRAP));
548 }
Chris Wren22e130d2013-09-23 18:25:57 -0400549 Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
550 if (icon == null) {
551 Log.w(TAG, "failed to unpack icon for " + key.name);
552 }
Chris Wren5dee7af2013-12-20 17:22:11 -0500553
554 if (!mRestoreEnabled) {
Chris Wren6d0dde02014-02-10 12:16:54 -0500555 if (VERBOSE) {
556 Log.v(TAG, "restore not enabled: skipping database mutation");
557 }
Chris Wren5dee7af2013-12-20 17:22:11 -0500558 return;
559 } else {
Chris Wren6d0dde02014-02-10 12:16:54 -0500560 IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(key.name),
561 icon, res.dpi);
Chris Wren5dee7af2013-12-20 17:22:11 -0500562 }
Chris Wren6d0dde02014-02-10 12:16:54 -0500563 } catch (IOException e) {
564 Log.d(TAG, "failed to save restored icon for: " + key.name, e);
Chris Wren22e130d2013-09-23 18:25:57 -0400565 }
566 }
567
Chris Wrenfd13c712013-09-27 15:45:19 -0400568 /**
569 * Write all the static widget resources we need to render placeholders
570 * for a package that is not installed.
571 *
572 * @param in notes from last backup
573 * @param data output stream for key/value pairs
574 * @param out notes about this backup
575 * @param keys keys to mark as clean in the notes for next backup
576 * @throws IOException
577 */
578 private void backupWidgets(Journal in, BackupDataOutput data, Journal out,
579 ArrayList<Key> keys) throws IOException {
580 // persist static widget info that hasn't been persisted yet
Chris Wrend8fe6de2013-10-04 10:42:14 -0400581 final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
Chris Wren6d0dde02014-02-10 12:16:54 -0500582 if (appState == null || !initializeIconCache()) {
583 Log.w(TAG, "Failed to get icon cache during restore");
Chris Wrend8fe6de2013-10-04 10:42:14 -0400584 return;
585 }
Chris Wren92aa4232013-10-04 11:29:36 -0400586 final ContentResolver cr = mContext.getContentResolver();
587 final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext);
588 final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(mContext);
Chris Wren92aa4232013-10-04 11:29:36 -0400589 final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
Chris Wrenfd13c712013-09-27 15:45:19 -0400590 final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
591 if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
592
593 // read the old ID set
594 Set<String> savedIds = getSavedIdsByType(Key.WIDGET, in);
595 if (DEBUG) Log.d(TAG, "widgets savedIds.size()=" + savedIds.size());
596
597 int startRows = out.rows;
598 if (DEBUG) Log.d(TAG, "starting here: " + startRows);
599 String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPWIDGET;
600 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
601 where, null, null);
602 Set<String> currentIds = new HashSet<String>(cursor.getCount());
603 try {
604 cursor.moveToPosition(-1);
605 while(cursor.moveToNext()) {
606 final long id = cursor.getLong(ID_INDEX);
607 final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX);
608 final int spanX = cursor.getInt(SPANX_INDEX);
609 final int spanY = cursor.getInt(SPANY_INDEX);
610 final ComponentName provider = ComponentName.unflattenFromString(providerName);
611 Key key = null;
612 String backupKey = null;
613 if (provider != null) {
614 key = getKey(Key.WIDGET, providerName);
615 backupKey = keyToBackupKey(key);
616 currentIds.add(backupKey);
617 } else {
618 Log.w(TAG, "empty intent on appwidget: " + id);
619 }
620 if (savedIds.contains(backupKey)) {
Chris Wren50c8f422014-01-15 16:10:39 -0500621 if (VERBOSE) Log.v(TAG, "already saved widget " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400622
623 // remember that we already backed this up previously
624 keys.add(key);
625 } else if (backupKey != null) {
626 if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
627 if ((out.rows - startRows) < MAX_WIDGETS_PER_PASS) {
Chris Wren50c8f422014-01-15 16:10:39 -0500628 if (VERBOSE) Log.v(TAG, "saving widget " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400629 previewLoader.setPreviewSize(spanX * profile.cellWidthPx,
630 spanY * profile.cellHeightPx, widgetSpacingLayout);
Chris Wren6d0dde02014-02-10 12:16:54 -0500631 byte[] blob = packWidget(dpi, previewLoader, mIconCache, provider);
Chris Wrenb1fd63b2013-10-03 15:43:58 -0400632 keys.add(key);
Chris Wrenfd13c712013-09-27 15:45:19 -0400633 writeRowToBackup(key, blob, out, data);
634
635 } else {
Chris Wren50c8f422014-01-15 16:10:39 -0500636 if (VERBOSE) Log.d(TAG, "deferring widget backup " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400637 // too many widgets for this pass, request another.
Chris Wren92aa4232013-10-04 11:29:36 -0400638 dataChanged();
Chris Wrenfd13c712013-09-27 15:45:19 -0400639 }
640 }
641 }
642 } finally {
643 cursor.close();
644 }
645 if (DEBUG) Log.d(TAG, "widget currentIds.size()=" + currentIds.size());
646
647 // these IDs must have been deleted
648 savedIds.removeAll(currentIds);
649 out.rows += removeDeletedKeysFromBackup(savedIds, data);
650 }
651
652 /**
653 * Read a widget from the stream.
654 *
655 * <P>Keys arrive in any order, so widgets that use this data may already exist.
656 *
657 * @param key identifier for the row
658 * @param buffer the serialized proto from the stream, may be larger than dataSize
659 * @param dataSize the size of the proto from the stream
660 * @param keys keys to mark as clean in the notes for next backup
661 */
662 private void restoreWidget(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
Chris Wren50c8f422014-01-15 16:10:39 -0500663 if (VERBOSE) Log.v(TAG, "unpacking widget " + key.id);
Chris Wrenfd13c712013-09-27 15:45:19 -0400664 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
665 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
666 try {
667 Widget widget = unpackWidget(buffer, 0, dataSize);
668 if (DEBUG) Log.d(TAG, "unpacked " + widget.provider);
669 if (widget.icon.data != null) {
670 Bitmap icon = BitmapFactory
671 .decodeByteArray(widget.icon.data, 0, widget.icon.data.length);
672 if (icon == null) {
673 Log.w(TAG, "failed to unpack widget icon for " + key.name);
674 }
675 }
Chris Wren5dee7af2013-12-20 17:22:11 -0500676
677 if (!mRestoreEnabled) {
Chris Wren50c8f422014-01-15 16:10:39 -0500678 if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
Chris Wren5dee7af2013-12-20 17:22:11 -0500679 return;
680 } else {
681 // future site of widget table mutation
682 }
Chris Wrenfd13c712013-09-27 15:45:19 -0400683 } catch (InvalidProtocolBufferNanoException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500684 Log.e(TAG, "failed to decode widget", e);
Chris Wrenfd13c712013-09-27 15:45:19 -0400685 }
686 }
687
Chris Wren22e130d2013-09-23 18:25:57 -0400688 /** create a new key, with an integer ID.
Chris Wren1ada10d2013-09-13 18:01:38 -0400689 *
690 * <P> Keys contain their own checksum instead of using
691 * the heavy-weight CheckedMessage wrapper.
692 */
693 private Key getKey(int type, long id) {
694 Key key = new Key();
695 key.type = type;
696 key.id = id;
697 key.checksum = checkKey(key);
698 return key;
699 }
700
Chris Wren22e130d2013-09-23 18:25:57 -0400701 /** create a new key for a named object.
702 *
703 * <P> Keys contain their own checksum instead of using
704 * the heavy-weight CheckedMessage wrapper.
705 */
706 private Key getKey(int type, String name) {
707 Key key = new Key();
708 key.type = type;
709 key.name = name;
710 key.checksum = checkKey(key);
711 return key;
712 }
713
Chris Wren1ada10d2013-09-13 18:01:38 -0400714 /** keys need to be strings, serialize and encode. */
715 private String keyToBackupKey(Key key) {
Chris Wren978194c2013-10-03 17:47:22 -0400716 return Base64.encodeToString(Key.toByteArray(key), Base64.NO_WRAP);
Chris Wren1ada10d2013-09-13 18:01:38 -0400717 }
718
719 /** keys need to be strings, decode and parse. */
720 private Key backupKeyToKey(String backupKey) throws KeyParsingException {
721 try {
722 Key key = Key.parseFrom(Base64.decode(backupKey, Base64.DEFAULT));
723 if (key.checksum != checkKey(key)) {
724 key = null;
725 throw new KeyParsingException("invalid key read from stream" + backupKey);
726 }
727 return key;
728 } catch (InvalidProtocolBufferNanoException e) {
729 throw new KeyParsingException(e);
730 } catch (IllegalArgumentException e) {
731 throw new KeyParsingException(e);
732 }
733 }
734
Chris Wren22e130d2013-09-23 18:25:57 -0400735 private String getKeyName(Key key) {
736 if (TextUtils.isEmpty(key.name)) {
737 return Long.toString(key.id);
738 } else {
739 return key.name;
740 }
741
742 }
743
744 private String geKeyType(Key key) {
745 switch (key.type) {
746 case Key.FAVORITE:
747 return "favorite";
748 case Key.SCREEN:
749 return "screen";
750 case Key.ICON:
751 return "icon";
Chris Wrenfd13c712013-09-27 15:45:19 -0400752 case Key.WIDGET:
753 return "widget";
Chris Wren22e130d2013-09-23 18:25:57 -0400754 default:
755 return "anonymous";
756 }
757 }
758
Chris Wren1ada10d2013-09-13 18:01:38 -0400759 /** Compute the checksum over the important bits of a key. */
760 private long checkKey(Key key) {
761 CRC32 checksum = new CRC32();
762 checksum.update(key.type);
763 checksum.update((int) (key.id & 0xffff));
764 checksum.update((int) ((key.id >> 32) & 0xffff));
765 if (!TextUtils.isEmpty(key.name)) {
766 checksum.update(key.name.getBytes());
767 }
768 return checksum.getValue();
769 }
770
771 /** Serialize a Favorite for persistence, including a checksum wrapper. */
772 private byte[] packFavorite(Cursor c) {
773 Favorite favorite = new Favorite();
774 favorite.id = c.getLong(ID_INDEX);
775 favorite.screen = c.getInt(SCREEN_INDEX);
776 favorite.container = c.getInt(CONTAINER_INDEX);
777 favorite.cellX = c.getInt(CELLX_INDEX);
778 favorite.cellY = c.getInt(CELLY_INDEX);
779 favorite.spanX = c.getInt(SPANX_INDEX);
780 favorite.spanY = c.getInt(SPANY_INDEX);
781 favorite.iconType = c.getInt(ICON_TYPE_INDEX);
782 if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
783 String iconPackage = c.getString(ICON_PACKAGE_INDEX);
784 if (!TextUtils.isEmpty(iconPackage)) {
785 favorite.iconPackage = iconPackage;
786 }
787 String iconResource = c.getString(ICON_RESOURCE_INDEX);
788 if (!TextUtils.isEmpty(iconResource)) {
789 favorite.iconResource = iconResource;
790 }
791 }
792 if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
793 byte[] blob = c.getBlob(ICON_INDEX);
794 if (blob != null && blob.length > 0) {
795 favorite.icon = blob;
796 }
797 }
798 String title = c.getString(TITLE_INDEX);
799 if (!TextUtils.isEmpty(title)) {
800 favorite.title = title;
801 }
802 String intent = c.getString(INTENT_INDEX);
803 if (!TextUtils.isEmpty(intent)) {
804 favorite.intent = intent;
805 }
806 favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
807 if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
808 favorite.appWidgetId = c.getInt(APPWIDGET_ID_INDEX);
809 String appWidgetProvider = c.getString(APPWIDGET_PROVIDER_INDEX);
810 if (!TextUtils.isEmpty(appWidgetProvider)) {
811 favorite.appWidgetProvider = appWidgetProvider;
812 }
813 }
814
815 return writeCheckedBytes(favorite);
816 }
817
818 /** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
Chris Wren5dee7af2013-12-20 17:22:11 -0500819 private ContentValues unpackFavorite(byte[] buffer, int offset, int dataSize)
Chris Wren1ada10d2013-09-13 18:01:38 -0400820 throws InvalidProtocolBufferNanoException {
821 Favorite favorite = new Favorite();
822 MessageNano.mergeFrom(favorite, readCheckedBytes(buffer, offset, dataSize));
Chris Wren50c8f422014-01-15 16:10:39 -0500823 if (VERBOSE) Log.v(TAG, "unpacked favorite " + favorite.itemType + ", " +
824 (TextUtils.isEmpty(favorite.title) ? favorite.id : favorite.title));
Chris Wren5dee7af2013-12-20 17:22:11 -0500825 ContentValues values = new ContentValues();
826 values.put(Favorites._ID, favorite.id);
827 values.put(Favorites.SCREEN, favorite.screen);
828 values.put(Favorites.CONTAINER, favorite.container);
829 values.put(Favorites.CELLX, favorite.cellX);
830 values.put(Favorites.CELLY, favorite.cellY);
831 values.put(Favorites.SPANX, favorite.spanX);
832 values.put(Favorites.SPANY, favorite.spanY);
833 values.put(Favorites.ICON_TYPE, favorite.iconType);
834 if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
835 values.put(Favorites.ICON_PACKAGE, favorite.iconPackage);
836 values.put(Favorites.ICON_RESOURCE, favorite.iconResource);
837 }
838 if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
839 values.put(Favorites.ICON, favorite.icon);
840 }
841 if (!TextUtils.isEmpty(favorite.title)) {
842 values.put(Favorites.TITLE, favorite.title);
843 } else {
844 values.put(Favorites.TITLE, "");
845 }
846 if (!TextUtils.isEmpty(favorite.intent)) {
847 values.put(Favorites.INTENT, favorite.intent);
848 }
849 values.put(Favorites.ITEM_TYPE, favorite.itemType);
850 if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
851 if (!TextUtils.isEmpty(favorite.appWidgetProvider)) {
852 values.put(Favorites.APPWIDGET_PROVIDER, favorite.appWidgetProvider);
853 }
854 values.put(Favorites.APPWIDGET_ID, favorite.appWidgetId);
855 }
Chris Wrenf4d08112014-01-16 18:13:56 -0500856
857 // Let LauncherModel know we've been here.
858 values.put(LauncherSettings.Favorites.RESTORED, 1);
859
Chris Wren5dee7af2013-12-20 17:22:11 -0500860 return values;
Chris Wren1ada10d2013-09-13 18:01:38 -0400861 }
862
863 /** Serialize a Screen for persistence, including a checksum wrapper. */
864 private byte[] packScreen(Cursor c) {
865 Screen screen = new Screen();
866 screen.id = c.getLong(ID_INDEX);
867 screen.rank = c.getInt(SCREEN_RANK_INDEX);
868
869 return writeCheckedBytes(screen);
870 }
871
872 /** Deserialize a Screen from persistence, after verifying checksum wrapper. */
Chris Wren5dee7af2013-12-20 17:22:11 -0500873 private ContentValues unpackScreen(byte[] buffer, int offset, int dataSize)
Chris Wren1ada10d2013-09-13 18:01:38 -0400874 throws InvalidProtocolBufferNanoException {
875 Screen screen = new Screen();
876 MessageNano.mergeFrom(screen, readCheckedBytes(buffer, offset, dataSize));
Chris Wren50c8f422014-01-15 16:10:39 -0500877 if (VERBOSE) Log.v(TAG, "unpacked screen " + screen.id + "/" + screen.rank);
Chris Wren5dee7af2013-12-20 17:22:11 -0500878 ContentValues values = new ContentValues();
879 values.put(WorkspaceScreens._ID, screen.id);
880 values.put(WorkspaceScreens.SCREEN_RANK, screen.rank);
881 return values;
Chris Wren1ada10d2013-09-13 18:01:38 -0400882 }
883
Chris Wren22e130d2013-09-23 18:25:57 -0400884 /** Serialize an icon Resource for persistence, including a checksum wrapper. */
885 private byte[] packIcon(int dpi, Bitmap icon) {
886 Resource res = new Resource();
887 res.dpi = dpi;
888 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400889 if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wren22e130d2013-09-23 18:25:57 -0400890 res.data = os.toByteArray();
891 }
892 return writeCheckedBytes(res);
893 }
894
895 /** Deserialize an icon resource from persistence, after verifying checksum wrapper. */
Chris Wren6d0dde02014-02-10 12:16:54 -0500896 private static Resource unpackIcon(byte[] buffer, int offset, int dataSize)
Chris Wren22e130d2013-09-23 18:25:57 -0400897 throws InvalidProtocolBufferNanoException {
898 Resource res = new Resource();
899 MessageNano.mergeFrom(res, readCheckedBytes(buffer, offset, dataSize));
Chris Wren50c8f422014-01-15 16:10:39 -0500900 if (VERBOSE) Log.v(TAG, "unpacked icon " + res.dpi + "/" + res.data.length);
Chris Wren22e130d2013-09-23 18:25:57 -0400901 return res;
902 }
903
Chris Wrenfd13c712013-09-27 15:45:19 -0400904 /** Serialize a widget for persistence, including a checksum wrapper. */
905 private byte[] packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache,
906 ComponentName provider) {
907 final AppWidgetProviderInfo info = findAppWidgetProviderInfo(provider);
908 Widget widget = new Widget();
909 widget.provider = provider.flattenToShortString();
910 widget.label = info.label;
911 widget.configure = info.configure != null;
912 if (info.icon != 0) {
913 widget.icon = new Resource();
914 Drawable fullResIcon = iconCache.getFullResIcon(provider.getPackageName(), info.icon);
Chris Wren92aa4232013-10-04 11:29:36 -0400915 Bitmap icon = Utilities.createIconBitmap(fullResIcon, mContext);
Chris Wrenfd13c712013-09-27 15:45:19 -0400916 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400917 if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wrenfd13c712013-09-27 15:45:19 -0400918 widget.icon.data = os.toByteArray();
919 widget.icon.dpi = dpi;
920 }
921 }
922 if (info.previewImage != 0) {
923 widget.preview = new Resource();
924 Bitmap preview = previewLoader.generateWidgetPreview(info, null);
925 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400926 if (preview.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wrenfd13c712013-09-27 15:45:19 -0400927 widget.preview.data = os.toByteArray();
928 widget.preview.dpi = dpi;
929 }
930 }
931 return writeCheckedBytes(widget);
932 }
933
934 /** Deserialize a widget from persistence, after verifying checksum wrapper. */
935 private Widget unpackWidget(byte[] buffer, int offset, int dataSize)
936 throws InvalidProtocolBufferNanoException {
937 Widget widget = new Widget();
938 MessageNano.mergeFrom(widget, readCheckedBytes(buffer, offset, dataSize));
Chris Wren50c8f422014-01-15 16:10:39 -0500939 if (VERBOSE) Log.v(TAG, "unpacked widget " + widget.provider);
Chris Wrenfd13c712013-09-27 15:45:19 -0400940 return widget;
941 }
942
Chris Wren1ada10d2013-09-13 18:01:38 -0400943 /**
944 * Read the old journal from the input file.
945 *
946 * In the event of any error, just pretend we didn't have a journal,
947 * in that case, do a full backup.
948 *
949 * @param oldState the read-0only file descriptor pointing to the old journal
Chris Wren65b6a602014-01-10 14:11:25 -0500950 * @return a Journal protocol buffer
Chris Wren1ada10d2013-09-13 18:01:38 -0400951 */
952 private Journal readJournal(ParcelFileDescriptor oldState) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400953 Journal journal = new Journal();
Chris Wren92aa4232013-10-04 11:29:36 -0400954 if (oldState == null) {
955 return journal;
956 }
957 FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor());
958 try {
Chris Wren65b6a602014-01-10 14:11:25 -0500959 int availableBytes = inStream.available();
960 if (DEBUG) Log.d(TAG, "available " + availableBytes);
961 if (availableBytes < MAX_JOURNAL_SIZE) {
962 byte[] buffer = new byte[availableBytes];
Chris Wren1ada10d2013-09-13 18:01:38 -0400963 int bytesRead = 0;
Chris Wren65b6a602014-01-10 14:11:25 -0500964 boolean valid = false;
Chris Wren50c8f422014-01-15 16:10:39 -0500965 InvalidProtocolBufferNanoException lastProtoException = null;
Chris Wren65b6a602014-01-10 14:11:25 -0500966 while (availableBytes > 0) {
Chris Wren92aa4232013-10-04 11:29:36 -0400967 try {
Chris Wren65b6a602014-01-10 14:11:25 -0500968 // OMG what are you doing? This is crazy inefficient!
969 // If we read a byte that is not ours, we will cause trouble: b/12491813
970 // However, we don't know how many bytes to expect (oops).
971 // So we have to step through *slowly*, watching for the end.
972 int result = inStream.read(buffer, bytesRead, 1);
Chris Wren92aa4232013-10-04 11:29:36 -0400973 if (result > 0) {
Chris Wren65b6a602014-01-10 14:11:25 -0500974 availableBytes -= result;
Chris Wren92aa4232013-10-04 11:29:36 -0400975 bytesRead += result;
Chris Wren65b6a602014-01-10 14:11:25 -0500976 if (DEBUG && (bytesRead % 100 == 0)) {
977 Log.d(TAG, "read some bytes: " + bytesRead);
978 }
Chris Wren92aa4232013-10-04 11:29:36 -0400979 } else {
Chris Wren65b6a602014-01-10 14:11:25 -0500980 Log.w(TAG, "unexpected end of file while reading journal.");
981 // stop reading and see what there is to parse
982 availableBytes = 0;
Chris Wren92aa4232013-10-04 11:29:36 -0400983 }
984 } catch (IOException e) {
Chris Wren92aa4232013-10-04 11:29:36 -0400985 buffer = null;
Chris Wren65b6a602014-01-10 14:11:25 -0500986 availableBytes = 0;
987 }
988
989 // check the buffer to see if we have a valid journal
990 try {
991 MessageNano.mergeFrom(journal, readCheckedBytes(buffer, 0, bytesRead));
992 // if we are here, then we have read a valid, checksum-verified journal
993 valid = true;
994 availableBytes = 0;
Chris Wren50c8f422014-01-15 16:10:39 -0500995 if (VERBOSE) Log.v(TAG, "read " + bytesRead + " bytes of journal");
Chris Wren65b6a602014-01-10 14:11:25 -0500996 } catch (InvalidProtocolBufferNanoException e) {
997 // if we don't have the whole journal yet, mergeFrom will throw. keep going.
Chris Wren50c8f422014-01-15 16:10:39 -0500998 lastProtoException = e;
Chris Wren65b6a602014-01-10 14:11:25 -0500999 journal.clear();
Chris Wren92aa4232013-10-04 11:29:36 -04001000 }
Chris Wren1ada10d2013-09-13 18:01:38 -04001001 }
Chris Wren92aa4232013-10-04 11:29:36 -04001002 if (DEBUG) Log.d(TAG, "journal bytes read: " + bytesRead);
Chris Wren65b6a602014-01-10 14:11:25 -05001003 if (!valid) {
Chris Wren50c8f422014-01-15 16:10:39 -05001004 Log.w(TAG, "could not find a valid journal", lastProtoException);
Chris Wren1ada10d2013-09-13 18:01:38 -04001005 }
1006 }
Chris Wren92aa4232013-10-04 11:29:36 -04001007 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -05001008 Log.w(TAG, "failed to close the journal", e);
Chris Wren92aa4232013-10-04 11:29:36 -04001009 } finally {
Chris Wren1ada10d2013-09-13 18:01:38 -04001010 try {
1011 inStream.close();
1012 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -05001013 Log.w(TAG, "failed to close the journal", e);
Chris Wren1ada10d2013-09-13 18:01:38 -04001014 }
1015 }
1016 return journal;
1017 }
1018
Chris Wren22e130d2013-09-23 18:25:57 -04001019 private void writeRowToBackup(Key key, byte[] blob, Journal out,
1020 BackupDataOutput data) throws IOException {
1021 String backupKey = keyToBackupKey(key);
1022 data.writeEntityHeader(backupKey, blob.length);
1023 data.writeEntityData(blob, blob.length);
1024 out.rows++;
1025 out.bytes += blob.length;
Chris Wren50c8f422014-01-15 16:10:39 -05001026 if (VERBOSE) Log.v(TAG, "saving " + geKeyType(key) + " " + backupKey + ": " +
Chris Wren22e130d2013-09-23 18:25:57 -04001027 getKeyName(key) + "/" + blob.length);
Chris Wren92aa4232013-10-04 11:29:36 -04001028 if(DEBUG_PAYLOAD) {
Chris Wren2b6c21d2013-10-02 14:16:04 -04001029 String encoded = Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP);
1030 final int chunkSize = 1024;
1031 for (int offset = 0; offset < encoded.length(); offset += chunkSize) {
1032 int end = offset + chunkSize;
1033 end = Math.min(end, encoded.length());
Chris Wren50c8f422014-01-15 16:10:39 -05001034 Log.w(TAG, "wrote " + encoded.substring(offset, end));
Chris Wren2b6c21d2013-10-02 14:16:04 -04001035 }
1036 }
Chris Wren22e130d2013-09-23 18:25:57 -04001037 }
1038
1039 private Set<String> getSavedIdsByType(int type, Journal in) {
1040 Set<String> savedIds = new HashSet<String>();
1041 for(int i = 0; i < in.key.length; i++) {
1042 Key key = in.key[i];
1043 if (key.type == type) {
1044 savedIds.add(keyToBackupKey(key));
1045 }
1046 }
1047 return savedIds;
1048 }
1049
1050 private int removeDeletedKeysFromBackup(Set<String> deletedIds, BackupDataOutput data)
1051 throws IOException {
1052 int rows = 0;
1053 for(String deleted: deletedIds) {
Chris Wren50c8f422014-01-15 16:10:39 -05001054 if (VERBOSE) Log.v(TAG, "dropping deleted item " + deleted);
Chris Wren22e130d2013-09-23 18:25:57 -04001055 data.writeEntityHeader(deleted, -1);
1056 rows++;
1057 }
1058 return rows;
1059 }
1060
Chris Wren1ada10d2013-09-13 18:01:38 -04001061 /**
1062 * Write the new journal to the output file.
1063 *
1064 * In the event of any error, just pretend we didn't have a journal,
1065 * in that case, do a full backup.
1066
1067 * @param newState the write-only file descriptor pointing to the new journal
1068 * @param journal a Journal protocol buffer
1069 */
1070 private void writeJournal(ParcelFileDescriptor newState, Journal journal) {
1071 FileOutputStream outStream = null;
1072 try {
1073 outStream = new FileOutputStream(newState.getFileDescriptor());
Chris Wren65b6a602014-01-10 14:11:25 -05001074 final byte[] journalBytes = writeCheckedBytes(journal);
Chris Wren65b6a602014-01-10 14:11:25 -05001075 outStream.write(journalBytes);
Chris Wren1ada10d2013-09-13 18:01:38 -04001076 outStream.close();
Chris Wren50c8f422014-01-15 16:10:39 -05001077 if (VERBOSE) Log.v(TAG, "wrote " + journalBytes.length + " bytes of journal");
Chris Wren1ada10d2013-09-13 18:01:38 -04001078 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -05001079 Log.w(TAG, "failed to write backup journal", e);
Chris Wren1ada10d2013-09-13 18:01:38 -04001080 }
1081 }
1082
1083 /** Wrap a proto in a CheckedMessage and compute the checksum. */
1084 private byte[] writeCheckedBytes(MessageNano proto) {
1085 CheckedMessage wrapper = new CheckedMessage();
1086 wrapper.payload = MessageNano.toByteArray(proto);
1087 CRC32 checksum = new CRC32();
1088 checksum.update(wrapper.payload);
1089 wrapper.checksum = checksum.getValue();
1090 return MessageNano.toByteArray(wrapper);
1091 }
1092
1093 /** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
Chris Wren6d0dde02014-02-10 12:16:54 -05001094 private static byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
Chris Wren1ada10d2013-09-13 18:01:38 -04001095 throws InvalidProtocolBufferNanoException {
1096 CheckedMessage wrapper = new CheckedMessage();
1097 MessageNano.mergeFrom(wrapper, buffer, offset, dataSize);
1098 CRC32 checksum = new CRC32();
1099 checksum.update(wrapper.payload);
1100 if (wrapper.checksum != checksum.getValue()) {
1101 throw new InvalidProtocolBufferNanoException("checksum does not match");
1102 }
1103 return wrapper.payload;
1104 }
1105
Chris Wrenfd13c712013-09-27 15:45:19 -04001106 private AppWidgetProviderInfo findAppWidgetProviderInfo(ComponentName component) {
1107 if (mWidgetMap == null) {
1108 List<AppWidgetProviderInfo> widgets =
Chris Wren92aa4232013-10-04 11:29:36 -04001109 AppWidgetManager.getInstance(mContext).getInstalledProviders();
Chris Wrenfd13c712013-09-27 15:45:19 -04001110 mWidgetMap = new HashMap<ComponentName, AppWidgetProviderInfo>(widgets.size());
1111 for (AppWidgetProviderInfo info : widgets) {
1112 mWidgetMap.put(info.provider, info);
1113 }
1114 }
1115 return mWidgetMap.get(component);
1116 }
1117
Chris Wren6d0dde02014-02-10 12:16:54 -05001118
1119 private boolean initializeIconCache() {
1120 if (mIconCache != null) {
1121 return true;
1122 }
1123
1124 final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
1125 if (appState == null) {
1126 Throwable stackTrace = new Throwable();
1127 stackTrace.fillInStackTrace();
1128 Log.w(TAG, "Failed to get app state during backup/restore", stackTrace);
1129 return false;
1130 }
1131 mIconCache = appState.getIconCache();
1132 return mIconCache != null;
1133 }
1134
Chris Wren71144262014-02-27 15:49:39 -05001135
1136 // check if the launcher is in a state to support backup
1137 private boolean launcherIsReady() {
1138 ContentResolver cr = mContext.getContentResolver();
1139 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION, null, null, null);
1140 if (cursor == null) {
1141 // launcher data has been wiped, do nothing
1142 return false;
1143 }
1144 cursor.close();
1145
1146 if (!initializeIconCache()) {
1147 // launcher services are unavailable, try again later
1148 dataChanged();
1149 return false;
1150 }
1151
1152 return true;
1153 }
1154
Chris Wren1ada10d2013-09-13 18:01:38 -04001155 private class KeyParsingException extends Throwable {
1156 private KeyParsingException(Throwable cause) {
1157 super(cause);
1158 }
1159
1160 public KeyParsingException(String reason) {
1161 super(reason);
1162 }
1163 }
1164}