blob: 9b901eea19f5f4bcabcc4615aafbb5696acbc694 [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 */
16
17package com.android.launcher3;
18
19import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
20import com.google.protobuf.nano.MessageNano;
21
Chris Wren1ada10d2013-09-13 18:01:38 -040022import com.android.launcher3.LauncherSettings.Favorites;
23import com.android.launcher3.LauncherSettings.WorkspaceScreens;
24import com.android.launcher3.backup.BackupProtos;
25import com.android.launcher3.backup.BackupProtos.CheckedMessage;
26import com.android.launcher3.backup.BackupProtos.Favorite;
27import com.android.launcher3.backup.BackupProtos.Journal;
28import com.android.launcher3.backup.BackupProtos.Key;
Chris Wren22e130d2013-09-23 18:25:57 -040029import com.android.launcher3.backup.BackupProtos.Resource;
Chris Wren1ada10d2013-09-13 18:01:38 -040030import com.android.launcher3.backup.BackupProtos.Screen;
Chris Wrenfd13c712013-09-27 15:45:19 -040031import com.android.launcher3.backup.BackupProtos.Widget;
Chris Wren1ada10d2013-09-13 18:01:38 -040032
Chris Wren92aa4232013-10-04 11:29:36 -040033import android.app.backup.BackupDataInputStream;
34import android.app.backup.BackupHelper;
Chris Wren1ada10d2013-09-13 18:01:38 -040035import android.app.backup.BackupDataInput;
36import android.app.backup.BackupDataOutput;
37import 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;
42import android.content.Context;
Chris Wren22e130d2013-09-23 18:25:57 -040043import android.content.Intent;
Chris Wren1ada10d2013-09-13 18:01:38 -040044import android.database.Cursor;
Chris Wren22e130d2013-09-23 18:25:57 -040045import android.graphics.Bitmap;
46import android.graphics.BitmapFactory;
Chris Wrenfd13c712013-09-27 15:45:19 -040047import android.graphics.drawable.Drawable;
Chris Wren1ada10d2013-09-13 18:01:38 -040048import android.os.ParcelFileDescriptor;
Chris Wren1ada10d2013-09-13 18:01:38 -040049import android.text.TextUtils;
50import android.util.Base64;
51import android.util.Log;
52
Chris Wren22e130d2013-09-23 18:25:57 -040053import java.io.ByteArrayOutputStream;
Chris Wren1ada10d2013-09-13 18:01:38 -040054import java.io.FileInputStream;
55import java.io.FileOutputStream;
56import java.io.IOException;
Chris Wren22e130d2013-09-23 18:25:57 -040057import java.net.URISyntaxException;
Chris Wren1ada10d2013-09-13 18:01:38 -040058import java.util.ArrayList;
Chris Wren22e130d2013-09-23 18:25:57 -040059import java.util.HashMap;
Chris Wren1ada10d2013-09-13 18:01:38 -040060import java.util.HashSet;
Chris Wren22e130d2013-09-23 18:25:57 -040061import java.util.List;
Chris Wren1ada10d2013-09-13 18:01:38 -040062import java.util.Set;
63import 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 Wrenfd13c712013-09-27 15:45:19 -040071 private static final boolean DEBUG = false;
Chris Wren92aa4232013-10-04 11:29:36 -040072 private static final boolean DEBUG_PAYLOAD = false;
Chris Wren1ada10d2013-09-13 18:01:38 -040073
74 private static final int MAX_JOURNAL_SIZE = 1000000;
75
Chris Wrenfd13c712013-09-27 15:45:19 -040076 /** icons are large, dribble them out */
Chris Wren22e130d2013-09-23 18:25:57 -040077 private static final int MAX_ICONS_PER_PASS = 10;
78
Chris Wrenfd13c712013-09-27 15:45:19 -040079 /** widgets contain previews, which are very large, dribble them out */
80 private static final int MAX_WIDGETS_PER_PASS = 5;
81
82 public static final int IMAGE_COMPRESSION_QUALITY = 75;
83
Chris Wren92aa4232013-10-04 11:29:36 -040084 public static final String LAUNCHER_PREFIX = "L";
85
Chris Wrenb86f0762013-10-04 10:10:21 -040086 private static final Bitmap.CompressFormat IMAGE_FORMAT =
87 android.graphics.Bitmap.CompressFormat.PNG;
88
Chris Wren1ada10d2013-09-13 18:01:38 -040089 private static BackupManager sBackupManager;
90
91 private static final String[] FAVORITE_PROJECTION = {
92 Favorites._ID, // 0
Chris Wren22e130d2013-09-23 18:25:57 -040093 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
Chris Wren1ada10d2013-09-13 18:01:38 -0400109 };
110
111 private static final int ID_INDEX = 0;
Chris Wren22e130d2013-09-23 18:25:57 -0400112 private static final int ID_MODIFIED = 1;
113 private static final int INTENT_INDEX = 2;
114 private static final int APPWIDGET_PROVIDER_INDEX = 3;
115 private static final int APPWIDGET_ID_INDEX = 4;
116 private static final int CELLX_INDEX = 5;
117 private static final int CELLY_INDEX = 6;
118 private static final int CONTAINER_INDEX = 7;
119 private static final int ICON_INDEX = 8;
120 private static final int ICON_PACKAGE_INDEX = 9;
121 private static final int ICON_RESOURCE_INDEX = 10;
122 private static final int ICON_TYPE_INDEX = 11;
123 private static final int ITEM_TYPE_INDEX = 12;
124 private static final int SCREEN_INDEX = 13;
125 private static final int SPANX_INDEX = 14;
126 private static final int SPANY_INDEX = 15;
127 private static final int TITLE_INDEX = 16;
Chris Wren1ada10d2013-09-13 18:01:38 -0400128
129 private static final String[] SCREEN_PROJECTION = {
130 WorkspaceScreens._ID, // 0
Chris Wren22e130d2013-09-23 18:25:57 -0400131 WorkspaceScreens.MODIFIED, // 1
132 WorkspaceScreens.SCREEN_RANK // 2
Chris Wren1ada10d2013-09-13 18:01:38 -0400133 };
134
Chris Wren22e130d2013-09-23 18:25:57 -0400135 private static final int SCREEN_RANK_INDEX = 2;
Chris Wren1ada10d2013-09-13 18:01:38 -0400136
Chris Wren92aa4232013-10-04 11:29:36 -0400137 private final Context mContext;
Chris Wren1ada10d2013-09-13 18:01:38 -0400138
Chris Wren22e130d2013-09-23 18:25:57 -0400139 private HashMap<ComponentName, AppWidgetProviderInfo> mWidgetMap;
140
Chris Wren92aa4232013-10-04 11:29:36 -0400141 private ArrayList<Key> mKeys;
Chris Wren1ada10d2013-09-13 18:01:38 -0400142
Chris Wren92aa4232013-10-04 11:29:36 -0400143 public LauncherBackupHelper(Context context) {
144 mContext = context;
145 }
146
147 private void dataChanged() {
Chris Wren1ada10d2013-09-13 18:01:38 -0400148 if (sBackupManager == null) {
Chris Wren92aa4232013-10-04 11:29:36 -0400149 sBackupManager = new BackupManager(mContext);
Chris Wren1ada10d2013-09-13 18:01:38 -0400150 }
151 sBackupManager.dataChanged();
152 }
153
154 /**
155 * Back up launcher data so we can restore the user's state on a new device.
156 *
157 * <P>The journal is a timestamp and a list of keys that were saved as of that time.
158 *
159 * <P>Keys may come back in any order, so each key/value is one complete row of the database.
160 *
161 * @param oldState notes from the last backup
162 * @param data incremental key/value pairs to persist off-device
163 * @param newState notes for the next backup
164 * @throws IOException
165 */
166 @Override
Chris Wren92aa4232013-10-04 11:29:36 -0400167 public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
168 ParcelFileDescriptor newState) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400169 Log.v(TAG, "onBackup");
170
171 Journal in = readJournal(oldState);
172 Journal out = new Journal();
173
174 long lastBackupTime = in.t;
175 out.t = System.currentTimeMillis();
176 out.rows = 0;
177 out.bytes = 0;
178
179 Log.v(TAG, "lastBackupTime=" + lastBackupTime);
180
181 ArrayList<Key> keys = new ArrayList<Key>();
Chris Wren92aa4232013-10-04 11:29:36 -0400182 try {
183 backupFavorites(in, data, out, keys);
184 backupScreens(in, data, out, keys);
185 backupIcons(in, data, out, keys);
186 backupWidgets(in, data, out, keys);
187 } catch (IOException e) {
188 Log.e(TAG, "launcher backup has failed", e);
189 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400190
191 out.key = keys.toArray(BackupProtos.Key.EMPTY_ARRAY);
192 writeJournal(newState, out);
193 Log.v(TAG, "onBackup: wrote " + out.bytes + "b in " + out.rows + " rows.");
Chris Wren1ada10d2013-09-13 18:01:38 -0400194 }
195
196 /**
Chris Wren92aa4232013-10-04 11:29:36 -0400197 * Restore launcher configuration from the restored data stream.
Chris Wren1ada10d2013-09-13 18:01:38 -0400198 *
199 * <P>Keys may arrive in any order.
200 *
Chris Wren92aa4232013-10-04 11:29:36 -0400201 * @param data the key/value pair from the server
Chris Wren1ada10d2013-09-13 18:01:38 -0400202 */
203 @Override
Chris Wren92aa4232013-10-04 11:29:36 -0400204 public void restoreEntity(BackupDataInputStream data) {
205 Log.v(TAG, "restoreEntity");
206 if (mKeys == null) {
207 mKeys = new ArrayList<Key>();
208 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400209 byte[] buffer = new byte[512];
Chris Wren1ada10d2013-09-13 18:01:38 -0400210 String backupKey = data.getKey();
Chris Wren92aa4232013-10-04 11:29:36 -0400211 int dataSize = data.size();
Chris Wren1ada10d2013-09-13 18:01:38 -0400212 if (buffer.length < dataSize) {
213 buffer = new byte[dataSize];
214 }
215 Key key = null;
Chris Wren92aa4232013-10-04 11:29:36 -0400216 int bytesRead = 0;
217 try {
218 bytesRead = data.read(buffer, 0, dataSize);
219 if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
220 } catch (IOException e) {
221 Log.d(TAG, "failed to read entity from restore data", e);
222 }
223 try {
224 key = backupKeyToKey(backupKey);
225 switch (key.type) {
226 case Key.FAVORITE:
227 restoreFavorite(key, buffer, dataSize, mKeys);
228 break;
229
230 case Key.SCREEN:
231 restoreScreen(key, buffer, dataSize, mKeys);
232 break;
233
234 case Key.ICON:
235 restoreIcon(key, buffer, dataSize, mKeys);
236 break;
237
238 case Key.WIDGET:
239 restoreWidget(key, buffer, dataSize, mKeys);
240 break;
241
242 default:
243 Log.w(TAG, "unknown restore entity type: " + key.type);
244 break;
Chris Wren1ada10d2013-09-13 18:01:38 -0400245 }
Chris Wren92aa4232013-10-04 11:29:36 -0400246 } catch (KeyParsingException e) {
247 Log.w(TAG, "ignoring unparsable backup key: " + backupKey);
Chris Wren1ada10d2013-09-13 18:01:38 -0400248 }
249
Chris Wren92aa4232013-10-04 11:29:36 -0400250 }
251
252 /**
253 * Record the restore state for the next backup.
254 *
255 * @param newState notes about the backup state after restore.
256 */
257 @Override
258 public void writeNewStateDescription(ParcelFileDescriptor newState) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400259 // clear the output journal time, to force a full backup to
260 // will catch any changes the restore process might have made
Chris Wren92aa4232013-10-04 11:29:36 -0400261 Journal out = new Journal();
Chris Wren1ada10d2013-09-13 18:01:38 -0400262 out.t = 0;
Chris Wren92aa4232013-10-04 11:29:36 -0400263 out.key = mKeys.toArray(BackupProtos.Key.EMPTY_ARRAY);
Chris Wren1ada10d2013-09-13 18:01:38 -0400264 writeJournal(newState, out);
Chris Wren92aa4232013-10-04 11:29:36 -0400265 Log.v(TAG, "onRestore: read " + mKeys.size() + " rows");
266 mKeys.clear();
Chris Wren1ada10d2013-09-13 18:01:38 -0400267 }
268
269 /**
270 * Write all modified favorites to the data stream.
271 *
272 *
273 * @param in notes from last backup
274 * @param data output stream for key/value pairs
275 * @param out notes about this backup
276 * @param keys keys to mark as clean in the notes for next backup
277 * @throws IOException
278 */
279 private void backupFavorites(Journal in, BackupDataOutput data, Journal out,
280 ArrayList<Key> keys)
281 throws IOException {
282 // read the old ID set
Chris Wren22e130d2013-09-23 18:25:57 -0400283 Set<String> savedIds = getSavedIdsByType(Key.FAVORITE, in);
Chris Wren1ada10d2013-09-13 18:01:38 -0400284 if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
285
286 // persist things that have changed since the last backup
Chris Wren92aa4232013-10-04 11:29:36 -0400287 ContentResolver cr = mContext.getContentResolver();
Chris Wren22e130d2013-09-23 18:25:57 -0400288 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
289 null, null, null);
290 Set<String> currentIds = new HashSet<String>(cursor.getCount());
Chris Wren1ada10d2013-09-13 18:01:38 -0400291 try {
Chris Wren22e130d2013-09-23 18:25:57 -0400292 cursor.moveToPosition(-1);
293 while(cursor.moveToNext()) {
294 final long id = cursor.getLong(ID_INDEX);
295 final long updateTime = cursor.getLong(ID_MODIFIED);
Chris Wren1ada10d2013-09-13 18:01:38 -0400296 Key key = getKey(Key.FAVORITE, id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400297 keys.add(key);
Chris Wren22e130d2013-09-23 18:25:57 -0400298 currentIds.add(keyToBackupKey(key));
299 if (updateTime > in.t) {
300 byte[] blob = packFavorite(cursor);
301 writeRowToBackup(key, blob, out, data);
302 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400303 }
304 } finally {
Chris Wren22e130d2013-09-23 18:25:57 -0400305 cursor.close();
Chris Wren1ada10d2013-09-13 18:01:38 -0400306 }
307 if (DEBUG) Log.d(TAG, "favorite currentIds.size()=" + currentIds.size());
308
309 // these IDs must have been deleted
310 savedIds.removeAll(currentIds);
Chris Wren22e130d2013-09-23 18:25:57 -0400311 out.rows += removeDeletedKeysFromBackup(savedIds, data);
Chris Wren1ada10d2013-09-13 18:01:38 -0400312 }
313
314 /**
315 * Read a favorite from the stream.
316 *
317 * <P>Keys arrive in any order, so screens and containers may not exist yet.
318 *
319 * @param key identifier for the row
320 * @param buffer the serialized proto from the stream, may be larger than dataSize
321 * @param dataSize the size of the proto from the stream
322 * @param keys keys to mark as clean in the notes for next backup
323 */
324 private void restoreFavorite(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
325 Log.v(TAG, "unpacking favorite " + key.id + " (" + dataSize + " bytes)");
326 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
327 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
328
329 try {
330 Favorite favorite = unpackFavorite(buffer, 0, dataSize);
331 if (DEBUG) Log.d(TAG, "unpacked " + favorite.itemType);
332 } catch (InvalidProtocolBufferNanoException e) {
333 Log.w(TAG, "failed to decode proto", e);
334 }
335 }
336
337 /**
338 * Write all modified screens to the data stream.
339 *
340 *
341 * @param in notes from last backup
342 * @param data output stream for key/value pairs
343 * @param out notes about this backup
Chris Wren22e130d2013-09-23 18:25:57 -0400344 * @param keys keys to mark as clean in the notes for next backup
345 * @throws IOException
Chris Wren1ada10d2013-09-13 18:01:38 -0400346 */
347 private void backupScreens(Journal in, BackupDataOutput data, Journal out,
348 ArrayList<Key> keys)
349 throws IOException {
350 // read the old ID set
Chris Wren22e130d2013-09-23 18:25:57 -0400351 Set<String> savedIds = getSavedIdsByType(Key.SCREEN, in);
352 if (DEBUG) Log.d(TAG, "screen savedIds.size()=" + savedIds.size());
Chris Wren1ada10d2013-09-13 18:01:38 -0400353
354 // persist things that have changed since the last backup
Chris Wren92aa4232013-10-04 11:29:36 -0400355 ContentResolver cr = mContext.getContentResolver();
Chris Wren22e130d2013-09-23 18:25:57 -0400356 Cursor cursor = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
357 null, null, null);
358 Set<String> currentIds = new HashSet<String>(cursor.getCount());
Chris Wren1ada10d2013-09-13 18:01:38 -0400359 try {
Chris Wren22e130d2013-09-23 18:25:57 -0400360 cursor.moveToPosition(-1);
361 while(cursor.moveToNext()) {
362 final long id = cursor.getLong(ID_INDEX);
363 final long updateTime = cursor.getLong(ID_MODIFIED);
Chris Wren1ada10d2013-09-13 18:01:38 -0400364 Key key = getKey(Key.SCREEN, id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400365 keys.add(key);
Chris Wren22e130d2013-09-23 18:25:57 -0400366 currentIds.add(keyToBackupKey(key));
367 if (updateTime > in.t) {
368 byte[] blob = packScreen(cursor);
369 writeRowToBackup(key, blob, out, data);
370 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400371 }
372 } finally {
Chris Wren22e130d2013-09-23 18:25:57 -0400373 cursor.close();
Chris Wren1ada10d2013-09-13 18:01:38 -0400374 }
375 if (DEBUG) Log.d(TAG, "screen currentIds.size()=" + currentIds.size());
376
377 // these IDs must have been deleted
378 savedIds.removeAll(currentIds);
Chris Wren22e130d2013-09-23 18:25:57 -0400379 out.rows += removeDeletedKeysFromBackup(savedIds, data);
Chris Wren1ada10d2013-09-13 18:01:38 -0400380 }
381
382 /**
383 * Read a screen from the stream.
384 *
385 * <P>Keys arrive in any order, so children of this screen may already exist.
386 *
387 * @param key identifier for the row
388 * @param buffer the serialized proto from the stream, may be larger than dataSize
389 * @param dataSize the size of the proto from the stream
390 * @param keys keys to mark as clean in the notes for next backup
391 */
392 private void restoreScreen(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
393 Log.v(TAG, "unpacking screen " + key.id);
394 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
395 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
396 try {
397 Screen screen = unpackScreen(buffer, 0, dataSize);
398 if (DEBUG) Log.d(TAG, "unpacked " + screen.rank);
399 } catch (InvalidProtocolBufferNanoException e) {
400 Log.w(TAG, "failed to decode proto", e);
401 }
402 }
403
Chris Wren22e130d2013-09-23 18:25:57 -0400404 /**
405 * Write all the static icon resources we need to render placeholders
406 * for a package that is not installed.
407 *
408 * @param in notes from last backup
409 * @param data output stream for key/value pairs
410 * @param out notes about this backup
411 * @param keys keys to mark as clean in the notes for next backup
412 * @throws IOException
413 */
414 private void backupIcons(Journal in, BackupDataOutput data, Journal out,
415 ArrayList<Key> keys) throws IOException {
Chris Wrenfd13c712013-09-27 15:45:19 -0400416 // persist icons that haven't been persisted yet
Chris Wren92aa4232013-10-04 11:29:36 -0400417 final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
418 if (appState == null) {
419 dataChanged(); // try again later
Chris Wrend8fe6de2013-10-04 10:42:14 -0400420 if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying icon backup");
421 return;
422 }
Chris Wren92aa4232013-10-04 11:29:36 -0400423 final ContentResolver cr = mContext.getContentResolver();
424 final IconCache iconCache = appState.getIconCache();
425 final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
Chris Wren22e130d2013-09-23 18:25:57 -0400426
427 // read the old ID set
428 Set<String> savedIds = getSavedIdsByType(Key.ICON, in);
429 if (DEBUG) Log.d(TAG, "icon savedIds.size()=" + savedIds.size());
430
431 int startRows = out.rows;
432 if (DEBUG) Log.d(TAG, "starting here: " + startRows);
433 String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION;
434 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
435 where, null, null);
436 Set<String> currentIds = new HashSet<String>(cursor.getCount());
437 try {
438 cursor.moveToPosition(-1);
439 while(cursor.moveToNext()) {
440 final long id = cursor.getLong(ID_INDEX);
441 final String intentDescription = cursor.getString(INTENT_INDEX);
442 try {
443 Intent intent = Intent.parseUri(intentDescription, 0);
444 ComponentName cn = intent.getComponent();
445 Key key = null;
446 String backupKey = null;
447 if (cn != null) {
448 key = getKey(Key.ICON, cn.flattenToShortString());
449 backupKey = keyToBackupKey(key);
450 currentIds.add(backupKey);
451 } else {
452 Log.w(TAG, "empty intent on application favorite: " + id);
453 }
454 if (savedIds.contains(backupKey)) {
455 if (DEBUG) Log.d(TAG, "already saved icon " + backupKey);
456
457 // remember that we already backed this up previously
458 keys.add(key);
459 } else if (backupKey != null) {
460 if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
461 if ((out.rows - startRows) < MAX_ICONS_PER_PASS) {
462 if (DEBUG) Log.d(TAG, "saving icon " + backupKey);
463 Bitmap icon = iconCache.getIcon(intent);
464 keys.add(key);
465 if (icon != null && !iconCache.isDefaultIcon(icon)) {
466 byte[] blob = packIcon(dpi, icon);
467 writeRowToBackup(key, blob, out, data);
468 }
469 } else {
Chris Wrenfd13c712013-09-27 15:45:19 -0400470 if (DEBUG) Log.d(TAG, "scheduling another run for icon " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -0400471 // too many icons for this pass, request another.
Chris Wren92aa4232013-10-04 11:29:36 -0400472 dataChanged();
Chris Wren22e130d2013-09-23 18:25:57 -0400473 }
474 }
475 } catch (URISyntaxException e) {
476 Log.w(TAG, "invalid URI on application favorite: " + id);
477 } catch (IOException e) {
478 Log.w(TAG, "unable to save application icon for favorite: " + id);
479 }
480
481 }
482 } finally {
483 cursor.close();
484 }
485 if (DEBUG) Log.d(TAG, "icon currentIds.size()=" + currentIds.size());
486
487 // these IDs must have been deleted
488 savedIds.removeAll(currentIds);
489 out.rows += removeDeletedKeysFromBackup(savedIds, data);
490 }
491
492 /**
493 * Read an icon from the stream.
494 *
Chris Wrenfd13c712013-09-27 15:45:19 -0400495 * <P>Keys arrive in any order, so shortcuts that use this icon may already exist.
Chris Wren22e130d2013-09-23 18:25:57 -0400496 *
497 * @param key identifier for the row
498 * @param buffer the serialized proto from the stream, may be larger than dataSize
499 * @param dataSize the size of the proto from the stream
500 * @param keys keys to mark as clean in the notes for next backup
501 */
502 private void restoreIcon(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
503 Log.v(TAG, "unpacking icon " + key.id);
504 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
505 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
506 try {
507 Resource res = unpackIcon(buffer, 0, dataSize);
508 if (DEBUG) Log.d(TAG, "unpacked " + res.dpi);
509 if (DEBUG) Log.d(TAG, "read " +
510 Base64.encodeToString(res.data, 0, res.data.length,
511 Base64.NO_WRAP));
512 Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
513 if (icon == null) {
514 Log.w(TAG, "failed to unpack icon for " + key.name);
515 }
516 } catch (InvalidProtocolBufferNanoException e) {
517 Log.w(TAG, "failed to decode proto", e);
518 }
519 }
520
Chris Wrenfd13c712013-09-27 15:45:19 -0400521 /**
522 * Write all the static widget resources we need to render placeholders
523 * for a package that is not installed.
524 *
525 * @param in notes from last backup
526 * @param data output stream for key/value pairs
527 * @param out notes about this backup
528 * @param keys keys to mark as clean in the notes for next backup
529 * @throws IOException
530 */
531 private void backupWidgets(Journal in, BackupDataOutput data, Journal out,
532 ArrayList<Key> keys) throws IOException {
533 // persist static widget info that hasn't been persisted yet
Chris Wrend8fe6de2013-10-04 10:42:14 -0400534 final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
535 if (appState == null) {
Chris Wren92aa4232013-10-04 11:29:36 -0400536 dataChanged(); // try again later
Chris Wrend8fe6de2013-10-04 10:42:14 -0400537 if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying widget backup");
538 return;
539 }
Chris Wren92aa4232013-10-04 11:29:36 -0400540 final ContentResolver cr = mContext.getContentResolver();
541 final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext);
542 final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(mContext);
Chris Wrenfd13c712013-09-27 15:45:19 -0400543 final IconCache iconCache = appState.getIconCache();
Chris Wren92aa4232013-10-04 11:29:36 -0400544 final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
Chris Wrenfd13c712013-09-27 15:45:19 -0400545 final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
546 if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
547
548 // read the old ID set
549 Set<String> savedIds = getSavedIdsByType(Key.WIDGET, in);
550 if (DEBUG) Log.d(TAG, "widgets savedIds.size()=" + savedIds.size());
551
552 int startRows = out.rows;
553 if (DEBUG) Log.d(TAG, "starting here: " + startRows);
554 String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPWIDGET;
555 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
556 where, null, null);
557 Set<String> currentIds = new HashSet<String>(cursor.getCount());
558 try {
559 cursor.moveToPosition(-1);
560 while(cursor.moveToNext()) {
561 final long id = cursor.getLong(ID_INDEX);
562 final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX);
563 final int spanX = cursor.getInt(SPANX_INDEX);
564 final int spanY = cursor.getInt(SPANY_INDEX);
565 final ComponentName provider = ComponentName.unflattenFromString(providerName);
566 Key key = null;
567 String backupKey = null;
568 if (provider != null) {
569 key = getKey(Key.WIDGET, providerName);
570 backupKey = keyToBackupKey(key);
571 currentIds.add(backupKey);
572 } else {
573 Log.w(TAG, "empty intent on appwidget: " + id);
574 }
575 if (savedIds.contains(backupKey)) {
576 if (DEBUG) Log.d(TAG, "already saved widget " + backupKey);
577
578 // remember that we already backed this up previously
579 keys.add(key);
580 } else if (backupKey != null) {
581 if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
582 if ((out.rows - startRows) < MAX_WIDGETS_PER_PASS) {
583 if (DEBUG) Log.d(TAG, "saving widget " + backupKey);
584 previewLoader.setPreviewSize(spanX * profile.cellWidthPx,
585 spanY * profile.cellHeightPx, widgetSpacingLayout);
586 byte[] blob = packWidget(dpi, previewLoader, iconCache, provider);
Chris Wrenb1fd63b2013-10-03 15:43:58 -0400587 keys.add(key);
Chris Wrenfd13c712013-09-27 15:45:19 -0400588 writeRowToBackup(key, blob, out, data);
589
590 } else {
591 if (DEBUG) Log.d(TAG, "scheduling another run for widget " + backupKey);
592 // too many widgets for this pass, request another.
Chris Wren92aa4232013-10-04 11:29:36 -0400593 dataChanged();
Chris Wrenfd13c712013-09-27 15:45:19 -0400594 }
595 }
596 }
597 } finally {
598 cursor.close();
599 }
600 if (DEBUG) Log.d(TAG, "widget currentIds.size()=" + currentIds.size());
601
602 // these IDs must have been deleted
603 savedIds.removeAll(currentIds);
604 out.rows += removeDeletedKeysFromBackup(savedIds, data);
605 }
606
607 /**
608 * Read a widget from the stream.
609 *
610 * <P>Keys arrive in any order, so widgets that use this data may already exist.
611 *
612 * @param key identifier for the row
613 * @param buffer the serialized proto from the stream, may be larger than dataSize
614 * @param dataSize the size of the proto from the stream
615 * @param keys keys to mark as clean in the notes for next backup
616 */
617 private void restoreWidget(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
618 Log.v(TAG, "unpacking widget " + key.id);
619 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
620 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
621 try {
622 Widget widget = unpackWidget(buffer, 0, dataSize);
623 if (DEBUG) Log.d(TAG, "unpacked " + widget.provider);
624 if (widget.icon.data != null) {
625 Bitmap icon = BitmapFactory
626 .decodeByteArray(widget.icon.data, 0, widget.icon.data.length);
627 if (icon == null) {
628 Log.w(TAG, "failed to unpack widget icon for " + key.name);
629 }
630 }
631 } catch (InvalidProtocolBufferNanoException e) {
632 Log.w(TAG, "failed to decode proto", e);
633 }
634 }
635
Chris Wren22e130d2013-09-23 18:25:57 -0400636 /** create a new key, with an integer ID.
Chris Wren1ada10d2013-09-13 18:01:38 -0400637 *
638 * <P> Keys contain their own checksum instead of using
639 * the heavy-weight CheckedMessage wrapper.
640 */
641 private Key getKey(int type, long id) {
642 Key key = new Key();
643 key.type = type;
644 key.id = id;
645 key.checksum = checkKey(key);
646 return key;
647 }
648
Chris Wren22e130d2013-09-23 18:25:57 -0400649 /** create a new key for a named object.
650 *
651 * <P> Keys contain their own checksum instead of using
652 * the heavy-weight CheckedMessage wrapper.
653 */
654 private Key getKey(int type, String name) {
655 Key key = new Key();
656 key.type = type;
657 key.name = name;
658 key.checksum = checkKey(key);
659 return key;
660 }
661
Chris Wren1ada10d2013-09-13 18:01:38 -0400662 /** keys need to be strings, serialize and encode. */
663 private String keyToBackupKey(Key key) {
Chris Wren978194c2013-10-03 17:47:22 -0400664 return Base64.encodeToString(Key.toByteArray(key), Base64.NO_WRAP);
Chris Wren1ada10d2013-09-13 18:01:38 -0400665 }
666
667 /** keys need to be strings, decode and parse. */
668 private Key backupKeyToKey(String backupKey) throws KeyParsingException {
669 try {
670 Key key = Key.parseFrom(Base64.decode(backupKey, Base64.DEFAULT));
671 if (key.checksum != checkKey(key)) {
672 key = null;
673 throw new KeyParsingException("invalid key read from stream" + backupKey);
674 }
675 return key;
676 } catch (InvalidProtocolBufferNanoException e) {
677 throw new KeyParsingException(e);
678 } catch (IllegalArgumentException e) {
679 throw new KeyParsingException(e);
680 }
681 }
682
Chris Wren22e130d2013-09-23 18:25:57 -0400683 private String getKeyName(Key key) {
684 if (TextUtils.isEmpty(key.name)) {
685 return Long.toString(key.id);
686 } else {
687 return key.name;
688 }
689
690 }
691
692 private String geKeyType(Key key) {
693 switch (key.type) {
694 case Key.FAVORITE:
695 return "favorite";
696 case Key.SCREEN:
697 return "screen";
698 case Key.ICON:
699 return "icon";
Chris Wrenfd13c712013-09-27 15:45:19 -0400700 case Key.WIDGET:
701 return "widget";
Chris Wren22e130d2013-09-23 18:25:57 -0400702 default:
703 return "anonymous";
704 }
705 }
706
Chris Wren1ada10d2013-09-13 18:01:38 -0400707 /** Compute the checksum over the important bits of a key. */
708 private long checkKey(Key key) {
709 CRC32 checksum = new CRC32();
710 checksum.update(key.type);
711 checksum.update((int) (key.id & 0xffff));
712 checksum.update((int) ((key.id >> 32) & 0xffff));
713 if (!TextUtils.isEmpty(key.name)) {
714 checksum.update(key.name.getBytes());
715 }
716 return checksum.getValue();
717 }
718
719 /** Serialize a Favorite for persistence, including a checksum wrapper. */
720 private byte[] packFavorite(Cursor c) {
721 Favorite favorite = new Favorite();
722 favorite.id = c.getLong(ID_INDEX);
723 favorite.screen = c.getInt(SCREEN_INDEX);
724 favorite.container = c.getInt(CONTAINER_INDEX);
725 favorite.cellX = c.getInt(CELLX_INDEX);
726 favorite.cellY = c.getInt(CELLY_INDEX);
727 favorite.spanX = c.getInt(SPANX_INDEX);
728 favorite.spanY = c.getInt(SPANY_INDEX);
729 favorite.iconType = c.getInt(ICON_TYPE_INDEX);
730 if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
731 String iconPackage = c.getString(ICON_PACKAGE_INDEX);
732 if (!TextUtils.isEmpty(iconPackage)) {
733 favorite.iconPackage = iconPackage;
734 }
735 String iconResource = c.getString(ICON_RESOURCE_INDEX);
736 if (!TextUtils.isEmpty(iconResource)) {
737 favorite.iconResource = iconResource;
738 }
739 }
740 if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
741 byte[] blob = c.getBlob(ICON_INDEX);
742 if (blob != null && blob.length > 0) {
743 favorite.icon = blob;
744 }
745 }
746 String title = c.getString(TITLE_INDEX);
747 if (!TextUtils.isEmpty(title)) {
748 favorite.title = title;
749 }
750 String intent = c.getString(INTENT_INDEX);
751 if (!TextUtils.isEmpty(intent)) {
752 favorite.intent = intent;
753 }
754 favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
755 if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
756 favorite.appWidgetId = c.getInt(APPWIDGET_ID_INDEX);
757 String appWidgetProvider = c.getString(APPWIDGET_PROVIDER_INDEX);
758 if (!TextUtils.isEmpty(appWidgetProvider)) {
759 favorite.appWidgetProvider = appWidgetProvider;
760 }
761 }
762
763 return writeCheckedBytes(favorite);
764 }
765
766 /** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
767 private Favorite unpackFavorite(byte[] buffer, int offset, int dataSize)
768 throws InvalidProtocolBufferNanoException {
769 Favorite favorite = new Favorite();
770 MessageNano.mergeFrom(favorite, readCheckedBytes(buffer, offset, dataSize));
771 return favorite;
772 }
773
774 /** Serialize a Screen for persistence, including a checksum wrapper. */
775 private byte[] packScreen(Cursor c) {
776 Screen screen = new Screen();
777 screen.id = c.getLong(ID_INDEX);
778 screen.rank = c.getInt(SCREEN_RANK_INDEX);
779
780 return writeCheckedBytes(screen);
781 }
782
783 /** Deserialize a Screen from persistence, after verifying checksum wrapper. */
784 private Screen unpackScreen(byte[] buffer, int offset, int dataSize)
785 throws InvalidProtocolBufferNanoException {
786 Screen screen = new Screen();
787 MessageNano.mergeFrom(screen, readCheckedBytes(buffer, offset, dataSize));
788 return screen;
789 }
790
Chris Wren22e130d2013-09-23 18:25:57 -0400791 /** Serialize an icon Resource for persistence, including a checksum wrapper. */
792 private byte[] packIcon(int dpi, Bitmap icon) {
793 Resource res = new Resource();
794 res.dpi = dpi;
795 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400796 if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wren22e130d2013-09-23 18:25:57 -0400797 res.data = os.toByteArray();
798 }
799 return writeCheckedBytes(res);
800 }
801
802 /** Deserialize an icon resource from persistence, after verifying checksum wrapper. */
803 private Resource unpackIcon(byte[] buffer, int offset, int dataSize)
804 throws InvalidProtocolBufferNanoException {
805 Resource res = new Resource();
806 MessageNano.mergeFrom(res, readCheckedBytes(buffer, offset, dataSize));
807 return res;
808 }
809
Chris Wrenfd13c712013-09-27 15:45:19 -0400810 /** Serialize a widget for persistence, including a checksum wrapper. */
811 private byte[] packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache,
812 ComponentName provider) {
813 final AppWidgetProviderInfo info = findAppWidgetProviderInfo(provider);
814 Widget widget = new Widget();
815 widget.provider = provider.flattenToShortString();
816 widget.label = info.label;
817 widget.configure = info.configure != null;
818 if (info.icon != 0) {
819 widget.icon = new Resource();
820 Drawable fullResIcon = iconCache.getFullResIcon(provider.getPackageName(), info.icon);
Chris Wren92aa4232013-10-04 11:29:36 -0400821 Bitmap icon = Utilities.createIconBitmap(fullResIcon, mContext);
Chris Wrenfd13c712013-09-27 15:45:19 -0400822 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400823 if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wrenfd13c712013-09-27 15:45:19 -0400824 widget.icon.data = os.toByteArray();
825 widget.icon.dpi = dpi;
826 }
827 }
828 if (info.previewImage != 0) {
829 widget.preview = new Resource();
830 Bitmap preview = previewLoader.generateWidgetPreview(info, null);
831 ByteArrayOutputStream os = new ByteArrayOutputStream();
Chris Wrenb86f0762013-10-04 10:10:21 -0400832 if (preview.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
Chris Wrenfd13c712013-09-27 15:45:19 -0400833 widget.preview.data = os.toByteArray();
834 widget.preview.dpi = dpi;
835 }
836 }
837 return writeCheckedBytes(widget);
838 }
839
840 /** Deserialize a widget from persistence, after verifying checksum wrapper. */
841 private Widget unpackWidget(byte[] buffer, int offset, int dataSize)
842 throws InvalidProtocolBufferNanoException {
843 Widget widget = new Widget();
844 MessageNano.mergeFrom(widget, readCheckedBytes(buffer, offset, dataSize));
845 return widget;
846 }
847
Chris Wren1ada10d2013-09-13 18:01:38 -0400848 /**
849 * Read the old journal from the input file.
850 *
851 * In the event of any error, just pretend we didn't have a journal,
852 * in that case, do a full backup.
853 *
854 * @param oldState the read-0only file descriptor pointing to the old journal
855 * @return a Journal protocol bugffer
856 */
857 private Journal readJournal(ParcelFileDescriptor oldState) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400858 Journal journal = new Journal();
Chris Wren92aa4232013-10-04 11:29:36 -0400859 if (oldState == null) {
860 return journal;
861 }
862 FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor());
863 try {
864 int remaining = inStream.available();
865 if (DEBUG) Log.d(TAG, "available " + remaining);
866 if (remaining < MAX_JOURNAL_SIZE) {
867 byte[] buffer = new byte[remaining];
Chris Wren1ada10d2013-09-13 18:01:38 -0400868 int bytesRead = 0;
Chris Wren92aa4232013-10-04 11:29:36 -0400869 while (remaining > 0) {
870 try {
871 int result = inStream.read(buffer, bytesRead, remaining);
872 if (result > 0) {
873 if (DEBUG) Log.d(TAG, "read some bytes: " + result);
874 remaining -= result;
875 bytesRead += result;
876 } else {
877 // stop reading ands see what there is to parse
878 Log.w(TAG, "read error: " + result);
879 remaining = 0;
880 }
881 } catch (IOException e) {
882 Log.w(TAG, "failed to read the journal", e);
883 buffer = null;
884 remaining = 0;
885 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400886 }
Chris Wren92aa4232013-10-04 11:29:36 -0400887 if (DEBUG) Log.d(TAG, "journal bytes read: " + bytesRead);
888
889 if (buffer != null) {
890 try {
891 MessageNano.mergeFrom(journal, readCheckedBytes(buffer, 0, bytesRead));
892 } catch (InvalidProtocolBufferNanoException e) {
893 Log.d(TAG, "failed to read the journal", e);
894 journal.clear();
895 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400896 }
897 }
Chris Wren92aa4232013-10-04 11:29:36 -0400898 } catch (IOException e) {
899 Log.d(TAG, "failed to close the journal", e);
900 } finally {
Chris Wren1ada10d2013-09-13 18:01:38 -0400901 try {
902 inStream.close();
903 } catch (IOException e) {
904 Log.d(TAG, "failed to close the journal", e);
905 }
906 }
907 return journal;
908 }
909
Chris Wren22e130d2013-09-23 18:25:57 -0400910 private void writeRowToBackup(Key key, byte[] blob, Journal out,
911 BackupDataOutput data) throws IOException {
912 String backupKey = keyToBackupKey(key);
913 data.writeEntityHeader(backupKey, blob.length);
914 data.writeEntityData(blob, blob.length);
915 out.rows++;
916 out.bytes += blob.length;
917 Log.v(TAG, "saving " + geKeyType(key) + " " + backupKey + ": " +
918 getKeyName(key) + "/" + blob.length);
Chris Wren92aa4232013-10-04 11:29:36 -0400919 if(DEBUG_PAYLOAD) {
Chris Wren2b6c21d2013-10-02 14:16:04 -0400920 String encoded = Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP);
921 final int chunkSize = 1024;
922 for (int offset = 0; offset < encoded.length(); offset += chunkSize) {
923 int end = offset + chunkSize;
924 end = Math.min(end, encoded.length());
925 Log.d(TAG, "wrote " + encoded.substring(offset, end));
926 }
927 }
Chris Wren22e130d2013-09-23 18:25:57 -0400928 }
929
930 private Set<String> getSavedIdsByType(int type, Journal in) {
931 Set<String> savedIds = new HashSet<String>();
932 for(int i = 0; i < in.key.length; i++) {
933 Key key = in.key[i];
934 if (key.type == type) {
935 savedIds.add(keyToBackupKey(key));
936 }
937 }
938 return savedIds;
939 }
940
941 private int removeDeletedKeysFromBackup(Set<String> deletedIds, BackupDataOutput data)
942 throws IOException {
943 int rows = 0;
944 for(String deleted: deletedIds) {
945 Log.v(TAG, "dropping icon " + deleted);
946 data.writeEntityHeader(deleted, -1);
947 rows++;
948 }
949 return rows;
950 }
951
Chris Wren1ada10d2013-09-13 18:01:38 -0400952 /**
953 * Write the new journal to the output file.
954 *
955 * In the event of any error, just pretend we didn't have a journal,
956 * in that case, do a full backup.
957
958 * @param newState the write-only file descriptor pointing to the new journal
959 * @param journal a Journal protocol buffer
960 */
961 private void writeJournal(ParcelFileDescriptor newState, Journal journal) {
962 FileOutputStream outStream = null;
963 try {
964 outStream = new FileOutputStream(newState.getFileDescriptor());
965 outStream.write(writeCheckedBytes(journal));
966 outStream.close();
967 } catch (IOException e) {
968 Log.d(TAG, "failed to write backup journal", e);
969 }
970 }
971
972 /** Wrap a proto in a CheckedMessage and compute the checksum. */
973 private byte[] writeCheckedBytes(MessageNano proto) {
974 CheckedMessage wrapper = new CheckedMessage();
975 wrapper.payload = MessageNano.toByteArray(proto);
976 CRC32 checksum = new CRC32();
977 checksum.update(wrapper.payload);
978 wrapper.checksum = checksum.getValue();
979 return MessageNano.toByteArray(wrapper);
980 }
981
982 /** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
983 private byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
984 throws InvalidProtocolBufferNanoException {
985 CheckedMessage wrapper = new CheckedMessage();
986 MessageNano.mergeFrom(wrapper, buffer, offset, dataSize);
987 CRC32 checksum = new CRC32();
988 checksum.update(wrapper.payload);
989 if (wrapper.checksum != checksum.getValue()) {
990 throw new InvalidProtocolBufferNanoException("checksum does not match");
991 }
992 return wrapper.payload;
993 }
994
Chris Wrenfd13c712013-09-27 15:45:19 -0400995 private AppWidgetProviderInfo findAppWidgetProviderInfo(ComponentName component) {
996 if (mWidgetMap == null) {
997 List<AppWidgetProviderInfo> widgets =
Chris Wren92aa4232013-10-04 11:29:36 -0400998 AppWidgetManager.getInstance(mContext).getInstalledProviders();
Chris Wrenfd13c712013-09-27 15:45:19 -0400999 mWidgetMap = new HashMap<ComponentName, AppWidgetProviderInfo>(widgets.size());
1000 for (AppWidgetProviderInfo info : widgets) {
1001 mWidgetMap.put(info.provider, info);
1002 }
1003 }
1004 return mWidgetMap.get(component);
1005 }
1006
Chris Wren1ada10d2013-09-13 18:01:38 -04001007 private class KeyParsingException extends Throwable {
1008 private KeyParsingException(Throwable cause) {
1009 super(cause);
1010 }
1011
1012 public KeyParsingException(String reason) {
1013 super(reason);
1014 }
1015 }
1016}