blob: 275fccc16abc34c3e527ecde8b7ed0abee5922f0 [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;
Kenny Guyed131872014-04-30 03:02:21 +010031import com.android.launcher3.compat.UserManagerCompat;
32import com.android.launcher3.compat.UserHandleCompat;
Chris Wren1ada10d2013-09-13 18:01:38 -040033
Chris Wren92aa4232013-10-04 11:29:36 -040034import android.app.backup.BackupDataInputStream;
Chris Wren1ada10d2013-09-13 18:01:38 -040035import android.app.backup.BackupDataOutput;
Chris Wren4d89e2a2013-10-09 17:03:50 -040036import android.app.backup.BackupHelper;
Chris Wren1ada10d2013-09-13 18:01:38 -040037import android.app.backup.BackupManager;
Chris Wren22e130d2013-09-23 18:25:57 -040038import android.appwidget.AppWidgetManager;
39import android.appwidget.AppWidgetProviderInfo;
40import android.content.ComponentName;
Chris Wren1ada10d2013-09-13 18:01:38 -040041import android.content.ContentResolver;
Chris Wren5dee7af2013-12-20 17:22:11 -050042import android.content.ContentValues;
Chris Wren1ada10d2013-09-13 18:01:38 -040043import android.content.Context;
Chris Wren22e130d2013-09-23 18:25:57 -040044import android.content.Intent;
Chris Wren1ada10d2013-09-13 18:01:38 -040045import android.database.Cursor;
Chris Wren22e130d2013-09-23 18:25:57 -040046import android.graphics.Bitmap;
47import android.graphics.BitmapFactory;
Chris Wrenfd13c712013-09-27 15:45:19 -040048import android.graphics.drawable.Drawable;
Chris Wren1ada10d2013-09-13 18:01:38 -040049import android.os.ParcelFileDescriptor;
Chris Wren1ada10d2013-09-13 18:01:38 -040050import android.text.TextUtils;
51import android.util.Base64;
52import android.util.Log;
53
Chris Wren22e130d2013-09-23 18:25:57 -040054import java.io.ByteArrayOutputStream;
Chris Wren1ada10d2013-09-13 18:01:38 -040055import java.io.FileInputStream;
56import java.io.FileOutputStream;
57import java.io.IOException;
Chris Wren22e130d2013-09-23 18:25:57 -040058import java.net.URISyntaxException;
Chris Wren1ada10d2013-09-13 18:01:38 -040059import java.util.ArrayList;
Chris Wren22e130d2013-09-23 18:25:57 -040060import java.util.HashMap;
Chris Wren1ada10d2013-09-13 18:01:38 -040061import java.util.HashSet;
Chris Wren22e130d2013-09-23 18:25:57 -040062import java.util.List;
Chris Wren1ada10d2013-09-13 18:01:38 -040063import java.util.Set;
64import java.util.zip.CRC32;
65
66/**
67 * Persist the launcher home state across calamities.
68 */
Chris Wren92aa4232013-10-04 11:29:36 -040069public class LauncherBackupHelper implements BackupHelper {
Chris Wren1ada10d2013-09-13 18:01:38 -040070
Chris Wren92aa4232013-10-04 11:29:36 -040071 private static final String TAG = "LauncherBackupHelper";
Chris Wren50c8f422014-01-15 16:10:39 -050072 private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
73 private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
Chris Wren92aa4232013-10-04 11:29:36 -040074 private static final boolean DEBUG_PAYLOAD = false;
Chris Wren1ada10d2013-09-13 18:01:38 -040075
76 private static final int MAX_JOURNAL_SIZE = 1000000;
77
Chris Wrenfd13c712013-09-27 15:45:19 -040078 /** icons are large, dribble them out */
Chris Wren22e130d2013-09-23 18:25:57 -040079 private static final int MAX_ICONS_PER_PASS = 10;
80
Chris Wrenfd13c712013-09-27 15:45:19 -040081 /** widgets contain previews, which are very large, dribble them out */
82 private static final int MAX_WIDGETS_PER_PASS = 5;
83
84 public static final int IMAGE_COMPRESSION_QUALITY = 75;
85
Chris Wren92aa4232013-10-04 11:29:36 -040086 public static final String LAUNCHER_PREFIX = "L";
87
Chris Wren45297f82013-10-17 15:16:48 -040088 public static final String LAUNCHER_PREFS_PREFIX = "LP";
89
Chris Wrenb86f0762013-10-04 10:10:21 -040090 private static final Bitmap.CompressFormat IMAGE_FORMAT =
91 android.graphics.Bitmap.CompressFormat.PNG;
92
Chris Wren1ada10d2013-09-13 18:01:38 -040093 private static BackupManager sBackupManager;
94
95 private static final String[] FAVORITE_PROJECTION = {
96 Favorites._ID, // 0
Chris Wren22e130d2013-09-23 18:25:57 -040097 Favorites.MODIFIED, // 1
98 Favorites.INTENT, // 2
99 Favorites.APPWIDGET_PROVIDER, // 3
100 Favorites.APPWIDGET_ID, // 4
101 Favorites.CELLX, // 5
102 Favorites.CELLY, // 6
103 Favorites.CONTAINER, // 7
104 Favorites.ICON, // 8
105 Favorites.ICON_PACKAGE, // 9
106 Favorites.ICON_RESOURCE, // 10
107 Favorites.ICON_TYPE, // 11
108 Favorites.ITEM_TYPE, // 12
109 Favorites.SCREEN, // 13
110 Favorites.SPANX, // 14
111 Favorites.SPANY, // 15
112 Favorites.TITLE, // 16
Chris Wren1ada10d2013-09-13 18:01:38 -0400113 };
114
115 private static final int ID_INDEX = 0;
Chris Wren22e130d2013-09-23 18:25:57 -0400116 private static final int ID_MODIFIED = 1;
117 private static final int INTENT_INDEX = 2;
118 private static final int APPWIDGET_PROVIDER_INDEX = 3;
119 private static final int APPWIDGET_ID_INDEX = 4;
120 private static final int CELLX_INDEX = 5;
121 private static final int CELLY_INDEX = 6;
122 private static final int CONTAINER_INDEX = 7;
123 private static final int ICON_INDEX = 8;
124 private static final int ICON_PACKAGE_INDEX = 9;
125 private static final int ICON_RESOURCE_INDEX = 10;
126 private static final int ICON_TYPE_INDEX = 11;
127 private static final int ITEM_TYPE_INDEX = 12;
128 private static final int SCREEN_INDEX = 13;
129 private static final int SPANX_INDEX = 14;
130 private static final int SPANY_INDEX = 15;
131 private static final int TITLE_INDEX = 16;
Chris Wren1ada10d2013-09-13 18:01:38 -0400132
133 private static final String[] SCREEN_PROJECTION = {
134 WorkspaceScreens._ID, // 0
Chris Wren22e130d2013-09-23 18:25:57 -0400135 WorkspaceScreens.MODIFIED, // 1
136 WorkspaceScreens.SCREEN_RANK // 2
Chris Wren1ada10d2013-09-13 18:01:38 -0400137 };
138
Chris Wren22e130d2013-09-23 18:25:57 -0400139 private static final int SCREEN_RANK_INDEX = 2;
Chris Wren1ada10d2013-09-13 18:01:38 -0400140
Chris Wren6d0dde02014-02-10 12:16:54 -0500141 private static IconCache mIconCache;
142
Chris Wren92aa4232013-10-04 11:29:36 -0400143 private final Context mContext;
Chris Wren1ada10d2013-09-13 18:01:38 -0400144
Chris Wren4b171362014-01-13 17:39:02 -0500145 private final boolean mRestoreEnabled;
146
Chris Wren22e130d2013-09-23 18:25:57 -0400147 private HashMap<ComponentName, AppWidgetProviderInfo> mWidgetMap;
148
Chris Wren92aa4232013-10-04 11:29:36 -0400149 private ArrayList<Key> mKeys;
Chris Wren1ada10d2013-09-13 18:01:38 -0400150
Chris Wren4b171362014-01-13 17:39:02 -0500151 public LauncherBackupHelper(Context context, boolean restoreEnabled) {
Chris Wren92aa4232013-10-04 11:29:36 -0400152 mContext = context;
Chris Wren4b171362014-01-13 17:39:02 -0500153 mRestoreEnabled = restoreEnabled;
Chris Wren92aa4232013-10-04 11:29:36 -0400154 }
155
156 private void dataChanged() {
Chris Wren1ada10d2013-09-13 18:01:38 -0400157 if (sBackupManager == null) {
Chris Wren92aa4232013-10-04 11:29:36 -0400158 sBackupManager = new BackupManager(mContext);
Chris Wren1ada10d2013-09-13 18:01:38 -0400159 }
160 sBackupManager.dataChanged();
161 }
162
163 /**
164 * Back up launcher data so we can restore the user's state on a new device.
165 *
166 * <P>The journal is a timestamp and a list of keys that were saved as of that time.
167 *
168 * <P>Keys may come back in any order, so each key/value is one complete row of the database.
169 *
170 * @param oldState notes from the last backup
171 * @param data incremental key/value pairs to persist off-device
172 * @param newState notes for the next backup
173 * @throws IOException
174 */
175 @Override
Chris Wren92aa4232013-10-04 11:29:36 -0400176 public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
177 ParcelFileDescriptor newState) {
Chris Wren50c8f422014-01-15 16:10:39 -0500178 if (VERBOSE) Log.v(TAG, "onBackup");
Chris Wren1ada10d2013-09-13 18:01:38 -0400179
180 Journal in = readJournal(oldState);
181 Journal out = new Journal();
182
183 long lastBackupTime = in.t;
184 out.t = System.currentTimeMillis();
185 out.rows = 0;
186 out.bytes = 0;
187
Chris Wren50c8f422014-01-15 16:10:39 -0500188 Log.v(TAG, "lastBackupTime = " + lastBackupTime);
Chris Wren1ada10d2013-09-13 18:01:38 -0400189
190 ArrayList<Key> keys = new ArrayList<Key>();
Chris Wren71144262014-02-27 15:49:39 -0500191 if (launcherIsReady()) {
192 try {
193 backupFavorites(in, data, out, keys);
194 backupScreens(in, data, out, keys);
195 backupIcons(in, data, out, keys);
196 backupWidgets(in, data, out, keys);
197 } catch (IOException e) {
198 Log.e(TAG, "launcher backup has failed", e);
199 }
200 out.key = keys.toArray(new BackupProtos.Key[keys.size()]);
201 } else {
202 out = in;
Chris Wren92aa4232013-10-04 11:29:36 -0400203 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400204
Chris Wren1ada10d2013-09-13 18:01:38 -0400205 writeJournal(newState, out);
206 Log.v(TAG, "onBackup: wrote " + out.bytes + "b in " + out.rows + " rows.");
Chris Wren1ada10d2013-09-13 18:01:38 -0400207 }
208
209 /**
Chris Wren92aa4232013-10-04 11:29:36 -0400210 * Restore launcher configuration from the restored data stream.
Chris Wren1ada10d2013-09-13 18:01:38 -0400211 *
212 * <P>Keys may arrive in any order.
213 *
Chris Wren92aa4232013-10-04 11:29:36 -0400214 * @param data the key/value pair from the server
Chris Wren1ada10d2013-09-13 18:01:38 -0400215 */
216 @Override
Chris Wren92aa4232013-10-04 11:29:36 -0400217 public void restoreEntity(BackupDataInputStream data) {
Chris Wren50c8f422014-01-15 16:10:39 -0500218 if (VERBOSE) Log.v(TAG, "restoreEntity");
Chris Wren92aa4232013-10-04 11:29:36 -0400219 if (mKeys == null) {
220 mKeys = new ArrayList<Key>();
221 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400222 byte[] buffer = new byte[512];
Chris Wren1ada10d2013-09-13 18:01:38 -0400223 String backupKey = data.getKey();
Chris Wren92aa4232013-10-04 11:29:36 -0400224 int dataSize = data.size();
Chris Wren1ada10d2013-09-13 18:01:38 -0400225 if (buffer.length < dataSize) {
226 buffer = new byte[dataSize];
227 }
228 Key key = null;
Chris Wren92aa4232013-10-04 11:29:36 -0400229 int bytesRead = 0;
230 try {
231 bytesRead = data.read(buffer, 0, dataSize);
232 if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
233 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500234 Log.e(TAG, "failed to read entity from restore data", e);
Chris Wren92aa4232013-10-04 11:29:36 -0400235 }
236 try {
237 key = backupKeyToKey(backupKey);
Chris Wren50c8f422014-01-15 16:10:39 -0500238 mKeys.add(key);
Chris Wren92aa4232013-10-04 11:29:36 -0400239 switch (key.type) {
240 case Key.FAVORITE:
241 restoreFavorite(key, buffer, dataSize, mKeys);
242 break;
243
244 case Key.SCREEN:
245 restoreScreen(key, buffer, dataSize, mKeys);
246 break;
247
248 case Key.ICON:
249 restoreIcon(key, buffer, dataSize, mKeys);
250 break;
251
252 case Key.WIDGET:
253 restoreWidget(key, buffer, dataSize, mKeys);
254 break;
255
256 default:
257 Log.w(TAG, "unknown restore entity type: " + key.type);
258 break;
Chris Wren1ada10d2013-09-13 18:01:38 -0400259 }
Chris Wren92aa4232013-10-04 11:29:36 -0400260 } catch (KeyParsingException e) {
261 Log.w(TAG, "ignoring unparsable backup key: " + backupKey);
Chris Wren1ada10d2013-09-13 18:01:38 -0400262 }
263
Chris Wren92aa4232013-10-04 11:29:36 -0400264 }
265
266 /**
267 * Record the restore state for the next backup.
268 *
269 * @param newState notes about the backup state after restore.
270 */
271 @Override
272 public void writeNewStateDescription(ParcelFileDescriptor newState) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400273 // clear the output journal time, to force a full backup to
274 // will catch any changes the restore process might have made
Chris Wren92aa4232013-10-04 11:29:36 -0400275 Journal out = new Journal();
Chris Wren1ada10d2013-09-13 18:01:38 -0400276 out.t = 0;
Mac Duy Hai22dc65e2014-02-05 10:52:07 +0000277 out.key = mKeys.toArray(new BackupProtos.Key[mKeys.size()]);
Chris Wren1ada10d2013-09-13 18:01:38 -0400278 writeJournal(newState, out);
Chris Wren92aa4232013-10-04 11:29:36 -0400279 Log.v(TAG, "onRestore: read " + mKeys.size() + " rows");
280 mKeys.clear();
Chris Wren1ada10d2013-09-13 18:01:38 -0400281 }
282
283 /**
284 * Write all modified favorites to the data stream.
285 *
286 *
287 * @param in notes from last backup
288 * @param data output stream for key/value pairs
289 * @param out notes about this backup
290 * @param keys keys to mark as clean in the notes for next backup
291 * @throws IOException
292 */
293 private void backupFavorites(Journal in, BackupDataOutput data, Journal out,
294 ArrayList<Key> keys)
295 throws IOException {
296 // read the old ID set
Chris Wren22e130d2013-09-23 18:25:57 -0400297 Set<String> savedIds = getSavedIdsByType(Key.FAVORITE, in);
Chris Wren1ada10d2013-09-13 18:01:38 -0400298 if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
299
300 // persist things that have changed since the last backup
Chris Wren92aa4232013-10-04 11:29:36 -0400301 ContentResolver cr = mContext.getContentResolver();
Chris Wren22e130d2013-09-23 18:25:57 -0400302 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
303 null, null, null);
304 Set<String> currentIds = new HashSet<String>(cursor.getCount());
Chris Wren1ada10d2013-09-13 18:01:38 -0400305 try {
Chris Wren22e130d2013-09-23 18:25:57 -0400306 cursor.moveToPosition(-1);
307 while(cursor.moveToNext()) {
308 final long id = cursor.getLong(ID_INDEX);
309 final long updateTime = cursor.getLong(ID_MODIFIED);
Chris Wren1ada10d2013-09-13 18:01:38 -0400310 Key key = getKey(Key.FAVORITE, id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400311 keys.add(key);
Chris Wren5743aa92014-01-10 18:02:06 -0500312 final String backupKey = keyToBackupKey(key);
313 currentIds.add(backupKey);
314 if (!savedIds.contains(backupKey) || updateTime >= in.t) {
Chris Wren22e130d2013-09-23 18:25:57 -0400315 byte[] blob = packFavorite(cursor);
316 writeRowToBackup(key, blob, out, data);
Chris Wren50c8f422014-01-15 16:10:39 -0500317 } else {
318 if (VERBOSE) Log.v(TAG, "favorite " + id + " was too old: " + updateTime);
Chris Wren22e130d2013-09-23 18:25:57 -0400319 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400320 }
321 } finally {
Chris Wren22e130d2013-09-23 18:25:57 -0400322 cursor.close();
Chris Wren1ada10d2013-09-13 18:01:38 -0400323 }
324 if (DEBUG) Log.d(TAG, "favorite currentIds.size()=" + currentIds.size());
325
326 // these IDs must have been deleted
327 savedIds.removeAll(currentIds);
Chris Wren22e130d2013-09-23 18:25:57 -0400328 out.rows += removeDeletedKeysFromBackup(savedIds, data);
Chris Wren1ada10d2013-09-13 18:01:38 -0400329 }
330
331 /**
332 * Read a favorite from the stream.
333 *
334 * <P>Keys arrive in any order, so screens and containers may not exist yet.
335 *
336 * @param key identifier for the row
337 * @param buffer the serialized proto from the stream, may be larger than dataSize
338 * @param dataSize the size of the proto from the stream
339 * @param keys keys to mark as clean in the notes for next backup
340 */
341 private void restoreFavorite(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
Chris Wren50c8f422014-01-15 16:10:39 -0500342 if (VERBOSE) Log.v(TAG, "unpacking favorite " + key.id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400343 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
344 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
345
Chris Wren5dee7af2013-12-20 17:22:11 -0500346 if (!mRestoreEnabled) {
Chris Wren50c8f422014-01-15 16:10:39 -0500347 if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
Chris Wren5dee7af2013-12-20 17:22:11 -0500348 return;
349 }
350
Chris Wren1ada10d2013-09-13 18:01:38 -0400351 try {
Chris Wren5dee7af2013-12-20 17:22:11 -0500352 ContentResolver cr = mContext.getContentResolver();
353 ContentValues values = unpackFavorite(buffer, 0, dataSize);
354 cr.insert(Favorites.CONTENT_URI, values);
Chris Wren1ada10d2013-09-13 18:01:38 -0400355 } catch (InvalidProtocolBufferNanoException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500356 Log.e(TAG, "failed to decode favorite", e);
Chris Wren1ada10d2013-09-13 18:01:38 -0400357 }
358 }
359
360 /**
361 * Write all modified screens to the data stream.
362 *
363 *
364 * @param in notes from last backup
365 * @param data output stream for key/value pairs
366 * @param out notes about this backup
Chris Wren22e130d2013-09-23 18:25:57 -0400367 * @param keys keys to mark as clean in the notes for next backup
368 * @throws IOException
Chris Wren1ada10d2013-09-13 18:01:38 -0400369 */
370 private void backupScreens(Journal in, BackupDataOutput data, Journal out,
371 ArrayList<Key> keys)
372 throws IOException {
373 // read the old ID set
Chris Wren22e130d2013-09-23 18:25:57 -0400374 Set<String> savedIds = getSavedIdsByType(Key.SCREEN, in);
375 if (DEBUG) Log.d(TAG, "screen savedIds.size()=" + savedIds.size());
Chris Wren1ada10d2013-09-13 18:01:38 -0400376
377 // persist things that have changed since the last backup
Chris Wren92aa4232013-10-04 11:29:36 -0400378 ContentResolver cr = mContext.getContentResolver();
Chris Wren22e130d2013-09-23 18:25:57 -0400379 Cursor cursor = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
380 null, null, null);
381 Set<String> currentIds = new HashSet<String>(cursor.getCount());
Chris Wren1ada10d2013-09-13 18:01:38 -0400382 try {
Chris Wren22e130d2013-09-23 18:25:57 -0400383 cursor.moveToPosition(-1);
Chris Wren50c8f422014-01-15 16:10:39 -0500384 if (DEBUG) Log.d(TAG, "dumping screens after: " + in.t);
Chris Wren22e130d2013-09-23 18:25:57 -0400385 while(cursor.moveToNext()) {
386 final long id = cursor.getLong(ID_INDEX);
387 final long updateTime = cursor.getLong(ID_MODIFIED);
Chris Wren1ada10d2013-09-13 18:01:38 -0400388 Key key = getKey(Key.SCREEN, id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400389 keys.add(key);
Chris Wren5743aa92014-01-10 18:02:06 -0500390 final String backupKey = keyToBackupKey(key);
391 currentIds.add(backupKey);
392 if (!savedIds.contains(backupKey) || updateTime >= in.t) {
Chris Wren22e130d2013-09-23 18:25:57 -0400393 byte[] blob = packScreen(cursor);
394 writeRowToBackup(key, blob, out, data);
Chris Wren5dee7af2013-12-20 17:22:11 -0500395 } else {
Chris Wren50c8f422014-01-15 16:10:39 -0500396 if (VERBOSE) Log.v(TAG, "screen " + id + " was too old: " + updateTime);
Chris Wren22e130d2013-09-23 18:25:57 -0400397 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400398 }
399 } finally {
Chris Wren22e130d2013-09-23 18:25:57 -0400400 cursor.close();
Chris Wren1ada10d2013-09-13 18:01:38 -0400401 }
402 if (DEBUG) Log.d(TAG, "screen currentIds.size()=" + currentIds.size());
403
404 // these IDs must have been deleted
405 savedIds.removeAll(currentIds);
Chris Wren22e130d2013-09-23 18:25:57 -0400406 out.rows += removeDeletedKeysFromBackup(savedIds, data);
Chris Wren1ada10d2013-09-13 18:01:38 -0400407 }
408
409 /**
410 * Read a screen from the stream.
411 *
412 * <P>Keys arrive in any order, so children of this screen may already exist.
413 *
414 * @param key identifier for the row
415 * @param buffer the serialized proto from the stream, may be larger than dataSize
416 * @param dataSize the size of the proto from the stream
417 * @param keys keys to mark as clean in the notes for next backup
418 */
419 private void restoreScreen(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
Chris Wren50c8f422014-01-15 16:10:39 -0500420 if (VERBOSE) Log.v(TAG, "unpacking screen " + key.id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400421 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
422 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
Chris Wren5dee7af2013-12-20 17:22:11 -0500423
424 if (!mRestoreEnabled) {
Chris Wren50c8f422014-01-15 16:10:39 -0500425 if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
Chris Wren5dee7af2013-12-20 17:22:11 -0500426 return;
427 }
428
Chris Wren1ada10d2013-09-13 18:01:38 -0400429 try {
Chris Wren5dee7af2013-12-20 17:22:11 -0500430 ContentResolver cr = mContext.getContentResolver();
431 ContentValues values = unpackScreen(buffer, 0, dataSize);
432 cr.insert(WorkspaceScreens.CONTENT_URI, values);
433
Chris Wren1ada10d2013-09-13 18:01:38 -0400434 } catch (InvalidProtocolBufferNanoException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500435 Log.e(TAG, "failed to decode screen", e);
Chris Wren1ada10d2013-09-13 18:01:38 -0400436 }
437 }
438
Chris Wren22e130d2013-09-23 18:25:57 -0400439 /**
440 * Write all the static icon resources we need to render placeholders
441 * for a package that is not installed.
442 *
443 * @param in notes from last backup
444 * @param data output stream for key/value pairs
445 * @param out notes about this backup
446 * @param keys keys to mark as clean in the notes for next backup
447 * @throws IOException
448 */
449 private void backupIcons(Journal in, BackupDataOutput data, Journal out,
450 ArrayList<Key> keys) throws IOException {
Chris Wrenfd13c712013-09-27 15:45:19 -0400451 // persist icons that haven't been persisted yet
Chris Wren6d0dde02014-02-10 12:16:54 -0500452 if (!initializeIconCache()) {
Chris Wren92aa4232013-10-04 11:29:36 -0400453 dataChanged(); // try again later
Chris Wrend8fe6de2013-10-04 10:42:14 -0400454 if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying icon backup");
455 return;
456 }
Chris Wren92aa4232013-10-04 11:29:36 -0400457 final ContentResolver cr = mContext.getContentResolver();
Chris Wren92aa4232013-10-04 11:29:36 -0400458 final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
Chris Wren22e130d2013-09-23 18:25:57 -0400459
460 // read the old ID set
461 Set<String> savedIds = getSavedIdsByType(Key.ICON, in);
462 if (DEBUG) Log.d(TAG, "icon savedIds.size()=" + savedIds.size());
463
Kenny Guyed131872014-04-30 03:02:21 +0100464 // Don't backup apps in other profiles for now.
465 UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
466 long userSerialNumber =
467 UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
Chris Wren22e130d2013-09-23 18:25:57 -0400468 int startRows = out.rows;
469 if (DEBUG) Log.d(TAG, "starting here: " + startRows);
Kenny Guyed131872014-04-30 03:02:21 +0100470 String where = "(" + Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION + " OR " +
471 Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT + ") AND" +
472 Favorites.PROFILE_ID + "=" + userSerialNumber;
Chris Wren22e130d2013-09-23 18:25:57 -0400473 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
474 where, null, null);
475 Set<String> currentIds = new HashSet<String>(cursor.getCount());
476 try {
477 cursor.moveToPosition(-1);
478 while(cursor.moveToNext()) {
479 final long id = cursor.getLong(ID_INDEX);
480 final String intentDescription = cursor.getString(INTENT_INDEX);
481 try {
482 Intent intent = Intent.parseUri(intentDescription, 0);
483 ComponentName cn = intent.getComponent();
484 Key key = null;
485 String backupKey = null;
486 if (cn != null) {
487 key = getKey(Key.ICON, cn.flattenToShortString());
488 backupKey = keyToBackupKey(key);
489 currentIds.add(backupKey);
490 } else {
491 Log.w(TAG, "empty intent on application favorite: " + id);
492 }
493 if (savedIds.contains(backupKey)) {
Chris Wren50c8f422014-01-15 16:10:39 -0500494 if (VERBOSE) Log.v(TAG, "already saved icon " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -0400495
496 // remember that we already backed this up previously
497 keys.add(key);
498 } else if (backupKey != null) {
499 if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
500 if ((out.rows - startRows) < MAX_ICONS_PER_PASS) {
Chris Wren50c8f422014-01-15 16:10:39 -0500501 if (VERBOSE) Log.v(TAG, "saving icon " + backupKey);
Kenny Guyed131872014-04-30 03:02:21 +0100502 Bitmap icon = mIconCache.getIcon(intent, myUserHandle);
Chris Wren22e130d2013-09-23 18:25:57 -0400503 keys.add(key);
Kenny Guyed131872014-04-30 03:02:21 +0100504 if (icon != null && !mIconCache.isDefaultIcon(icon, myUserHandle)) {
Chris Wren22e130d2013-09-23 18:25:57 -0400505 byte[] blob = packIcon(dpi, icon);
506 writeRowToBackup(key, blob, out, data);
507 }
508 } else {
Chris Wren50c8f422014-01-15 16:10:39 -0500509 if (VERBOSE) Log.d(TAG, "deferring icon backup " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -0400510 // too many icons for this pass, request another.
Chris Wren92aa4232013-10-04 11:29:36 -0400511 dataChanged();
Chris Wren22e130d2013-09-23 18:25:57 -0400512 }
513 }
514 } catch (URISyntaxException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500515 Log.e(TAG, "invalid URI on application favorite: " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400516 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500517 Log.e(TAG, "unable to save application icon for favorite: " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400518 }
519
520 }
521 } finally {
522 cursor.close();
523 }
524 if (DEBUG) Log.d(TAG, "icon currentIds.size()=" + currentIds.size());
525
526 // these IDs must have been deleted
527 savedIds.removeAll(currentIds);
528 out.rows += removeDeletedKeysFromBackup(savedIds, data);
529 }
530
531 /**
532 * Read an icon from the stream.
533 *
Chris Wrenfd13c712013-09-27 15:45:19 -0400534 * <P>Keys arrive in any order, so shortcuts that use this icon may already exist.
Chris Wren22e130d2013-09-23 18:25:57 -0400535 *
536 * @param key identifier for the row
537 * @param buffer the serialized proto from the stream, may be larger than dataSize
538 * @param dataSize the size of the proto from the stream
539 * @param keys keys to mark as clean in the notes for next backup
540 */
541 private void restoreIcon(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
Chris Wren50c8f422014-01-15 16:10:39 -0500542 if (VERBOSE) Log.v(TAG, "unpacking icon " + key.id);
Chris Wren22e130d2013-09-23 18:25:57 -0400543 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
544 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
Chris Wren6d0dde02014-02-10 12:16:54 -0500545
Chris Wren22e130d2013-09-23 18:25:57 -0400546 try {
547 Resource res = unpackIcon(buffer, 0, dataSize);
Chris Wren6d0dde02014-02-10 12:16:54 -0500548 if (DEBUG) {
549 Log.d(TAG, "unpacked " + res.dpi + " dpi icon");
550 }
551 if (DEBUG_PAYLOAD) {
552 Log.d(TAG, "read " +
553 Base64.encodeToString(res.data, 0, res.data.length,
554 Base64.NO_WRAP));
555 }
Chris Wren22e130d2013-09-23 18:25:57 -0400556 Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
557 if (icon == null) {
558 Log.w(TAG, "failed to unpack icon for " + key.name);
559 }
Chris Wren5dee7af2013-12-20 17:22:11 -0500560
561 if (!mRestoreEnabled) {
Chris Wren6d0dde02014-02-10 12:16:54 -0500562 if (VERBOSE) {
563 Log.v(TAG, "restore not enabled: skipping database mutation");
564 }
Chris Wren5dee7af2013-12-20 17:22:11 -0500565 return;
566 } else {
Chris Wren6d0dde02014-02-10 12:16:54 -0500567 IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(key.name),
568 icon, res.dpi);
Chris Wren5dee7af2013-12-20 17:22:11 -0500569 }
Chris Wren6d0dde02014-02-10 12:16:54 -0500570 } catch (IOException e) {
571 Log.d(TAG, "failed to save restored icon for: " + key.name, e);
Chris Wren22e130d2013-09-23 18:25:57 -0400572 }
573 }
574
Chris Wrenfd13c712013-09-27 15:45:19 -0400575 /**
576 * Write all the static widget resources we need to render placeholders
577 * for a package that is not installed.
578 *
579 * @param in notes from last backup
580 * @param data output stream for key/value pairs
581 * @param out notes about this backup
582 * @param keys keys to mark as clean in the notes for next backup
583 * @throws IOException
584 */
585 private void backupWidgets(Journal in, BackupDataOutput data, Journal out,
586 ArrayList<Key> keys) throws IOException {
587 // persist static widget info that hasn't been persisted yet
Chris Wrend8fe6de2013-10-04 10:42:14 -0400588 final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
Chris Wren6d0dde02014-02-10 12:16:54 -0500589 if (appState == null || !initializeIconCache()) {
590 Log.w(TAG, "Failed to get icon cache during restore");
Chris Wrend8fe6de2013-10-04 10:42:14 -0400591 return;
592 }
Chris Wren92aa4232013-10-04 11:29:36 -0400593 final ContentResolver cr = mContext.getContentResolver();
594 final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext);
595 final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(mContext);
Chris Wren92aa4232013-10-04 11:29:36 -0400596 final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
Chris Wrenfd13c712013-09-27 15:45:19 -0400597 final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
598 if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
599
600 // read the old ID set
601 Set<String> savedIds = getSavedIdsByType(Key.WIDGET, in);
602 if (DEBUG) Log.d(TAG, "widgets savedIds.size()=" + savedIds.size());
603
604 int startRows = out.rows;
605 if (DEBUG) Log.d(TAG, "starting here: " + startRows);
606 String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPWIDGET;
607 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
608 where, null, null);
609 Set<String> currentIds = new HashSet<String>(cursor.getCount());
610 try {
611 cursor.moveToPosition(-1);
612 while(cursor.moveToNext()) {
613 final long id = cursor.getLong(ID_INDEX);
614 final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX);
615 final int spanX = cursor.getInt(SPANX_INDEX);
616 final int spanY = cursor.getInt(SPANY_INDEX);
617 final ComponentName provider = ComponentName.unflattenFromString(providerName);
618 Key key = null;
619 String backupKey = null;
620 if (provider != null) {
621 key = getKey(Key.WIDGET, providerName);
622 backupKey = keyToBackupKey(key);
623 currentIds.add(backupKey);
624 } else {
625 Log.w(TAG, "empty intent on appwidget: " + id);
626 }
627 if (savedIds.contains(backupKey)) {
Chris Wren50c8f422014-01-15 16:10:39 -0500628 if (VERBOSE) Log.v(TAG, "already saved widget " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400629
630 // remember that we already backed this up previously
631 keys.add(key);
632 } else if (backupKey != null) {
633 if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
634 if ((out.rows - startRows) < MAX_WIDGETS_PER_PASS) {
Chris Wren50c8f422014-01-15 16:10:39 -0500635 if (VERBOSE) Log.v(TAG, "saving widget " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400636 previewLoader.setPreviewSize(spanX * profile.cellWidthPx,
637 spanY * profile.cellHeightPx, widgetSpacingLayout);
Chris Wren6d0dde02014-02-10 12:16:54 -0500638 byte[] blob = packWidget(dpi, previewLoader, mIconCache, provider);
Chris Wrenb1fd63b2013-10-03 15:43:58 -0400639 keys.add(key);
Chris Wrenfd13c712013-09-27 15:45:19 -0400640 writeRowToBackup(key, blob, out, data);
641
642 } else {
Chris Wren50c8f422014-01-15 16:10:39 -0500643 if (VERBOSE) Log.d(TAG, "deferring widget backup " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400644 // too many widgets for this pass, request another.
Chris Wren92aa4232013-10-04 11:29:36 -0400645 dataChanged();
Chris Wrenfd13c712013-09-27 15:45:19 -0400646 }
647 }
648 }
649 } finally {
650 cursor.close();
651 }
652 if (DEBUG) Log.d(TAG, "widget currentIds.size()=" + currentIds.size());
653
654 // these IDs must have been deleted
655 savedIds.removeAll(currentIds);
656 out.rows += removeDeletedKeysFromBackup(savedIds, data);
657 }
658
659 /**
660 * Read a widget from the stream.
661 *
662 * <P>Keys arrive in any order, so widgets that use this data may already exist.
663 *
664 * @param key identifier for the row
665 * @param buffer the serialized proto from the stream, may be larger than dataSize
666 * @param dataSize the size of the proto from the stream
667 * @param keys keys to mark as clean in the notes for next backup
668 */
669 private void restoreWidget(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
Chris Wren50c8f422014-01-15 16:10:39 -0500670 if (VERBOSE) Log.v(TAG, "unpacking widget " + key.id);
Chris Wrenfd13c712013-09-27 15:45:19 -0400671 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
672 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
673 try {
674 Widget widget = unpackWidget(buffer, 0, dataSize);
675 if (DEBUG) Log.d(TAG, "unpacked " + widget.provider);
676 if (widget.icon.data != null) {
677 Bitmap icon = BitmapFactory
678 .decodeByteArray(widget.icon.data, 0, widget.icon.data.length);
679 if (icon == null) {
680 Log.w(TAG, "failed to unpack widget icon for " + key.name);
681 }
682 }
Chris Wren5dee7af2013-12-20 17:22:11 -0500683
684 if (!mRestoreEnabled) {
Chris Wren50c8f422014-01-15 16:10:39 -0500685 if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
Chris Wren5dee7af2013-12-20 17:22:11 -0500686 return;
687 } else {
688 // future site of widget table mutation
689 }
Chris Wrenfd13c712013-09-27 15:45:19 -0400690 } catch (InvalidProtocolBufferNanoException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500691 Log.e(TAG, "failed to decode widget", e);
Chris Wrenfd13c712013-09-27 15:45:19 -0400692 }
693 }
694
Chris Wren22e130d2013-09-23 18:25:57 -0400695 /** create a new key, with an integer ID.
Chris Wren1ada10d2013-09-13 18:01:38 -0400696 *
697 * <P> Keys contain their own checksum instead of using
698 * the heavy-weight CheckedMessage wrapper.
699 */
700 private Key getKey(int type, long id) {
701 Key key = new Key();
702 key.type = type;
703 key.id = id;
704 key.checksum = checkKey(key);
705 return key;
706 }
707
Chris Wren22e130d2013-09-23 18:25:57 -0400708 /** create a new key for a named object.
709 *
710 * <P> Keys contain their own checksum instead of using
711 * the heavy-weight CheckedMessage wrapper.
712 */
713 private Key getKey(int type, String name) {
714 Key key = new Key();
715 key.type = type;
716 key.name = name;
717 key.checksum = checkKey(key);
718 return key;
719 }
720
Chris Wren1ada10d2013-09-13 18:01:38 -0400721 /** keys need to be strings, serialize and encode. */
722 private String keyToBackupKey(Key key) {
Chris Wren978194c2013-10-03 17:47:22 -0400723 return Base64.encodeToString(Key.toByteArray(key), Base64.NO_WRAP);
Chris Wren1ada10d2013-09-13 18:01:38 -0400724 }
725
726 /** keys need to be strings, decode and parse. */
727 private Key backupKeyToKey(String backupKey) throws KeyParsingException {
728 try {
729 Key key = Key.parseFrom(Base64.decode(backupKey, Base64.DEFAULT));
730 if (key.checksum != checkKey(key)) {
731 key = null;
732 throw new KeyParsingException("invalid key read from stream" + backupKey);
733 }
734 return key;
735 } catch (InvalidProtocolBufferNanoException e) {
736 throw new KeyParsingException(e);
737 } catch (IllegalArgumentException e) {
738 throw new KeyParsingException(e);
739 }
740 }
741
Chris Wren22e130d2013-09-23 18:25:57 -0400742 private String getKeyName(Key key) {
743 if (TextUtils.isEmpty(key.name)) {
744 return Long.toString(key.id);
745 } else {
746 return key.name;
747 }
748
749 }
750
751 private String geKeyType(Key key) {
752 switch (key.type) {
753 case Key.FAVORITE:
754 return "favorite";
755 case Key.SCREEN:
756 return "screen";
757 case Key.ICON:
758 return "icon";
Chris Wrenfd13c712013-09-27 15:45:19 -0400759 case Key.WIDGET:
760 return "widget";
Chris Wren22e130d2013-09-23 18:25:57 -0400761 default:
762 return "anonymous";
763 }
764 }
765
Chris Wren1ada10d2013-09-13 18:01:38 -0400766 /** Compute the checksum over the important bits of a key. */
767 private long checkKey(Key key) {
768 CRC32 checksum = new CRC32();
769 checksum.update(key.type);
770 checksum.update((int) (key.id & 0xffff));
771 checksum.update((int) ((key.id >> 32) & 0xffff));
772 if (!TextUtils.isEmpty(key.name)) {
773 checksum.update(key.name.getBytes());
774 }
775 return checksum.getValue();
776 }
777
778 /** Serialize a Favorite for persistence, including a checksum wrapper. */
779 private byte[] packFavorite(Cursor c) {
780 Favorite favorite = new Favorite();
781 favorite.id = c.getLong(ID_INDEX);
782 favorite.screen = c.getInt(SCREEN_INDEX);
783 favorite.container = c.getInt(CONTAINER_INDEX);
784 favorite.cellX = c.getInt(CELLX_INDEX);
785 favorite.cellY = c.getInt(CELLY_INDEX);
786 favorite.spanX = c.getInt(SPANX_INDEX);
787 favorite.spanY = c.getInt(SPANY_INDEX);
788 favorite.iconType = c.getInt(ICON_TYPE_INDEX);
789 if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
790 String iconPackage = c.getString(ICON_PACKAGE_INDEX);
791 if (!TextUtils.isEmpty(iconPackage)) {
792 favorite.iconPackage = iconPackage;
793 }
794 String iconResource = c.getString(ICON_RESOURCE_INDEX);
795 if (!TextUtils.isEmpty(iconResource)) {
796 favorite.iconResource = iconResource;
797 }
798 }
799 if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
800 byte[] blob = c.getBlob(ICON_INDEX);
801 if (blob != null && blob.length > 0) {
802 favorite.icon = blob;
803 }
804 }
805 String title = c.getString(TITLE_INDEX);
806 if (!TextUtils.isEmpty(title)) {
807 favorite.title = title;
808 }
809 String intent = c.getString(INTENT_INDEX);
810 if (!TextUtils.isEmpty(intent)) {
811 favorite.intent = intent;
812 }
813 favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
814 if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
815 favorite.appWidgetId = c.getInt(APPWIDGET_ID_INDEX);
816 String appWidgetProvider = c.getString(APPWIDGET_PROVIDER_INDEX);
817 if (!TextUtils.isEmpty(appWidgetProvider)) {
818 favorite.appWidgetProvider = appWidgetProvider;
819 }
820 }
821
822 return writeCheckedBytes(favorite);
823 }
824
825 /** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
Chris Wren5dee7af2013-12-20 17:22:11 -0500826 private ContentValues unpackFavorite(byte[] buffer, int offset, int dataSize)
Chris Wren1ada10d2013-09-13 18:01:38 -0400827 throws InvalidProtocolBufferNanoException {
828 Favorite favorite = new Favorite();
829 MessageNano.mergeFrom(favorite, readCheckedBytes(buffer, offset, dataSize));
Chris Wren50c8f422014-01-15 16:10:39 -0500830 if (VERBOSE) Log.v(TAG, "unpacked favorite " + favorite.itemType + ", " +
831 (TextUtils.isEmpty(favorite.title) ? favorite.id : favorite.title));
Chris Wren5dee7af2013-12-20 17:22:11 -0500832 ContentValues values = new ContentValues();
833 values.put(Favorites._ID, favorite.id);
834 values.put(Favorites.SCREEN, favorite.screen);
835 values.put(Favorites.CONTAINER, favorite.container);
836 values.put(Favorites.CELLX, favorite.cellX);
837 values.put(Favorites.CELLY, favorite.cellY);
838 values.put(Favorites.SPANX, favorite.spanX);
839 values.put(Favorites.SPANY, favorite.spanY);
840 values.put(Favorites.ICON_TYPE, favorite.iconType);
841 if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
842 values.put(Favorites.ICON_PACKAGE, favorite.iconPackage);
843 values.put(Favorites.ICON_RESOURCE, favorite.iconResource);
844 }
845 if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
846 values.put(Favorites.ICON, favorite.icon);
847 }
848 if (!TextUtils.isEmpty(favorite.title)) {
849 values.put(Favorites.TITLE, favorite.title);
850 } else {
851 values.put(Favorites.TITLE, "");
852 }
853 if (!TextUtils.isEmpty(favorite.intent)) {
854 values.put(Favorites.INTENT, favorite.intent);
855 }
856 values.put(Favorites.ITEM_TYPE, favorite.itemType);
857 if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
858 if (!TextUtils.isEmpty(favorite.appWidgetProvider)) {
859 values.put(Favorites.APPWIDGET_PROVIDER, favorite.appWidgetProvider);
860 }
861 values.put(Favorites.APPWIDGET_ID, favorite.appWidgetId);
862 }
Chris Wrenf4d08112014-01-16 18:13:56 -0500863
864 // Let LauncherModel know we've been here.
865 values.put(LauncherSettings.Favorites.RESTORED, 1);
866
Chris Wren5dee7af2013-12-20 17:22:11 -0500867 return values;
Chris Wren1ada10d2013-09-13 18:01:38 -0400868 }
869
870 /** Serialize a Screen for persistence, including a checksum wrapper. */
871 private byte[] packScreen(Cursor c) {
872 Screen screen = new Screen();
873 screen.id = c.getLong(ID_INDEX);
874 screen.rank = c.getInt(SCREEN_RANK_INDEX);
875
876 return writeCheckedBytes(screen);
877 }
878
879 /** Deserialize a Screen from persistence, after verifying checksum wrapper. */
Chris Wren5dee7af2013-12-20 17:22:11 -0500880 private ContentValues unpackScreen(byte[] buffer, int offset, int dataSize)
Chris Wren1ada10d2013-09-13 18:01:38 -0400881 throws InvalidProtocolBufferNanoException {
882 Screen screen = new Screen();
883 MessageNano.mergeFrom(screen, readCheckedBytes(buffer, offset, dataSize));
Chris Wren50c8f422014-01-15 16:10:39 -0500884 if (VERBOSE) Log.v(TAG, "unpacked screen " + screen.id + "/" + screen.rank);
Chris Wren5dee7af2013-12-20 17:22:11 -0500885 ContentValues values = new ContentValues();
886 values.put(WorkspaceScreens._ID, screen.id);
887 values.put(WorkspaceScreens.SCREEN_RANK, screen.rank);
888 return values;
Chris Wren1ada10d2013-09-13 18:01:38 -0400889 }
890
Chris Wren22e130d2013-09-23 18:25:57 -0400891 /** Serialize an icon Resource for persistence, including a checksum wrapper. */
892 private byte[] packIcon(int dpi, Bitmap icon) {
893 Resource res = new Resource();
894 res.dpi = dpi;
895 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400896 if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wren22e130d2013-09-23 18:25:57 -0400897 res.data = os.toByteArray();
898 }
899 return writeCheckedBytes(res);
900 }
901
902 /** Deserialize an icon resource from persistence, after verifying checksum wrapper. */
Chris Wren6d0dde02014-02-10 12:16:54 -0500903 private static Resource unpackIcon(byte[] buffer, int offset, int dataSize)
Chris Wren22e130d2013-09-23 18:25:57 -0400904 throws InvalidProtocolBufferNanoException {
905 Resource res = new Resource();
906 MessageNano.mergeFrom(res, readCheckedBytes(buffer, offset, dataSize));
Chris Wren50c8f422014-01-15 16:10:39 -0500907 if (VERBOSE) Log.v(TAG, "unpacked icon " + res.dpi + "/" + res.data.length);
Chris Wren22e130d2013-09-23 18:25:57 -0400908 return res;
909 }
910
Chris Wrenfd13c712013-09-27 15:45:19 -0400911 /** Serialize a widget for persistence, including a checksum wrapper. */
912 private byte[] packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache,
913 ComponentName provider) {
914 final AppWidgetProviderInfo info = findAppWidgetProviderInfo(provider);
915 Widget widget = new Widget();
916 widget.provider = provider.flattenToShortString();
917 widget.label = info.label;
918 widget.configure = info.configure != null;
919 if (info.icon != 0) {
920 widget.icon = new Resource();
921 Drawable fullResIcon = iconCache.getFullResIcon(provider.getPackageName(), info.icon);
Chris Wren92aa4232013-10-04 11:29:36 -0400922 Bitmap icon = Utilities.createIconBitmap(fullResIcon, mContext);
Chris Wrenfd13c712013-09-27 15:45:19 -0400923 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400924 if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wrenfd13c712013-09-27 15:45:19 -0400925 widget.icon.data = os.toByteArray();
926 widget.icon.dpi = dpi;
927 }
928 }
929 if (info.previewImage != 0) {
930 widget.preview = new Resource();
931 Bitmap preview = previewLoader.generateWidgetPreview(info, null);
932 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400933 if (preview.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wrenfd13c712013-09-27 15:45:19 -0400934 widget.preview.data = os.toByteArray();
935 widget.preview.dpi = dpi;
936 }
937 }
938 return writeCheckedBytes(widget);
939 }
940
941 /** Deserialize a widget from persistence, after verifying checksum wrapper. */
942 private Widget unpackWidget(byte[] buffer, int offset, int dataSize)
943 throws InvalidProtocolBufferNanoException {
944 Widget widget = new Widget();
945 MessageNano.mergeFrom(widget, readCheckedBytes(buffer, offset, dataSize));
Chris Wren50c8f422014-01-15 16:10:39 -0500946 if (VERBOSE) Log.v(TAG, "unpacked widget " + widget.provider);
Chris Wrenfd13c712013-09-27 15:45:19 -0400947 return widget;
948 }
949
Chris Wren1ada10d2013-09-13 18:01:38 -0400950 /**
951 * Read the old journal from the input file.
952 *
953 * In the event of any error, just pretend we didn't have a journal,
954 * in that case, do a full backup.
955 *
956 * @param oldState the read-0only file descriptor pointing to the old journal
Chris Wren65b6a602014-01-10 14:11:25 -0500957 * @return a Journal protocol buffer
Chris Wren1ada10d2013-09-13 18:01:38 -0400958 */
959 private Journal readJournal(ParcelFileDescriptor oldState) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400960 Journal journal = new Journal();
Chris Wren92aa4232013-10-04 11:29:36 -0400961 if (oldState == null) {
962 return journal;
963 }
964 FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor());
965 try {
Chris Wren65b6a602014-01-10 14:11:25 -0500966 int availableBytes = inStream.available();
967 if (DEBUG) Log.d(TAG, "available " + availableBytes);
968 if (availableBytes < MAX_JOURNAL_SIZE) {
969 byte[] buffer = new byte[availableBytes];
Chris Wren1ada10d2013-09-13 18:01:38 -0400970 int bytesRead = 0;
Chris Wren65b6a602014-01-10 14:11:25 -0500971 boolean valid = false;
Chris Wren50c8f422014-01-15 16:10:39 -0500972 InvalidProtocolBufferNanoException lastProtoException = null;
Chris Wren65b6a602014-01-10 14:11:25 -0500973 while (availableBytes > 0) {
Chris Wren92aa4232013-10-04 11:29:36 -0400974 try {
Chris Wren65b6a602014-01-10 14:11:25 -0500975 // OMG what are you doing? This is crazy inefficient!
976 // If we read a byte that is not ours, we will cause trouble: b/12491813
977 // However, we don't know how many bytes to expect (oops).
978 // So we have to step through *slowly*, watching for the end.
979 int result = inStream.read(buffer, bytesRead, 1);
Chris Wren92aa4232013-10-04 11:29:36 -0400980 if (result > 0) {
Chris Wren65b6a602014-01-10 14:11:25 -0500981 availableBytes -= result;
Chris Wren92aa4232013-10-04 11:29:36 -0400982 bytesRead += result;
Chris Wren65b6a602014-01-10 14:11:25 -0500983 if (DEBUG && (bytesRead % 100 == 0)) {
984 Log.d(TAG, "read some bytes: " + bytesRead);
985 }
Chris Wren92aa4232013-10-04 11:29:36 -0400986 } else {
Chris Wren65b6a602014-01-10 14:11:25 -0500987 Log.w(TAG, "unexpected end of file while reading journal.");
988 // stop reading and see what there is to parse
989 availableBytes = 0;
Chris Wren92aa4232013-10-04 11:29:36 -0400990 }
991 } catch (IOException e) {
Chris Wren92aa4232013-10-04 11:29:36 -0400992 buffer = null;
Chris Wren65b6a602014-01-10 14:11:25 -0500993 availableBytes = 0;
994 }
995
996 // check the buffer to see if we have a valid journal
997 try {
998 MessageNano.mergeFrom(journal, readCheckedBytes(buffer, 0, bytesRead));
999 // if we are here, then we have read a valid, checksum-verified journal
1000 valid = true;
1001 availableBytes = 0;
Chris Wren50c8f422014-01-15 16:10:39 -05001002 if (VERBOSE) Log.v(TAG, "read " + bytesRead + " bytes of journal");
Chris Wren65b6a602014-01-10 14:11:25 -05001003 } catch (InvalidProtocolBufferNanoException e) {
1004 // if we don't have the whole journal yet, mergeFrom will throw. keep going.
Chris Wren50c8f422014-01-15 16:10:39 -05001005 lastProtoException = e;
Chris Wren65b6a602014-01-10 14:11:25 -05001006 journal.clear();
Chris Wren92aa4232013-10-04 11:29:36 -04001007 }
Chris Wren1ada10d2013-09-13 18:01:38 -04001008 }
Chris Wren92aa4232013-10-04 11:29:36 -04001009 if (DEBUG) Log.d(TAG, "journal bytes read: " + bytesRead);
Chris Wren65b6a602014-01-10 14:11:25 -05001010 if (!valid) {
Chris Wren50c8f422014-01-15 16:10:39 -05001011 Log.w(TAG, "could not find a valid journal", lastProtoException);
Chris Wren1ada10d2013-09-13 18:01:38 -04001012 }
1013 }
Chris Wren92aa4232013-10-04 11:29:36 -04001014 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -05001015 Log.w(TAG, "failed to close the journal", e);
Chris Wren92aa4232013-10-04 11:29:36 -04001016 } finally {
Chris Wren1ada10d2013-09-13 18:01:38 -04001017 try {
1018 inStream.close();
1019 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -05001020 Log.w(TAG, "failed to close the journal", e);
Chris Wren1ada10d2013-09-13 18:01:38 -04001021 }
1022 }
1023 return journal;
1024 }
1025
Chris Wren22e130d2013-09-23 18:25:57 -04001026 private void writeRowToBackup(Key key, byte[] blob, Journal out,
1027 BackupDataOutput data) throws IOException {
1028 String backupKey = keyToBackupKey(key);
1029 data.writeEntityHeader(backupKey, blob.length);
1030 data.writeEntityData(blob, blob.length);
1031 out.rows++;
1032 out.bytes += blob.length;
Chris Wren50c8f422014-01-15 16:10:39 -05001033 if (VERBOSE) Log.v(TAG, "saving " + geKeyType(key) + " " + backupKey + ": " +
Chris Wren22e130d2013-09-23 18:25:57 -04001034 getKeyName(key) + "/" + blob.length);
Chris Wren92aa4232013-10-04 11:29:36 -04001035 if(DEBUG_PAYLOAD) {
Chris Wren2b6c21d2013-10-02 14:16:04 -04001036 String encoded = Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP);
1037 final int chunkSize = 1024;
1038 for (int offset = 0; offset < encoded.length(); offset += chunkSize) {
1039 int end = offset + chunkSize;
1040 end = Math.min(end, encoded.length());
Chris Wren50c8f422014-01-15 16:10:39 -05001041 Log.w(TAG, "wrote " + encoded.substring(offset, end));
Chris Wren2b6c21d2013-10-02 14:16:04 -04001042 }
1043 }
Chris Wren22e130d2013-09-23 18:25:57 -04001044 }
1045
1046 private Set<String> getSavedIdsByType(int type, Journal in) {
1047 Set<String> savedIds = new HashSet<String>();
1048 for(int i = 0; i < in.key.length; i++) {
1049 Key key = in.key[i];
1050 if (key.type == type) {
1051 savedIds.add(keyToBackupKey(key));
1052 }
1053 }
1054 return savedIds;
1055 }
1056
1057 private int removeDeletedKeysFromBackup(Set<String> deletedIds, BackupDataOutput data)
1058 throws IOException {
1059 int rows = 0;
1060 for(String deleted: deletedIds) {
Chris Wren50c8f422014-01-15 16:10:39 -05001061 if (VERBOSE) Log.v(TAG, "dropping deleted item " + deleted);
Chris Wren22e130d2013-09-23 18:25:57 -04001062 data.writeEntityHeader(deleted, -1);
1063 rows++;
1064 }
1065 return rows;
1066 }
1067
Chris Wren1ada10d2013-09-13 18:01:38 -04001068 /**
1069 * Write the new journal to the output file.
1070 *
1071 * In the event of any error, just pretend we didn't have a journal,
1072 * in that case, do a full backup.
1073
1074 * @param newState the write-only file descriptor pointing to the new journal
1075 * @param journal a Journal protocol buffer
1076 */
1077 private void writeJournal(ParcelFileDescriptor newState, Journal journal) {
1078 FileOutputStream outStream = null;
1079 try {
1080 outStream = new FileOutputStream(newState.getFileDescriptor());
Chris Wren65b6a602014-01-10 14:11:25 -05001081 final byte[] journalBytes = writeCheckedBytes(journal);
Chris Wren65b6a602014-01-10 14:11:25 -05001082 outStream.write(journalBytes);
Chris Wren1ada10d2013-09-13 18:01:38 -04001083 outStream.close();
Chris Wren50c8f422014-01-15 16:10:39 -05001084 if (VERBOSE) Log.v(TAG, "wrote " + journalBytes.length + " bytes of journal");
Chris Wren1ada10d2013-09-13 18:01:38 -04001085 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -05001086 Log.w(TAG, "failed to write backup journal", e);
Chris Wren1ada10d2013-09-13 18:01:38 -04001087 }
1088 }
1089
1090 /** Wrap a proto in a CheckedMessage and compute the checksum. */
1091 private byte[] writeCheckedBytes(MessageNano proto) {
1092 CheckedMessage wrapper = new CheckedMessage();
1093 wrapper.payload = MessageNano.toByteArray(proto);
1094 CRC32 checksum = new CRC32();
1095 checksum.update(wrapper.payload);
1096 wrapper.checksum = checksum.getValue();
1097 return MessageNano.toByteArray(wrapper);
1098 }
1099
1100 /** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
Chris Wren6d0dde02014-02-10 12:16:54 -05001101 private static byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
Chris Wren1ada10d2013-09-13 18:01:38 -04001102 throws InvalidProtocolBufferNanoException {
1103 CheckedMessage wrapper = new CheckedMessage();
1104 MessageNano.mergeFrom(wrapper, buffer, offset, dataSize);
1105 CRC32 checksum = new CRC32();
1106 checksum.update(wrapper.payload);
1107 if (wrapper.checksum != checksum.getValue()) {
1108 throw new InvalidProtocolBufferNanoException("checksum does not match");
1109 }
1110 return wrapper.payload;
1111 }
1112
Chris Wrenfd13c712013-09-27 15:45:19 -04001113 private AppWidgetProviderInfo findAppWidgetProviderInfo(ComponentName component) {
1114 if (mWidgetMap == null) {
1115 List<AppWidgetProviderInfo> widgets =
Chris Wren92aa4232013-10-04 11:29:36 -04001116 AppWidgetManager.getInstance(mContext).getInstalledProviders();
Chris Wrenfd13c712013-09-27 15:45:19 -04001117 mWidgetMap = new HashMap<ComponentName, AppWidgetProviderInfo>(widgets.size());
1118 for (AppWidgetProviderInfo info : widgets) {
1119 mWidgetMap.put(info.provider, info);
1120 }
1121 }
1122 return mWidgetMap.get(component);
1123 }
1124
Chris Wren6d0dde02014-02-10 12:16:54 -05001125
1126 private boolean initializeIconCache() {
1127 if (mIconCache != null) {
1128 return true;
1129 }
1130
1131 final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
1132 if (appState == null) {
1133 Throwable stackTrace = new Throwable();
1134 stackTrace.fillInStackTrace();
1135 Log.w(TAG, "Failed to get app state during backup/restore", stackTrace);
1136 return false;
1137 }
1138 mIconCache = appState.getIconCache();
1139 return mIconCache != null;
1140 }
1141
Chris Wren71144262014-02-27 15:49:39 -05001142
1143 // check if the launcher is in a state to support backup
1144 private boolean launcherIsReady() {
1145 ContentResolver cr = mContext.getContentResolver();
1146 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION, null, null, null);
1147 if (cursor == null) {
1148 // launcher data has been wiped, do nothing
1149 return false;
1150 }
1151 cursor.close();
1152
1153 if (!initializeIconCache()) {
1154 // launcher services are unavailable, try again later
1155 dataChanged();
1156 return false;
1157 }
1158
1159 return true;
1160 }
1161
Chris Wren1ada10d2013-09-13 18:01:38 -04001162 private class KeyParsingException extends Throwable {
1163 private KeyParsingException(Throwable cause) {
1164 super(cause);
1165 }
1166
1167 public KeyParsingException(String reason) {
1168 super(reason);
1169 }
1170 }
1171}