blob: b2ac577f40fdcef9c4c0f3c8bdfcceb59694e998 [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
Chris Wren92aa4232013-10-04 11:29:36 -040018import android.app.backup.BackupDataInputStream;
Chris Wren1ada10d2013-09-13 18:01:38 -040019import android.app.backup.BackupDataOutput;
Chris Wren4d89e2a2013-10-09 17:03:50 -040020import android.app.backup.BackupHelper;
Chris Wren1ada10d2013-09-13 18:01:38 -040021import android.app.backup.BackupManager;
Chris Wren22e130d2013-09-23 18:25:57 -040022import android.appwidget.AppWidgetManager;
23import android.appwidget.AppWidgetProviderInfo;
24import android.content.ComponentName;
Chris Wren1ada10d2013-09-13 18:01:38 -040025import android.content.ContentResolver;
Chris Wren5dee7af2013-12-20 17:22:11 -050026import android.content.ContentValues;
Chris Wren1ada10d2013-09-13 18:01:38 -040027import android.content.Context;
Chris Wren22e130d2013-09-23 18:25:57 -040028import android.content.Intent;
Sunny Goyalef728d42014-10-22 11:28:28 -070029import android.content.pm.PackageManager.NameNotFoundException;
Chris Wren1ada10d2013-09-13 18:01:38 -040030import android.database.Cursor;
Chris Wren22e130d2013-09-23 18:25:57 -040031import android.graphics.Bitmap;
32import android.graphics.BitmapFactory;
Chris Wrenfd13c712013-09-27 15:45:19 -040033import android.graphics.drawable.Drawable;
Chris Wren1ada10d2013-09-13 18:01:38 -040034import android.os.ParcelFileDescriptor;
Chris Wren1ada10d2013-09-13 18:01:38 -040035import android.text.TextUtils;
36import android.util.Base64;
37import android.util.Log;
38
Sunny Goyalef728d42014-10-22 11:28:28 -070039import com.android.launcher3.LauncherSettings.Favorites;
40import com.android.launcher3.LauncherSettings.WorkspaceScreens;
41import com.android.launcher3.backup.BackupProtos;
42import com.android.launcher3.backup.BackupProtos.CheckedMessage;
43import com.android.launcher3.backup.BackupProtos.Favorite;
44import com.android.launcher3.backup.BackupProtos.Journal;
45import com.android.launcher3.backup.BackupProtos.Key;
46import com.android.launcher3.backup.BackupProtos.Resource;
47import com.android.launcher3.backup.BackupProtos.Screen;
48import com.android.launcher3.backup.BackupProtos.Widget;
49import com.android.launcher3.compat.UserHandleCompat;
50import com.android.launcher3.compat.UserManagerCompat;
51import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
52import com.google.protobuf.nano.MessageNano;
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.zip.CRC32;
64
65/**
66 * Persist the launcher home state across calamities.
67 */
Chris Wren92aa4232013-10-04 11:29:36 -040068public class LauncherBackupHelper implements BackupHelper {
Chris Wren1ada10d2013-09-13 18:01:38 -040069
Chris Wren92aa4232013-10-04 11:29:36 -040070 private static final String TAG = "LauncherBackupHelper";
Chris Wren50c8f422014-01-15 16:10:39 -050071 private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
72 private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
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 final String[] FAVORITE_PROJECTION = {
Sunny Goyalef728d42014-10-22 11:28:28 -070092 Favorites._ID, // 0
93 Favorites.MODIFIED, // 1
94 Favorites.INTENT, // 2
95 Favorites.APPWIDGET_PROVIDER, // 3
96 Favorites.APPWIDGET_ID, // 4
97 Favorites.CELLX, // 5
98 Favorites.CELLY, // 6
99 Favorites.CONTAINER, // 7
100 Favorites.ICON, // 8
101 Favorites.ICON_PACKAGE, // 9
102 Favorites.ICON_RESOURCE, // 10
103 Favorites.ICON_TYPE, // 11
104 Favorites.ITEM_TYPE, // 12
105 Favorites.SCREEN, // 13
106 Favorites.SPANX, // 14
107 Favorites.SPANY, // 15
108 Favorites.TITLE, // 16
109 Favorites.PROFILE_ID, // 17
Chris Wren1ada10d2013-09-13 18:01:38 -0400110 };
111
112 private static final int ID_INDEX = 0;
Chris Wren22e130d2013-09-23 18:25:57 -0400113 private static final int ID_MODIFIED = 1;
114 private static final int INTENT_INDEX = 2;
115 private static final int APPWIDGET_PROVIDER_INDEX = 3;
116 private static final int APPWIDGET_ID_INDEX = 4;
117 private static final int CELLX_INDEX = 5;
118 private static final int CELLY_INDEX = 6;
119 private static final int CONTAINER_INDEX = 7;
120 private static final int ICON_INDEX = 8;
121 private static final int ICON_PACKAGE_INDEX = 9;
122 private static final int ICON_RESOURCE_INDEX = 10;
123 private static final int ICON_TYPE_INDEX = 11;
124 private static final int ITEM_TYPE_INDEX = 12;
125 private static final int SCREEN_INDEX = 13;
126 private static final int SPANX_INDEX = 14;
127 private static final int SPANY_INDEX = 15;
128 private static final int TITLE_INDEX = 16;
Chris Wren1ada10d2013-09-13 18:01:38 -0400129
130 private static final String[] SCREEN_PROJECTION = {
Sunny Goyalef728d42014-10-22 11:28:28 -0700131 WorkspaceScreens._ID, // 0
132 WorkspaceScreens.MODIFIED, // 1
133 WorkspaceScreens.SCREEN_RANK // 2
Chris Wren1ada10d2013-09-13 18:01:38 -0400134 };
135
Chris Wren22e130d2013-09-23 18:25:57 -0400136 private static final int SCREEN_RANK_INDEX = 2;
Chris Wren1ada10d2013-09-13 18:01:38 -0400137
Chris Wren92aa4232013-10-04 11:29:36 -0400138 private final Context mContext;
Sunny Goyalef728d42014-10-22 11:28:28 -0700139 private final HashSet<String> mExistingKeys;
Sunny Goyal42de82f2014-09-26 22:09:29 -0700140 private final ArrayList<Key> mKeys;
Chris Wren1ada10d2013-09-13 18:01:38 -0400141
Sunny Goyalef728d42014-10-22 11:28:28 -0700142 private IconCache mIconCache;
143 private BackupManager mBackupManager;
144 private HashMap<ComponentName, AppWidgetProviderInfo> mWidgetMap;
145 private byte[] mBuffer = new byte[512];
146 private long mLastBackupRestoreTime;
147
Chris Wren4b171362014-01-13 17:39:02 -0500148 public LauncherBackupHelper(Context context, boolean restoreEnabled) {
Chris Wren92aa4232013-10-04 11:29:36 -0400149 mContext = context;
Sunny Goyalef728d42014-10-22 11:28:28 -0700150 mExistingKeys = new HashSet<String>();
Sunny Goyal42de82f2014-09-26 22:09:29 -0700151 mKeys = new ArrayList<Key>();
Chris Wren92aa4232013-10-04 11:29:36 -0400152 }
153
154 private void dataChanged() {
Sunny Goyalef728d42014-10-22 11:28:28 -0700155 if (mBackupManager == null) {
156 mBackupManager = new BackupManager(mContext);
Chris Wren1ada10d2013-09-13 18:01:38 -0400157 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700158 mBackupManager.dataChanged();
159 }
160
161 private void applyJournal(Journal journal) {
162 mLastBackupRestoreTime = journal.t;
163 mExistingKeys.clear();
164 if (journal.key != null) {
165 for (Key key : journal.key) {
166 mExistingKeys.add(keyToBackupKey(key));
167 }
168 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400169 }
170
171 /**
172 * Back up launcher data so we can restore the user's state on a new device.
173 *
174 * <P>The journal is a timestamp and a list of keys that were saved as of that time.
175 *
176 * <P>Keys may come back in any order, so each key/value is one complete row of the database.
177 *
178 * @param oldState notes from the last backup
179 * @param data incremental key/value pairs to persist off-device
180 * @param newState notes for the next backup
181 * @throws IOException
182 */
183 @Override
Chris Wren92aa4232013-10-04 11:29:36 -0400184 public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
185 ParcelFileDescriptor newState) {
Chris Wren50c8f422014-01-15 16:10:39 -0500186 if (VERBOSE) Log.v(TAG, "onBackup");
Chris Wren1ada10d2013-09-13 18:01:38 -0400187
188 Journal in = readJournal(oldState);
Sunny Goyalef728d42014-10-22 11:28:28 -0700189 if (!launcherIsReady()) {
190 // Perform backup later.
191 writeJournal(newState, in);
192 return;
193 }
194 Log.v(TAG, "lastBackupTime = " + in.t);
195 mKeys.clear();
196 applyJournal(in);
Chris Wren1ada10d2013-09-13 18:01:38 -0400197
Sunny Goyalef728d42014-10-22 11:28:28 -0700198 // Record the time before performing backup so that entries edited while the backup
199 // was going on, do not get missed in next backup.
200 long newBackupTime = System.currentTimeMillis();
Chris Wren1ada10d2013-09-13 18:01:38 -0400201
Sunny Goyalef728d42014-10-22 11:28:28 -0700202 try {
203 backupFavorites(data);
204 backupScreens(data);
205 backupIcons(data);
206 backupWidgets(data);
Chris Wren1ada10d2013-09-13 18:01:38 -0400207
Sunny Goyalef728d42014-10-22 11:28:28 -0700208 // Delete any key which still exist in the old backup, but is not valid anymore.
209 HashSet<String> validKeys = new HashSet<String>();
210 for (Key key : mKeys) {
211 validKeys.add(keyToBackupKey(key));
Chris Wren71144262014-02-27 15:49:39 -0500212 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700213 mExistingKeys.removeAll(validKeys);
214
215 // Delete anything left in the existing keys.
216 for (String deleted: mExistingKeys) {
217 if (VERBOSE) Log.v(TAG, "dropping deleted item " + deleted);
218 data.writeEntityHeader(deleted, -1);
219 }
220
221 mExistingKeys.clear();
222 mLastBackupRestoreTime = newBackupTime;
223 } catch (IOException e) {
224 Log.e(TAG, "launcher backup has failed", e);
Chris Wren92aa4232013-10-04 11:29:36 -0400225 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400226
Sunny Goyalef728d42014-10-22 11:28:28 -0700227 writeNewStateDescription(newState);
Chris Wren1ada10d2013-09-13 18:01:38 -0400228 }
229
230 /**
Chris Wren92aa4232013-10-04 11:29:36 -0400231 * Restore launcher configuration from the restored data stream.
Chris Wren1ada10d2013-09-13 18:01:38 -0400232 *
233 * <P>Keys may arrive in any order.
234 *
Chris Wren92aa4232013-10-04 11:29:36 -0400235 * @param data the key/value pair from the server
Chris Wren1ada10d2013-09-13 18:01:38 -0400236 */
237 @Override
Chris Wren92aa4232013-10-04 11:29:36 -0400238 public void restoreEntity(BackupDataInputStream data) {
Sunny Goyalef728d42014-10-22 11:28:28 -0700239 int dataSize = data.size();
240 if (mBuffer.length < dataSize) {
241 mBuffer = new byte[dataSize];
Chris Wren92aa4232013-10-04 11:29:36 -0400242 }
243 try {
Sunny Goyalef728d42014-10-22 11:28:28 -0700244 int bytesRead = data.read(mBuffer, 0, dataSize);
245 if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
246 String backupKey = data.getKey();
247 Key key = backupKeyToKey(backupKey);
Chris Wren50c8f422014-01-15 16:10:39 -0500248 mKeys.add(key);
Chris Wren92aa4232013-10-04 11:29:36 -0400249 switch (key.type) {
250 case Key.FAVORITE:
Sunny Goyalef728d42014-10-22 11:28:28 -0700251 restoreFavorite(key, mBuffer, dataSize);
Chris Wren92aa4232013-10-04 11:29:36 -0400252 break;
253
254 case Key.SCREEN:
Sunny Goyalef728d42014-10-22 11:28:28 -0700255 restoreScreen(key, mBuffer, dataSize);
Chris Wren92aa4232013-10-04 11:29:36 -0400256 break;
257
258 case Key.ICON:
Sunny Goyalef728d42014-10-22 11:28:28 -0700259 restoreIcon(key, mBuffer, dataSize);
Chris Wren92aa4232013-10-04 11:29:36 -0400260 break;
261
262 case Key.WIDGET:
Sunny Goyalef728d42014-10-22 11:28:28 -0700263 restoreWidget(key, mBuffer, dataSize);
Chris Wren92aa4232013-10-04 11:29:36 -0400264 break;
265
266 default:
267 Log.w(TAG, "unknown restore entity type: " + key.type);
Sunny Goyalef728d42014-10-22 11:28:28 -0700268 mKeys.remove(key);
Chris Wren92aa4232013-10-04 11:29:36 -0400269 break;
Chris Wren1ada10d2013-09-13 18:01:38 -0400270 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700271 } catch (IOException e) {
272 Log.w(TAG, "ignoring unparsable backup entry", e);
Chris Wren1ada10d2013-09-13 18:01:38 -0400273 }
Chris Wren92aa4232013-10-04 11:29:36 -0400274 }
275
276 /**
277 * Record the restore state for the next backup.
278 *
279 * @param newState notes about the backup state after restore.
280 */
281 @Override
282 public void writeNewStateDescription(ParcelFileDescriptor newState) {
Sunny Goyalef728d42014-10-22 11:28:28 -0700283 writeJournal(newState, getCurrentStateJournal());
284 }
285
286 private Journal getCurrentStateJournal() {
287 Journal journal = new Journal();
288 journal.t = mLastBackupRestoreTime;
289 journal.key = mKeys.toArray(new BackupProtos.Key[mKeys.size()]);
290 journal.appVersion = getAppVersion();
291 return journal;
292 }
293
294 private int getAppVersion() {
295 try {
296 return mContext.getPackageManager()
297 .getPackageInfo(mContext.getPackageName(), 0).versionCode;
298 } catch (NameNotFoundException e) {
299 return 0;
300 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400301 }
302
303 /**
304 * Write all modified favorites to the data stream.
305 *
Chris Wren1ada10d2013-09-13 18:01:38 -0400306 * @param data output stream for key/value pairs
Chris Wren1ada10d2013-09-13 18:01:38 -0400307 * @throws IOException
308 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700309 private void backupFavorites(BackupDataOutput data) throws IOException {
Chris Wren1ada10d2013-09-13 18:01:38 -0400310 // persist things that have changed since the last backup
Chris Wren92aa4232013-10-04 11:29:36 -0400311 ContentResolver cr = mContext.getContentResolver();
Sunny Goyalffe83f12014-08-14 17:39:34 -0700312 // Don't backup apps in other profiles for now.
Chris Wren22e130d2013-09-23 18:25:57 -0400313 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
Sunny Goyalffe83f12014-08-14 17:39:34 -0700314 getUserSelectionArg(), null, null);
Chris Wren1ada10d2013-09-13 18:01:38 -0400315 try {
Chris Wren22e130d2013-09-23 18:25:57 -0400316 cursor.moveToPosition(-1);
317 while(cursor.moveToNext()) {
318 final long id = cursor.getLong(ID_INDEX);
Sunny Goyalffe83f12014-08-14 17:39:34 -0700319 final long updateTime = cursor.getLong(ID_MODIFIED);
320 Key key = getKey(Key.FAVORITE, id);
Sunny Goyalef728d42014-10-22 11:28:28 -0700321 mKeys.add(key);
Sunny Goyalffe83f12014-08-14 17:39:34 -0700322 final String backupKey = keyToBackupKey(key);
Sunny Goyalef728d42014-10-22 11:28:28 -0700323 if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime) {
324 writeRowToBackup(key, packFavorite(cursor), data);
Chris Wren50c8f422014-01-15 16:10:39 -0500325 } else {
Sunny Goyalef728d42014-10-22 11:28:28 -0700326 if (DEBUG) Log.d(TAG, "favorite already backup up: " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400327 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400328 }
329 } finally {
Chris Wren22e130d2013-09-23 18:25:57 -0400330 cursor.close();
Chris Wren1ada10d2013-09-13 18:01:38 -0400331 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400332 }
333
334 /**
335 * Read a favorite from the stream.
336 *
337 * <P>Keys arrive in any order, so screens and containers may not exist yet.
338 *
339 * @param key identifier for the row
340 * @param buffer the serialized proto from the stream, may be larger than dataSize
341 * @param dataSize the size of the proto from the stream
Chris Wren1ada10d2013-09-13 18:01:38 -0400342 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700343 private void restoreFavorite(Key key, byte[] buffer, int dataSize) throws IOException {
Chris Wren50c8f422014-01-15 16:10:39 -0500344 if (VERBOSE) Log.v(TAG, "unpacking favorite " + key.id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400345 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
346 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
347
Sunny Goyalef728d42014-10-22 11:28:28 -0700348 ContentResolver cr = mContext.getContentResolver();
349 ContentValues values = unpackFavorite(buffer, dataSize);
350 cr.insert(Favorites.CONTENT_URI_NO_NOTIFICATION, values);
Chris Wren1ada10d2013-09-13 18:01:38 -0400351 }
352
353 /**
354 * Write all modified screens to the data stream.
355 *
Chris Wren1ada10d2013-09-13 18:01:38 -0400356 * @param data output stream for key/value pairs
Chris Wren22e130d2013-09-23 18:25:57 -0400357 * @throws IOException
Chris Wren1ada10d2013-09-13 18:01:38 -0400358 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700359 private void backupScreens(BackupDataOutput data) throws IOException {
Chris Wren1ada10d2013-09-13 18:01:38 -0400360 // persist things that have changed since the last backup
Chris Wren92aa4232013-10-04 11:29:36 -0400361 ContentResolver cr = mContext.getContentResolver();
Chris Wren22e130d2013-09-23 18:25:57 -0400362 Cursor cursor = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
363 null, null, null);
Chris Wren1ada10d2013-09-13 18:01:38 -0400364 try {
Chris Wren22e130d2013-09-23 18:25:57 -0400365 cursor.moveToPosition(-1);
Sunny Goyalef728d42014-10-22 11:28:28 -0700366 if (DEBUG) Log.d(TAG, "dumping screens after: " + mLastBackupRestoreTime);
Chris Wren22e130d2013-09-23 18:25:57 -0400367 while(cursor.moveToNext()) {
368 final long id = cursor.getLong(ID_INDEX);
369 final long updateTime = cursor.getLong(ID_MODIFIED);
Chris Wren1ada10d2013-09-13 18:01:38 -0400370 Key key = getKey(Key.SCREEN, id);
Sunny Goyalef728d42014-10-22 11:28:28 -0700371 mKeys.add(key);
Chris Wren5743aa92014-01-10 18:02:06 -0500372 final String backupKey = keyToBackupKey(key);
Sunny Goyalef728d42014-10-22 11:28:28 -0700373 if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime) {
374 writeRowToBackup(key, packScreen(cursor), data);
Chris Wren5dee7af2013-12-20 17:22:11 -0500375 } else {
Sunny Goyalef728d42014-10-22 11:28:28 -0700376 if (VERBOSE) Log.v(TAG, "screen already backup up " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400377 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400378 }
379 } finally {
Chris Wren22e130d2013-09-23 18:25:57 -0400380 cursor.close();
Chris Wren1ada10d2013-09-13 18:01:38 -0400381 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400382 }
383
384 /**
385 * Read a screen from the stream.
386 *
387 * <P>Keys arrive in any order, so children of this screen may already exist.
388 *
389 * @param key identifier for the row
390 * @param buffer the serialized proto from the stream, may be larger than dataSize
391 * @param dataSize the size of the proto from the stream
Chris Wren1ada10d2013-09-13 18:01:38 -0400392 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700393 private void restoreScreen(Key key, byte[] buffer, int dataSize) throws IOException {
Chris Wren50c8f422014-01-15 16:10:39 -0500394 if (VERBOSE) Log.v(TAG, "unpacking screen " + key.id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400395 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
396 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
Chris Wren5dee7af2013-12-20 17:22:11 -0500397
Sunny Goyalef728d42014-10-22 11:28:28 -0700398 ContentResolver cr = mContext.getContentResolver();
399 ContentValues values = unpackScreen(buffer, dataSize);
400 cr.insert(WorkspaceScreens.CONTENT_URI, values);
Chris Wren1ada10d2013-09-13 18:01:38 -0400401 }
402
Chris Wren22e130d2013-09-23 18:25:57 -0400403 /**
404 * Write all the static icon resources we need to render placeholders
405 * for a package that is not installed.
406 *
Chris Wren22e130d2013-09-23 18:25:57 -0400407 * @param data output stream for key/value pairs
Chris Wren22e130d2013-09-23 18:25:57 -0400408 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700409 private void backupIcons(BackupDataOutput data) throws IOException {
Chris Wrenfd13c712013-09-27 15:45:19 -0400410 // persist icons that haven't been persisted yet
Chris Wren6d0dde02014-02-10 12:16:54 -0500411 if (!initializeIconCache()) {
Chris Wren92aa4232013-10-04 11:29:36 -0400412 dataChanged(); // try again later
Chris Wrend8fe6de2013-10-04 10:42:14 -0400413 if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying icon backup");
414 return;
415 }
Chris Wren92aa4232013-10-04 11:29:36 -0400416 final ContentResolver cr = mContext.getContentResolver();
Chris Wren92aa4232013-10-04 11:29:36 -0400417 final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
Sunny Goyalffe83f12014-08-14 17:39:34 -0700418 final UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
Sunny Goyalef728d42014-10-22 11:28:28 -0700419 int backupUpIconCount = 0;
Chris Wren22e130d2013-09-23 18:25:57 -0400420
Kenny Guyed131872014-04-30 03:02:21 +0100421 // Don't backup apps in other profiles for now.
Kenny Guyed131872014-04-30 03:02:21 +0100422 String where = "(" + Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION + " OR " +
Kenny Guy43ea7ac2014-05-09 16:44:18 +0100423 Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT + ") AND " +
Sunny Goyalffe83f12014-08-14 17:39:34 -0700424 getUserSelectionArg();
Chris Wren22e130d2013-09-23 18:25:57 -0400425 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
426 where, null, null);
Chris Wren22e130d2013-09-23 18:25:57 -0400427 try {
428 cursor.moveToPosition(-1);
429 while(cursor.moveToNext()) {
430 final long id = cursor.getLong(ID_INDEX);
431 final String intentDescription = cursor.getString(INTENT_INDEX);
432 try {
433 Intent intent = Intent.parseUri(intentDescription, 0);
434 ComponentName cn = intent.getComponent();
435 Key key = null;
436 String backupKey = null;
437 if (cn != null) {
438 key = getKey(Key.ICON, cn.flattenToShortString());
439 backupKey = keyToBackupKey(key);
Chris Wren22e130d2013-09-23 18:25:57 -0400440 } else {
441 Log.w(TAG, "empty intent on application favorite: " + id);
442 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700443 if (mExistingKeys.contains(backupKey)) {
444 if (DEBUG) Log.d(TAG, "already saved icon " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -0400445
446 // remember that we already backed this up previously
Sunny Goyalef728d42014-10-22 11:28:28 -0700447 mKeys.add(key);
Chris Wren22e130d2013-09-23 18:25:57 -0400448 } else if (backupKey != null) {
Sunny Goyalef728d42014-10-22 11:28:28 -0700449 if (DEBUG) Log.d(TAG, "I can count this high: " + backupUpIconCount);
450 if (backupUpIconCount < MAX_ICONS_PER_PASS) {
451 if (DEBUG) Log.d(TAG, "saving icon " + backupKey);
Kenny Guyed131872014-04-30 03:02:21 +0100452 Bitmap icon = mIconCache.getIcon(intent, myUserHandle);
Kenny Guyed131872014-04-30 03:02:21 +0100453 if (icon != null && !mIconCache.isDefaultIcon(icon, myUserHandle)) {
Sunny Goyalef728d42014-10-22 11:28:28 -0700454 writeRowToBackup(key, packIcon(dpi, icon), data);
455 mKeys.add(key);
456 backupUpIconCount ++;
Chris Wren22e130d2013-09-23 18:25:57 -0400457 }
458 } else {
Sunny Goyalef728d42014-10-22 11:28:28 -0700459 if (VERBOSE) Log.v(TAG, "deferring icon backup " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -0400460 // too many icons for this pass, request another.
Chris Wren92aa4232013-10-04 11:29:36 -0400461 dataChanged();
Chris Wren22e130d2013-09-23 18:25:57 -0400462 }
463 }
464 } catch (URISyntaxException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500465 Log.e(TAG, "invalid URI on application favorite: " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400466 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500467 Log.e(TAG, "unable to save application icon for favorite: " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400468 }
469
470 }
471 } finally {
472 cursor.close();
473 }
Chris Wren22e130d2013-09-23 18:25:57 -0400474 }
475
476 /**
477 * Read an icon from the stream.
478 *
Chris Wrenfd13c712013-09-27 15:45:19 -0400479 * <P>Keys arrive in any order, so shortcuts that use this icon may already exist.
Chris Wren22e130d2013-09-23 18:25:57 -0400480 *
481 * @param key identifier for the row
482 * @param buffer the serialized proto from the stream, may be larger than dataSize
483 * @param dataSize the size of the proto from the stream
Chris Wren22e130d2013-09-23 18:25:57 -0400484 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700485 private void restoreIcon(Key key, byte[] buffer, int dataSize) throws IOException {
Chris Wren50c8f422014-01-15 16:10:39 -0500486 if (VERBOSE) Log.v(TAG, "unpacking icon " + key.id);
Chris Wren22e130d2013-09-23 18:25:57 -0400487 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
488 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
Chris Wren6d0dde02014-02-10 12:16:54 -0500489
Sunny Goyalef728d42014-10-22 11:28:28 -0700490 Resource res = unpackProto(new Resource(), buffer, dataSize);
491 if (DEBUG) {
492 Log.d(TAG, "unpacked " + res.dpi + " dpi icon");
Chris Wren22e130d2013-09-23 18:25:57 -0400493 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700494 Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
495 if (icon == null) {
496 Log.w(TAG, "failed to unpack icon for " + key.name);
497 }
498 if (VERBOSE) Log.v(TAG, "saving restored icon as: " + key.name);
499 IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(key.name), icon, res.dpi);
Chris Wren22e130d2013-09-23 18:25:57 -0400500 }
501
Chris Wrenfd13c712013-09-27 15:45:19 -0400502 /**
503 * Write all the static widget resources we need to render placeholders
504 * for a package that is not installed.
505 *
Chris Wrenfd13c712013-09-27 15:45:19 -0400506 * @param data output stream for key/value pairs
Chris Wrenfd13c712013-09-27 15:45:19 -0400507 * @throws IOException
508 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700509 private void backupWidgets(BackupDataOutput data) throws IOException {
Chris Wrenfd13c712013-09-27 15:45:19 -0400510 // persist static widget info that hasn't been persisted yet
Chris Wrend8fe6de2013-10-04 10:42:14 -0400511 final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
Chris Wren6d0dde02014-02-10 12:16:54 -0500512 if (appState == null || !initializeIconCache()) {
513 Log.w(TAG, "Failed to get icon cache during restore");
Chris Wrend8fe6de2013-10-04 10:42:14 -0400514 return;
515 }
Chris Wren92aa4232013-10-04 11:29:36 -0400516 final ContentResolver cr = mContext.getContentResolver();
517 final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext);
518 final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(mContext);
Chris Wren92aa4232013-10-04 11:29:36 -0400519 final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
Chris Wrenfd13c712013-09-27 15:45:19 -0400520 final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
521 if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
Sunny Goyalef728d42014-10-22 11:28:28 -0700522 int backupWidgetCount = 0;
Chris Wrenfd13c712013-09-27 15:45:19 -0400523
Sunny Goyalffe83f12014-08-14 17:39:34 -0700524 String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPWIDGET + " AND "
525 + getUserSelectionArg();
Chris Wrenfd13c712013-09-27 15:45:19 -0400526 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
527 where, null, null);
Chris Wrenfd13c712013-09-27 15:45:19 -0400528 try {
529 cursor.moveToPosition(-1);
530 while(cursor.moveToNext()) {
531 final long id = cursor.getLong(ID_INDEX);
532 final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX);
533 final int spanX = cursor.getInt(SPANX_INDEX);
534 final int spanY = cursor.getInt(SPANY_INDEX);
535 final ComponentName provider = ComponentName.unflattenFromString(providerName);
536 Key key = null;
537 String backupKey = null;
538 if (provider != null) {
539 key = getKey(Key.WIDGET, providerName);
540 backupKey = keyToBackupKey(key);
Chris Wrenfd13c712013-09-27 15:45:19 -0400541 } else {
542 Log.w(TAG, "empty intent on appwidget: " + id);
543 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700544 if (mExistingKeys.contains(backupKey)) {
545 if (DEBUG) Log.d(TAG, "already saved widget " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400546
547 // remember that we already backed this up previously
Sunny Goyalef728d42014-10-22 11:28:28 -0700548 mKeys.add(key);
Chris Wrenfd13c712013-09-27 15:45:19 -0400549 } else if (backupKey != null) {
Sunny Goyalef728d42014-10-22 11:28:28 -0700550 if (DEBUG) Log.d(TAG, "I can count this high: " + backupWidgetCount);
551 if (backupWidgetCount < MAX_WIDGETS_PER_PASS) {
552 if (DEBUG) Log.d(TAG, "saving widget " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400553 previewLoader.setPreviewSize(spanX * profile.cellWidthPx,
554 spanY * profile.cellHeightPx, widgetSpacingLayout);
Sunny Goyalef728d42014-10-22 11:28:28 -0700555 writeRowToBackup(key, packWidget(dpi, previewLoader, mIconCache, provider), data);
556 mKeys.add(key);
557 backupWidgetCount ++;
Chris Wrenfd13c712013-09-27 15:45:19 -0400558 } else {
Sunny Goyalef728d42014-10-22 11:28:28 -0700559 if (VERBOSE) Log.v(TAG, "deferring widget backup " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400560 // too many widgets for this pass, request another.
Chris Wren92aa4232013-10-04 11:29:36 -0400561 dataChanged();
Chris Wrenfd13c712013-09-27 15:45:19 -0400562 }
563 }
564 }
565 } finally {
566 cursor.close();
567 }
Chris Wrenfd13c712013-09-27 15:45:19 -0400568 }
569
570 /**
571 * Read a widget from the stream.
572 *
573 * <P>Keys arrive in any order, so widgets that use this data may already exist.
574 *
575 * @param key identifier for the row
576 * @param buffer the serialized proto from the stream, may be larger than dataSize
577 * @param dataSize the size of the proto from the stream
Chris Wrenfd13c712013-09-27 15:45:19 -0400578 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700579 private void restoreWidget(Key key, byte[] buffer, int dataSize) throws IOException {
Chris Wren50c8f422014-01-15 16:10:39 -0500580 if (VERBOSE) Log.v(TAG, "unpacking widget " + key.id);
Chris Wrenfd13c712013-09-27 15:45:19 -0400581 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
582 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
Sunny Goyalef728d42014-10-22 11:28:28 -0700583 Widget widget = unpackProto(new Widget(), buffer, dataSize);
584 if (DEBUG) Log.d(TAG, "unpacked " + widget.provider);
585 if (widget.icon.data != null) {
586 Bitmap icon = BitmapFactory
587 .decodeByteArray(widget.icon.data, 0, widget.icon.data.length);
588 if (icon == null) {
589 Log.w(TAG, "failed to unpack widget icon for " + key.name);
Chris Wren5dee7af2013-12-20 17:22:11 -0500590 } else {
Sunny Goyalef728d42014-10-22 11:28:28 -0700591 IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(widget.provider),
592 icon, widget.icon.dpi);
Chris Wren5dee7af2013-12-20 17:22:11 -0500593 }
Chris Wrenfd13c712013-09-27 15:45:19 -0400594 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700595
596 // future site of widget table mutation
Chris Wrenfd13c712013-09-27 15:45:19 -0400597 }
598
Chris Wren22e130d2013-09-23 18:25:57 -0400599 /** create a new key, with an integer ID.
Chris Wren1ada10d2013-09-13 18:01:38 -0400600 *
601 * <P> Keys contain their own checksum instead of using
602 * the heavy-weight CheckedMessage wrapper.
603 */
604 private Key getKey(int type, long id) {
605 Key key = new Key();
606 key.type = type;
607 key.id = id;
608 key.checksum = checkKey(key);
609 return key;
610 }
611
Chris Wren22e130d2013-09-23 18:25:57 -0400612 /** create a new key for a named object.
613 *
614 * <P> Keys contain their own checksum instead of using
615 * the heavy-weight CheckedMessage wrapper.
616 */
617 private Key getKey(int type, String name) {
618 Key key = new Key();
619 key.type = type;
620 key.name = name;
621 key.checksum = checkKey(key);
622 return key;
623 }
624
Chris Wren1ada10d2013-09-13 18:01:38 -0400625 /** keys need to be strings, serialize and encode. */
626 private String keyToBackupKey(Key key) {
Chris Wren978194c2013-10-03 17:47:22 -0400627 return Base64.encodeToString(Key.toByteArray(key), Base64.NO_WRAP);
Chris Wren1ada10d2013-09-13 18:01:38 -0400628 }
629
630 /** keys need to be strings, decode and parse. */
631 private Key backupKeyToKey(String backupKey) throws KeyParsingException {
632 try {
633 Key key = Key.parseFrom(Base64.decode(backupKey, Base64.DEFAULT));
634 if (key.checksum != checkKey(key)) {
635 key = null;
636 throw new KeyParsingException("invalid key read from stream" + backupKey);
637 }
638 return key;
639 } catch (InvalidProtocolBufferNanoException e) {
640 throw new KeyParsingException(e);
641 } catch (IllegalArgumentException e) {
642 throw new KeyParsingException(e);
643 }
644 }
645
646 /** Compute the checksum over the important bits of a key. */
647 private long checkKey(Key key) {
648 CRC32 checksum = new CRC32();
649 checksum.update(key.type);
650 checksum.update((int) (key.id & 0xffff));
651 checksum.update((int) ((key.id >> 32) & 0xffff));
652 if (!TextUtils.isEmpty(key.name)) {
653 checksum.update(key.name.getBytes());
654 }
655 return checksum.getValue();
656 }
657
658 /** Serialize a Favorite for persistence, including a checksum wrapper. */
Sunny Goyalef728d42014-10-22 11:28:28 -0700659 private Favorite packFavorite(Cursor c) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400660 Favorite favorite = new Favorite();
661 favorite.id = c.getLong(ID_INDEX);
662 favorite.screen = c.getInt(SCREEN_INDEX);
663 favorite.container = c.getInt(CONTAINER_INDEX);
664 favorite.cellX = c.getInt(CELLX_INDEX);
665 favorite.cellY = c.getInt(CELLY_INDEX);
666 favorite.spanX = c.getInt(SPANX_INDEX);
667 favorite.spanY = c.getInt(SPANY_INDEX);
668 favorite.iconType = c.getInt(ICON_TYPE_INDEX);
669 if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
670 String iconPackage = c.getString(ICON_PACKAGE_INDEX);
671 if (!TextUtils.isEmpty(iconPackage)) {
672 favorite.iconPackage = iconPackage;
673 }
674 String iconResource = c.getString(ICON_RESOURCE_INDEX);
675 if (!TextUtils.isEmpty(iconResource)) {
676 favorite.iconResource = iconResource;
677 }
678 }
679 if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
680 byte[] blob = c.getBlob(ICON_INDEX);
681 if (blob != null && blob.length > 0) {
682 favorite.icon = blob;
683 }
684 }
685 String title = c.getString(TITLE_INDEX);
686 if (!TextUtils.isEmpty(title)) {
687 favorite.title = title;
688 }
Kenny Guyf8b1dfd2014-05-13 12:59:34 +0100689 String intentDescription = c.getString(INTENT_INDEX);
690 if (!TextUtils.isEmpty(intentDescription)) {
691 try {
692 Intent intent = Intent.parseUri(intentDescription, 0);
693 intent.removeExtra(ItemInfo.EXTRA_PROFILE);
694 favorite.intent = intent.toUri(0);
695 } catch (URISyntaxException e) {
696 Log.e(TAG, "Invalid intent", e);
Sunny Goyalef728d42014-10-22 11:28:28 -0700697 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400698 }
699 favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
700 if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
701 favorite.appWidgetId = c.getInt(APPWIDGET_ID_INDEX);
702 String appWidgetProvider = c.getString(APPWIDGET_PROVIDER_INDEX);
703 if (!TextUtils.isEmpty(appWidgetProvider)) {
704 favorite.appWidgetProvider = appWidgetProvider;
705 }
706 }
707
Sunny Goyalef728d42014-10-22 11:28:28 -0700708 return favorite;
Chris Wren1ada10d2013-09-13 18:01:38 -0400709 }
710
711 /** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
Sunny Goyalef728d42014-10-22 11:28:28 -0700712 private ContentValues unpackFavorite(byte[] buffer, int dataSize)
Chris Wren1ada10d2013-09-13 18:01:38 -0400713 throws InvalidProtocolBufferNanoException {
Sunny Goyalef728d42014-10-22 11:28:28 -0700714 Favorite favorite = unpackProto(new Favorite(), buffer, dataSize);
Chris Wren5dee7af2013-12-20 17:22:11 -0500715 ContentValues values = new ContentValues();
716 values.put(Favorites._ID, favorite.id);
717 values.put(Favorites.SCREEN, favorite.screen);
718 values.put(Favorites.CONTAINER, favorite.container);
719 values.put(Favorites.CELLX, favorite.cellX);
720 values.put(Favorites.CELLY, favorite.cellY);
721 values.put(Favorites.SPANX, favorite.spanX);
722 values.put(Favorites.SPANY, favorite.spanY);
723 values.put(Favorites.ICON_TYPE, favorite.iconType);
724 if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
725 values.put(Favorites.ICON_PACKAGE, favorite.iconPackage);
726 values.put(Favorites.ICON_RESOURCE, favorite.iconResource);
727 }
728 if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
729 values.put(Favorites.ICON, favorite.icon);
730 }
731 if (!TextUtils.isEmpty(favorite.title)) {
732 values.put(Favorites.TITLE, favorite.title);
733 } else {
734 values.put(Favorites.TITLE, "");
735 }
736 if (!TextUtils.isEmpty(favorite.intent)) {
737 values.put(Favorites.INTENT, favorite.intent);
738 }
739 values.put(Favorites.ITEM_TYPE, favorite.itemType);
Chris Wrenf4d08112014-01-16 18:13:56 -0500740
Kenny Guyf8b1dfd2014-05-13 12:59:34 +0100741 UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
742 long userSerialNumber =
743 UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
744 values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
745
Sunny Goyalff572272014-07-23 13:58:07 -0700746 if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
747 if (!TextUtils.isEmpty(favorite.appWidgetProvider)) {
748 values.put(Favorites.APPWIDGET_PROVIDER, favorite.appWidgetProvider);
749 }
750 values.put(Favorites.APPWIDGET_ID, favorite.appWidgetId);
751 values.put(LauncherSettings.Favorites.RESTORED,
752 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
753 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY |
754 LauncherAppWidgetInfo.FLAG_UI_NOT_READY);
755 } else {
756 // Let LauncherModel know we've been here.
757 values.put(LauncherSettings.Favorites.RESTORED, 1);
758 }
Chris Wrenf4d08112014-01-16 18:13:56 -0500759
Chris Wren5dee7af2013-12-20 17:22:11 -0500760 return values;
Chris Wren1ada10d2013-09-13 18:01:38 -0400761 }
762
763 /** Serialize a Screen for persistence, including a checksum wrapper. */
Sunny Goyalef728d42014-10-22 11:28:28 -0700764 private Screen packScreen(Cursor c) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400765 Screen screen = new Screen();
766 screen.id = c.getLong(ID_INDEX);
767 screen.rank = c.getInt(SCREEN_RANK_INDEX);
Sunny Goyalef728d42014-10-22 11:28:28 -0700768 return screen;
Chris Wren1ada10d2013-09-13 18:01:38 -0400769 }
770
771 /** Deserialize a Screen from persistence, after verifying checksum wrapper. */
Sunny Goyalef728d42014-10-22 11:28:28 -0700772 private ContentValues unpackScreen(byte[] buffer, int dataSize)
Chris Wren1ada10d2013-09-13 18:01:38 -0400773 throws InvalidProtocolBufferNanoException {
Sunny Goyalef728d42014-10-22 11:28:28 -0700774 Screen screen = unpackProto(new Screen(), buffer, dataSize);
Chris Wren5dee7af2013-12-20 17:22:11 -0500775 ContentValues values = new ContentValues();
776 values.put(WorkspaceScreens._ID, screen.id);
777 values.put(WorkspaceScreens.SCREEN_RANK, screen.rank);
778 return values;
Chris Wren1ada10d2013-09-13 18:01:38 -0400779 }
780
Chris Wren22e130d2013-09-23 18:25:57 -0400781 /** Serialize an icon Resource for persistence, including a checksum wrapper. */
Sunny Goyalef728d42014-10-22 11:28:28 -0700782 private Resource packIcon(int dpi, Bitmap icon) {
Chris Wren22e130d2013-09-23 18:25:57 -0400783 Resource res = new Resource();
784 res.dpi = dpi;
785 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400786 if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wren22e130d2013-09-23 18:25:57 -0400787 res.data = os.toByteArray();
788 }
Chris Wren22e130d2013-09-23 18:25:57 -0400789 return res;
790 }
791
Chris Wrenfd13c712013-09-27 15:45:19 -0400792 /** Serialize a widget for persistence, including a checksum wrapper. */
Sunny Goyalef728d42014-10-22 11:28:28 -0700793 private Widget packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache,
Chris Wrenfd13c712013-09-27 15:45:19 -0400794 ComponentName provider) {
795 final AppWidgetProviderInfo info = findAppWidgetProviderInfo(provider);
796 Widget widget = new Widget();
797 widget.provider = provider.flattenToShortString();
798 widget.label = info.label;
799 widget.configure = info.configure != null;
800 if (info.icon != 0) {
801 widget.icon = new Resource();
802 Drawable fullResIcon = iconCache.getFullResIcon(provider.getPackageName(), info.icon);
Chris Wren92aa4232013-10-04 11:29:36 -0400803 Bitmap icon = Utilities.createIconBitmap(fullResIcon, mContext);
Chris Wrenfd13c712013-09-27 15:45:19 -0400804 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400805 if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wrenfd13c712013-09-27 15:45:19 -0400806 widget.icon.data = os.toByteArray();
807 widget.icon.dpi = dpi;
808 }
809 }
810 if (info.previewImage != 0) {
811 widget.preview = new Resource();
812 Bitmap preview = previewLoader.generateWidgetPreview(info, null);
813 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400814 if (preview.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wrenfd13c712013-09-27 15:45:19 -0400815 widget.preview.data = os.toByteArray();
816 widget.preview.dpi = dpi;
817 }
818 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700819 return widget;
Chris Wrenfd13c712013-09-27 15:45:19 -0400820 }
821
Sunny Goyalef728d42014-10-22 11:28:28 -0700822 /**
823 * Deserialize a proto after verifying checksum wrapper.
824 */
825 private <T extends MessageNano> T unpackProto(T proto, byte[] buffer, int dataSize)
Chris Wrenfd13c712013-09-27 15:45:19 -0400826 throws InvalidProtocolBufferNanoException {
Sunny Goyalef728d42014-10-22 11:28:28 -0700827 MessageNano.mergeFrom(proto, readCheckedBytes(buffer, dataSize));
828 if (DEBUG) Log.d(TAG, "unpacked proto " + proto);
829 return proto;
Chris Wrenfd13c712013-09-27 15:45:19 -0400830 }
831
Chris Wren1ada10d2013-09-13 18:01:38 -0400832 /**
833 * Read the old journal from the input file.
834 *
835 * In the event of any error, just pretend we didn't have a journal,
836 * in that case, do a full backup.
837 *
838 * @param oldState the read-0only file descriptor pointing to the old journal
Chris Wren65b6a602014-01-10 14:11:25 -0500839 * @return a Journal protocol buffer
Chris Wren1ada10d2013-09-13 18:01:38 -0400840 */
841 private Journal readJournal(ParcelFileDescriptor oldState) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400842 Journal journal = new Journal();
Chris Wren92aa4232013-10-04 11:29:36 -0400843 if (oldState == null) {
844 return journal;
845 }
846 FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor());
847 try {
Chris Wren65b6a602014-01-10 14:11:25 -0500848 int availableBytes = inStream.available();
849 if (DEBUG) Log.d(TAG, "available " + availableBytes);
850 if (availableBytes < MAX_JOURNAL_SIZE) {
851 byte[] buffer = new byte[availableBytes];
Chris Wren1ada10d2013-09-13 18:01:38 -0400852 int bytesRead = 0;
Chris Wren65b6a602014-01-10 14:11:25 -0500853 boolean valid = false;
Chris Wren50c8f422014-01-15 16:10:39 -0500854 InvalidProtocolBufferNanoException lastProtoException = null;
Chris Wren65b6a602014-01-10 14:11:25 -0500855 while (availableBytes > 0) {
Chris Wren92aa4232013-10-04 11:29:36 -0400856 try {
Chris Wren65b6a602014-01-10 14:11:25 -0500857 // OMG what are you doing? This is crazy inefficient!
858 // If we read a byte that is not ours, we will cause trouble: b/12491813
859 // However, we don't know how many bytes to expect (oops).
860 // So we have to step through *slowly*, watching for the end.
861 int result = inStream.read(buffer, bytesRead, 1);
Chris Wren92aa4232013-10-04 11:29:36 -0400862 if (result > 0) {
Chris Wren65b6a602014-01-10 14:11:25 -0500863 availableBytes -= result;
Chris Wren92aa4232013-10-04 11:29:36 -0400864 bytesRead += result;
865 } else {
Chris Wren65b6a602014-01-10 14:11:25 -0500866 Log.w(TAG, "unexpected end of file while reading journal.");
867 // stop reading and see what there is to parse
868 availableBytes = 0;
Chris Wren92aa4232013-10-04 11:29:36 -0400869 }
870 } catch (IOException e) {
Chris Wren92aa4232013-10-04 11:29:36 -0400871 buffer = null;
Chris Wren65b6a602014-01-10 14:11:25 -0500872 availableBytes = 0;
873 }
874
875 // check the buffer to see if we have a valid journal
876 try {
Sunny Goyalef728d42014-10-22 11:28:28 -0700877 MessageNano.mergeFrom(journal, readCheckedBytes(buffer, bytesRead));
Chris Wren65b6a602014-01-10 14:11:25 -0500878 // if we are here, then we have read a valid, checksum-verified journal
879 valid = true;
880 availableBytes = 0;
Chris Wren50c8f422014-01-15 16:10:39 -0500881 if (VERBOSE) Log.v(TAG, "read " + bytesRead + " bytes of journal");
Chris Wren65b6a602014-01-10 14:11:25 -0500882 } catch (InvalidProtocolBufferNanoException e) {
883 // if we don't have the whole journal yet, mergeFrom will throw. keep going.
Chris Wren50c8f422014-01-15 16:10:39 -0500884 lastProtoException = e;
Chris Wren65b6a602014-01-10 14:11:25 -0500885 journal.clear();
Chris Wren92aa4232013-10-04 11:29:36 -0400886 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400887 }
Chris Wren92aa4232013-10-04 11:29:36 -0400888 if (DEBUG) Log.d(TAG, "journal bytes read: " + bytesRead);
Chris Wren65b6a602014-01-10 14:11:25 -0500889 if (!valid) {
Chris Wren50c8f422014-01-15 16:10:39 -0500890 Log.w(TAG, "could not find a valid journal", lastProtoException);
Chris Wren1ada10d2013-09-13 18:01:38 -0400891 }
892 }
Chris Wren92aa4232013-10-04 11:29:36 -0400893 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500894 Log.w(TAG, "failed to close the journal", e);
Chris Wren92aa4232013-10-04 11:29:36 -0400895 } finally {
Chris Wren1ada10d2013-09-13 18:01:38 -0400896 try {
897 inStream.close();
898 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500899 Log.w(TAG, "failed to close the journal", e);
Chris Wren1ada10d2013-09-13 18:01:38 -0400900 }
901 }
902 return journal;
903 }
904
Sunny Goyalef728d42014-10-22 11:28:28 -0700905
906 private void writeRowToBackup(Key key, MessageNano proto, BackupDataOutput data)
907 throws IOException {
908 writeRowToBackup(keyToBackupKey(key), proto, data);
909 }
910
911 private void writeRowToBackup(String backupKey, MessageNano proto,
Chris Wren22e130d2013-09-23 18:25:57 -0400912 BackupDataOutput data) throws IOException {
Sunny Goyalef728d42014-10-22 11:28:28 -0700913 byte[] blob = writeCheckedBytes(proto);
Chris Wren22e130d2013-09-23 18:25:57 -0400914 data.writeEntityHeader(backupKey, blob.length);
915 data.writeEntityData(blob, blob.length);
Sunny Goyalef728d42014-10-22 11:28:28 -0700916 if (VERBOSE) Log.v(TAG, "Writing New entry " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -0400917 }
918
Chris Wren1ada10d2013-09-13 18:01:38 -0400919 /**
920 * Write the new journal to the output file.
921 *
922 * In the event of any error, just pretend we didn't have a journal,
923 * in that case, do a full backup.
924
925 * @param newState the write-only file descriptor pointing to the new journal
926 * @param journal a Journal protocol buffer
927 */
928 private void writeJournal(ParcelFileDescriptor newState, Journal journal) {
929 FileOutputStream outStream = null;
930 try {
931 outStream = new FileOutputStream(newState.getFileDescriptor());
Chris Wren65b6a602014-01-10 14:11:25 -0500932 final byte[] journalBytes = writeCheckedBytes(journal);
Chris Wren65b6a602014-01-10 14:11:25 -0500933 outStream.write(journalBytes);
Chris Wren1ada10d2013-09-13 18:01:38 -0400934 outStream.close();
Chris Wren50c8f422014-01-15 16:10:39 -0500935 if (VERBOSE) Log.v(TAG, "wrote " + journalBytes.length + " bytes of journal");
Chris Wren1ada10d2013-09-13 18:01:38 -0400936 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500937 Log.w(TAG, "failed to write backup journal", e);
Chris Wren1ada10d2013-09-13 18:01:38 -0400938 }
939 }
940
941 /** Wrap a proto in a CheckedMessage and compute the checksum. */
942 private byte[] writeCheckedBytes(MessageNano proto) {
943 CheckedMessage wrapper = new CheckedMessage();
944 wrapper.payload = MessageNano.toByteArray(proto);
945 CRC32 checksum = new CRC32();
946 checksum.update(wrapper.payload);
947 wrapper.checksum = checksum.getValue();
948 return MessageNano.toByteArray(wrapper);
949 }
950
951 /** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
Sunny Goyalef728d42014-10-22 11:28:28 -0700952 private static byte[] readCheckedBytes(byte[] buffer, int dataSize)
Chris Wren1ada10d2013-09-13 18:01:38 -0400953 throws InvalidProtocolBufferNanoException {
954 CheckedMessage wrapper = new CheckedMessage();
Sunny Goyalef728d42014-10-22 11:28:28 -0700955 MessageNano.mergeFrom(wrapper, buffer, 0, dataSize);
Chris Wren1ada10d2013-09-13 18:01:38 -0400956 CRC32 checksum = new CRC32();
957 checksum.update(wrapper.payload);
958 if (wrapper.checksum != checksum.getValue()) {
959 throw new InvalidProtocolBufferNanoException("checksum does not match");
960 }
961 return wrapper.payload;
962 }
963
Chris Wrenfd13c712013-09-27 15:45:19 -0400964 private AppWidgetProviderInfo findAppWidgetProviderInfo(ComponentName component) {
965 if (mWidgetMap == null) {
966 List<AppWidgetProviderInfo> widgets =
Chris Wren92aa4232013-10-04 11:29:36 -0400967 AppWidgetManager.getInstance(mContext).getInstalledProviders();
Chris Wrenfd13c712013-09-27 15:45:19 -0400968 mWidgetMap = new HashMap<ComponentName, AppWidgetProviderInfo>(widgets.size());
969 for (AppWidgetProviderInfo info : widgets) {
970 mWidgetMap.put(info.provider, info);
971 }
972 }
973 return mWidgetMap.get(component);
974 }
975
Chris Wren6d0dde02014-02-10 12:16:54 -0500976
977 private boolean initializeIconCache() {
978 if (mIconCache != null) {
979 return true;
980 }
981
982 final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
983 if (appState == null) {
984 Throwable stackTrace = new Throwable();
985 stackTrace.fillInStackTrace();
986 Log.w(TAG, "Failed to get app state during backup/restore", stackTrace);
987 return false;
988 }
989 mIconCache = appState.getIconCache();
990 return mIconCache != null;
991 }
992
Chris Wren71144262014-02-27 15:49:39 -0500993
Sunny Goyalef728d42014-10-22 11:28:28 -0700994 /**
995 * @return true if the launcher is in a state to support backup
996 */
Chris Wren71144262014-02-27 15:49:39 -0500997 private boolean launcherIsReady() {
998 ContentResolver cr = mContext.getContentResolver();
999 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION, null, null, null);
1000 if (cursor == null) {
1001 // launcher data has been wiped, do nothing
1002 return false;
1003 }
1004 cursor.close();
1005
1006 if (!initializeIconCache()) {
1007 // launcher services are unavailable, try again later
1008 dataChanged();
1009 return false;
1010 }
1011
1012 return true;
1013 }
1014
Sunny Goyalffe83f12014-08-14 17:39:34 -07001015 private String getUserSelectionArg() {
1016 return Favorites.PROFILE_ID + '=' + UserManagerCompat.getInstance(mContext)
1017 .getSerialNumberForUser(UserHandleCompat.myUserHandle());
1018 }
1019
Sunny Goyalef728d42014-10-22 11:28:28 -07001020 private class KeyParsingException extends IOException {
Chris Wren1ada10d2013-09-13 18:01:38 -04001021 private KeyParsingException(Throwable cause) {
1022 super(cause);
1023 }
1024
1025 public KeyParsingException(String reason) {
1026 super(reason);
1027 }
1028 }
1029}