Home | History | Annotate | Download | only in launcher3
      1 /*
      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 package com.android.launcher3;
     17 
     18 import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
     19 import com.google.protobuf.nano.MessageNano;
     20 
     21 import com.android.launcher3.LauncherSettings.Favorites;
     22 import com.android.launcher3.LauncherSettings.WorkspaceScreens;
     23 import com.android.launcher3.backup.BackupProtos;
     24 import com.android.launcher3.backup.BackupProtos.CheckedMessage;
     25 import com.android.launcher3.backup.BackupProtos.Favorite;
     26 import com.android.launcher3.backup.BackupProtos.Journal;
     27 import com.android.launcher3.backup.BackupProtos.Key;
     28 import com.android.launcher3.backup.BackupProtos.Resource;
     29 import com.android.launcher3.backup.BackupProtos.Screen;
     30 import com.android.launcher3.backup.BackupProtos.Widget;
     31 import com.android.launcher3.compat.UserManagerCompat;
     32 import com.android.launcher3.compat.UserHandleCompat;
     33 
     34 import android.app.backup.BackupDataInputStream;
     35 import android.app.backup.BackupDataOutput;
     36 import android.app.backup.BackupHelper;
     37 import android.app.backup.BackupManager;
     38 import android.appwidget.AppWidgetManager;
     39 import android.appwidget.AppWidgetProviderInfo;
     40 import android.content.ComponentName;
     41 import android.content.ContentResolver;
     42 import android.content.ContentValues;
     43 import android.content.Context;
     44 import android.content.Intent;
     45 import android.database.Cursor;
     46 import android.graphics.Bitmap;
     47 import android.graphics.BitmapFactory;
     48 import android.graphics.drawable.Drawable;
     49 import android.os.ParcelFileDescriptor;
     50 import android.text.TextUtils;
     51 import android.util.Base64;
     52 import android.util.Log;
     53 
     54 import java.io.ByteArrayOutputStream;
     55 import java.io.FileInputStream;
     56 import java.io.FileOutputStream;
     57 import java.io.IOException;
     58 import java.net.URISyntaxException;
     59 import java.util.ArrayList;
     60 import java.util.HashMap;
     61 import java.util.HashSet;
     62 import java.util.List;
     63 import java.util.Set;
     64 import java.util.zip.CRC32;
     65 
     66 /**
     67  * Persist the launcher home state across calamities.
     68  */
     69 public class LauncherBackupHelper implements BackupHelper {
     70 
     71     private static final String TAG = "LauncherBackupHelper";
     72     private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
     73     private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
     74     private static final boolean DEBUG_PAYLOAD = false;
     75 
     76     private static final int MAX_JOURNAL_SIZE = 1000000;
     77 
     78     /** icons are large, dribble them out */
     79     private static final int MAX_ICONS_PER_PASS = 10;
     80 
     81     /** widgets contain previews, which are very large, dribble them out */
     82     private static final int MAX_WIDGETS_PER_PASS = 5;
     83 
     84     public static final int IMAGE_COMPRESSION_QUALITY = 75;
     85 
     86     public static final String LAUNCHER_PREFIX = "L";
     87 
     88     public static final String LAUNCHER_PREFS_PREFIX = "LP";
     89 
     90     private static final Bitmap.CompressFormat IMAGE_FORMAT =
     91             android.graphics.Bitmap.CompressFormat.PNG;
     92 
     93     private static BackupManager sBackupManager;
     94 
     95     private static final String[] FAVORITE_PROJECTION = {
     96             Favorites._ID,                     // 0
     97             Favorites.MODIFIED,                // 1
     98             Favorites.INTENT,                  // 2
     99             Favorites.APPWIDGET_PROVIDER,      // 3
    100             Favorites.APPWIDGET_ID,            // 4
    101             Favorites.CELLX,                   // 5
    102             Favorites.CELLY,                   // 6
    103             Favorites.CONTAINER,               // 7
    104             Favorites.ICON,                    // 8
    105             Favorites.ICON_PACKAGE,            // 9
    106             Favorites.ICON_RESOURCE,           // 10
    107             Favorites.ICON_TYPE,               // 11
    108             Favorites.ITEM_TYPE,               // 12
    109             Favorites.SCREEN,                  // 13
    110             Favorites.SPANX,                   // 14
    111             Favorites.SPANY,                   // 15
    112             Favorites.TITLE,                   // 16
    113             Favorites.PROFILE_ID,              // 17
    114     };
    115 
    116     private static final int ID_INDEX = 0;
    117     private static final int ID_MODIFIED = 1;
    118     private static final int INTENT_INDEX = 2;
    119     private static final int APPWIDGET_PROVIDER_INDEX = 3;
    120     private static final int APPWIDGET_ID_INDEX = 4;
    121     private static final int CELLX_INDEX = 5;
    122     private static final int CELLY_INDEX = 6;
    123     private static final int CONTAINER_INDEX = 7;
    124     private static final int ICON_INDEX = 8;
    125     private static final int ICON_PACKAGE_INDEX = 9;
    126     private static final int ICON_RESOURCE_INDEX = 10;
    127     private static final int ICON_TYPE_INDEX = 11;
    128     private static final int ITEM_TYPE_INDEX = 12;
    129     private static final int SCREEN_INDEX = 13;
    130     private static final int SPANX_INDEX = 14;
    131     private static final int SPANY_INDEX = 15;
    132     private static final int TITLE_INDEX = 16;
    133     private static final int PROFILE_ID_INDEX = 17;
    134 
    135     private static final String[] SCREEN_PROJECTION = {
    136             WorkspaceScreens._ID,              // 0
    137             WorkspaceScreens.MODIFIED,         // 1
    138             WorkspaceScreens.SCREEN_RANK       // 2
    139     };
    140 
    141     private static final int SCREEN_RANK_INDEX = 2;
    142 
    143     private static IconCache mIconCache;
    144 
    145     private final Context mContext;
    146 
    147     private final boolean mRestoreEnabled;
    148 
    149     private HashMap<ComponentName, AppWidgetProviderInfo> mWidgetMap;
    150 
    151     private final ArrayList<Key> mKeys;
    152 
    153     public LauncherBackupHelper(Context context, boolean restoreEnabled) {
    154         mContext = context;
    155         mRestoreEnabled = restoreEnabled;
    156         mKeys = new ArrayList<Key>();
    157     }
    158 
    159     private void dataChanged() {
    160         if (sBackupManager == null) {
    161             sBackupManager = new BackupManager(mContext);
    162         }
    163         sBackupManager.dataChanged();
    164     }
    165 
    166     /**
    167      * Back up launcher data so we can restore the user's state on a new device.
    168      *
    169      * <P>The journal is a timestamp and a list of keys that were saved as of that time.
    170      *
    171      * <P>Keys may come back in any order, so each key/value is one complete row of the database.
    172      *
    173      * @param oldState notes from the last backup
    174      * @param data incremental key/value pairs to persist off-device
    175      * @param newState notes for the next backup
    176      * @throws IOException
    177      */
    178     @Override
    179     public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
    180             ParcelFileDescriptor newState) {
    181         if (VERBOSE) Log.v(TAG, "onBackup");
    182 
    183         Journal in = readJournal(oldState);
    184         Journal out = new Journal();
    185 
    186         long lastBackupTime = in.t;
    187         out.t = System.currentTimeMillis();
    188         out.rows = 0;
    189         out.bytes = 0;
    190 
    191         Log.v(TAG, "lastBackupTime = " + lastBackupTime);
    192 
    193         ArrayList<Key> keys = new ArrayList<Key>();
    194         if (launcherIsReady()) {
    195             try {
    196                 backupFavorites(in, data, out, keys);
    197                 backupScreens(in, data, out, keys);
    198                 backupIcons(in, data, out, keys);
    199                 backupWidgets(in, data, out, keys);
    200             } catch (IOException e) {
    201                 Log.e(TAG, "launcher backup has failed", e);
    202             }
    203             out.key = keys.toArray(new BackupProtos.Key[keys.size()]);
    204         } else {
    205             out = in;
    206         }
    207 
    208         writeJournal(newState, out);
    209         Log.v(TAG, "onBackup: wrote " + out.bytes + "b in " + out.rows + " rows.");
    210     }
    211 
    212     /**
    213      * Restore launcher configuration from the restored data stream.
    214      *
    215      * <P>Keys may arrive in any order.
    216      *
    217      * @param data the key/value pair from the server
    218      */
    219     @Override
    220     public void restoreEntity(BackupDataInputStream data) {
    221         if (VERBOSE) Log.v(TAG, "restoreEntity");
    222         byte[] buffer = new byte[512];
    223             String backupKey = data.getKey();
    224             int dataSize = data.size();
    225             if (buffer.length < dataSize) {
    226                 buffer = new byte[dataSize];
    227             }
    228             Key key = null;
    229         int bytesRead = 0;
    230         try {
    231             bytesRead = data.read(buffer, 0, dataSize);
    232             if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
    233         } catch (IOException e) {
    234             Log.e(TAG, "failed to read entity from restore data", e);
    235         }
    236         try {
    237             key = backupKeyToKey(backupKey);
    238             mKeys.add(key);
    239             switch (key.type) {
    240                 case Key.FAVORITE:
    241                     restoreFavorite(key, buffer, dataSize, mKeys);
    242                     break;
    243 
    244                 case Key.SCREEN:
    245                     restoreScreen(key, buffer, dataSize, mKeys);
    246                     break;
    247 
    248                 case Key.ICON:
    249                     restoreIcon(key, buffer, dataSize, mKeys);
    250                     break;
    251 
    252                 case Key.WIDGET:
    253                     restoreWidget(key, buffer, dataSize, mKeys);
    254                     break;
    255 
    256                 default:
    257                     Log.w(TAG, "unknown restore entity type: " + key.type);
    258                     break;
    259             }
    260         } catch (KeyParsingException e) {
    261             Log.w(TAG, "ignoring unparsable backup key: " + backupKey);
    262         }
    263 
    264     }
    265 
    266     /**
    267      * Record the restore state for the next backup.
    268      *
    269      * @param newState notes about the backup state after restore.
    270      */
    271     @Override
    272     public void writeNewStateDescription(ParcelFileDescriptor newState) {
    273         // clear the output journal time, to force a full backup to
    274         // will catch any changes the restore process might have made
    275         Journal out = new Journal();
    276         out.t = 0;
    277         out.key = mKeys.toArray(new BackupProtos.Key[mKeys.size()]);
    278         writeJournal(newState, out);
    279         Log.v(TAG, "onRestore: read " + mKeys.size() + " rows");
    280         mKeys.clear();
    281     }
    282 
    283     /**
    284      * Write all modified favorites to the data stream.
    285      *
    286      *
    287      * @param in notes from last backup
    288      * @param data output stream for key/value pairs
    289      * @param out notes about this backup
    290      * @param keys keys to mark as clean in the notes for next backup
    291      * @throws IOException
    292      */
    293     private void backupFavorites(Journal in, BackupDataOutput data, Journal out,
    294             ArrayList<Key> keys)
    295             throws IOException {
    296         // read the old ID set
    297         Set<String> savedIds = getSavedIdsByType(Key.FAVORITE, in);
    298         if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
    299 
    300         // persist things that have changed since the last backup
    301         ContentResolver cr = mContext.getContentResolver();
    302         // Don't backup apps in other profiles for now.
    303         Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
    304                 getUserSelectionArg(), null, null);
    305         Set<String> currentIds = new HashSet<String>(cursor.getCount());
    306         try {
    307             cursor.moveToPosition(-1);
    308             while(cursor.moveToNext()) {
    309                 final long id = cursor.getLong(ID_INDEX);
    310                 final long updateTime = cursor.getLong(ID_MODIFIED);
    311                 Key key = getKey(Key.FAVORITE, id);
    312                 keys.add(key);
    313                 final String backupKey = keyToBackupKey(key);
    314                 currentIds.add(backupKey);
    315                 if (!savedIds.contains(backupKey) || updateTime >= in.t) {
    316                     byte[] blob = packFavorite(cursor);
    317                     writeRowToBackup(key, blob, out, data);
    318                 } else {
    319                     if (VERBOSE) Log.v(TAG, "favorite " + id + " was too old: " + updateTime);
    320                 }
    321             }
    322         } finally {
    323             cursor.close();
    324         }
    325         if (DEBUG) Log.d(TAG, "favorite currentIds.size()=" + currentIds.size());
    326 
    327         // these IDs must have been deleted
    328         savedIds.removeAll(currentIds);
    329         out.rows += removeDeletedKeysFromBackup(savedIds, data);
    330     }
    331 
    332     /**
    333      * Read a favorite from the stream.
    334      *
    335      * <P>Keys arrive in any order, so screens and containers may not exist yet.
    336      *
    337      * @param key identifier for the row
    338      * @param buffer the serialized proto from the stream, may be larger than dataSize
    339      * @param dataSize the size of the proto from the stream
    340      * @param keys keys to mark as clean in the notes for next backup
    341      */
    342     private void restoreFavorite(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
    343         if (VERBOSE) Log.v(TAG, "unpacking favorite " + key.id);
    344         if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
    345                 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
    346 
    347         if (!mRestoreEnabled) {
    348             if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
    349             return;
    350         }
    351 
    352         try {
    353             ContentResolver cr = mContext.getContentResolver();
    354             ContentValues values = unpackFavorite(buffer, 0, dataSize);
    355             cr.insert(Favorites.CONTENT_URI_NO_NOTIFICATION, values);
    356         } catch (InvalidProtocolBufferNanoException e) {
    357             Log.e(TAG, "failed to decode favorite", e);
    358         }
    359     }
    360 
    361     /**
    362      * Write all modified screens to the data stream.
    363      *
    364      *
    365      * @param in notes from last backup
    366      * @param data output stream for key/value pairs
    367      * @param out notes about this backup
    368      * @param keys keys to mark as clean in the notes for next backup
    369      * @throws IOException
    370      */
    371     private void backupScreens(Journal in, BackupDataOutput data, Journal out,
    372             ArrayList<Key> keys)
    373             throws IOException {
    374         // read the old ID set
    375         Set<String> savedIds = getSavedIdsByType(Key.SCREEN, in);
    376         if (DEBUG) Log.d(TAG, "screen savedIds.size()=" + savedIds.size());
    377 
    378         // persist things that have changed since the last backup
    379         ContentResolver cr = mContext.getContentResolver();
    380         Cursor cursor = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
    381                 null, null, null);
    382         Set<String> currentIds = new HashSet<String>(cursor.getCount());
    383         try {
    384             cursor.moveToPosition(-1);
    385             if (DEBUG) Log.d(TAG, "dumping screens after: " + in.t);
    386             while(cursor.moveToNext()) {
    387                 final long id = cursor.getLong(ID_INDEX);
    388                 final long updateTime = cursor.getLong(ID_MODIFIED);
    389                 Key key = getKey(Key.SCREEN, id);
    390                 keys.add(key);
    391                 final String backupKey = keyToBackupKey(key);
    392                 currentIds.add(backupKey);
    393                 if (!savedIds.contains(backupKey) || updateTime >= in.t) {
    394                     byte[] blob = packScreen(cursor);
    395                     writeRowToBackup(key, blob, out, data);
    396                 } else {
    397                     if (VERBOSE) Log.v(TAG, "screen " + id + " was too old: " + updateTime);
    398                 }
    399             }
    400         } finally {
    401             cursor.close();
    402         }
    403         if (DEBUG) Log.d(TAG, "screen currentIds.size()=" + currentIds.size());
    404 
    405         // these IDs must have been deleted
    406         savedIds.removeAll(currentIds);
    407         out.rows += removeDeletedKeysFromBackup(savedIds, data);
    408     }
    409 
    410     /**
    411      * Read a screen from the stream.
    412      *
    413      * <P>Keys arrive in any order, so children of this screen may already exist.
    414      *
    415      * @param key identifier for the row
    416      * @param buffer the serialized proto from the stream, may be larger than dataSize
    417      * @param dataSize the size of the proto from the stream
    418      * @param keys keys to mark as clean in the notes for next backup
    419      */
    420     private void restoreScreen(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
    421         if (VERBOSE) Log.v(TAG, "unpacking screen " + key.id);
    422         if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
    423                 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
    424 
    425         if (!mRestoreEnabled) {
    426             if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
    427             return;
    428         }
    429 
    430         try {
    431             ContentResolver cr = mContext.getContentResolver();
    432             ContentValues values = unpackScreen(buffer, 0, dataSize);
    433             cr.insert(WorkspaceScreens.CONTENT_URI, values);
    434 
    435         } catch (InvalidProtocolBufferNanoException e) {
    436             Log.e(TAG, "failed to decode screen", e);
    437         }
    438     }
    439 
    440     /**
    441      * Write all the static icon resources we need to render placeholders
    442      * for a package that is not installed.
    443      *
    444      * @param in notes from last backup
    445      * @param data output stream for key/value pairs
    446      * @param out notes about this backup
    447      * @param keys keys to mark as clean in the notes for next backup
    448      * @throws IOException
    449      */
    450     private void backupIcons(Journal in, BackupDataOutput data, Journal out,
    451             ArrayList<Key> keys) throws IOException {
    452         // persist icons that haven't been persisted yet
    453         if (!initializeIconCache()) {
    454             dataChanged(); // try again later
    455             if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying icon backup");
    456             return;
    457         }
    458         final ContentResolver cr = mContext.getContentResolver();
    459         final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
    460         final UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
    461 
    462         // read the old ID set
    463         Set<String> savedIds = getSavedIdsByType(Key.ICON, in);
    464         if (DEBUG) Log.d(TAG, "icon savedIds.size()=" + savedIds.size());
    465 
    466         // Don't backup apps in other profiles for now.
    467         int startRows = out.rows;
    468         if (DEBUG) Log.d(TAG, "starting here: " + startRows);
    469 
    470         String where = "(" + Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION + " OR " +
    471                 Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT + ") AND " +
    472                 getUserSelectionArg();
    473         Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
    474                 where, null, null);
    475         Set<String> currentIds = new HashSet<String>(cursor.getCount());
    476         try {
    477             cursor.moveToPosition(-1);
    478             while(cursor.moveToNext()) {
    479                 final long id = cursor.getLong(ID_INDEX);
    480                 final String intentDescription = cursor.getString(INTENT_INDEX);
    481                 try {
    482                     Intent intent = Intent.parseUri(intentDescription, 0);
    483                     ComponentName cn = intent.getComponent();
    484                     Key key = null;
    485                     String backupKey = null;
    486                     if (cn != null) {
    487                         key = getKey(Key.ICON, cn.flattenToShortString());
    488                         backupKey = keyToBackupKey(key);
    489                         currentIds.add(backupKey);
    490                     } else {
    491                         Log.w(TAG, "empty intent on application favorite: " + id);
    492                     }
    493                     if (savedIds.contains(backupKey)) {
    494                         if (VERBOSE) Log.v(TAG, "already saved icon " + backupKey);
    495 
    496                         // remember that we already backed this up previously
    497                         keys.add(key);
    498                     } else if (backupKey != null) {
    499                         if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
    500                         if ((out.rows - startRows) < MAX_ICONS_PER_PASS) {
    501                             if (VERBOSE) Log.v(TAG, "saving icon " + backupKey);
    502                             Bitmap icon = mIconCache.getIcon(intent, myUserHandle);
    503                             keys.add(key);
    504                             if (icon != null && !mIconCache.isDefaultIcon(icon, myUserHandle)) {
    505                                 byte[] blob = packIcon(dpi, icon);
    506                                 writeRowToBackup(key, blob, out, data);
    507                             }
    508                         } else {
    509                             if (VERBOSE) Log.d(TAG, "deferring icon backup " + backupKey);
    510                             // too many icons for this pass, request another.
    511                             dataChanged();
    512                         }
    513                     }
    514                 } catch (URISyntaxException e) {
    515                     Log.e(TAG, "invalid URI on application favorite: " + id);
    516                 } catch (IOException e) {
    517                     Log.e(TAG, "unable to save application icon for favorite: " + id);
    518                 }
    519 
    520             }
    521         } finally {
    522             cursor.close();
    523         }
    524         if (DEBUG) Log.d(TAG, "icon currentIds.size()=" + currentIds.size());
    525 
    526         // these IDs must have been deleted
    527         savedIds.removeAll(currentIds);
    528         out.rows += removeDeletedKeysFromBackup(savedIds, data);
    529     }
    530 
    531     /**
    532      * Read an icon from the stream.
    533      *
    534      * <P>Keys arrive in any order, so shortcuts that use this icon may already exist.
    535      *
    536      * @param key identifier for the row
    537      * @param buffer the serialized proto from the stream, may be larger than dataSize
    538      * @param dataSize the size of the proto from the stream
    539      * @param keys keys to mark as clean in the notes for next backup
    540      */
    541     private void restoreIcon(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
    542         if (VERBOSE) Log.v(TAG, "unpacking icon " + key.id);
    543         if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
    544                 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
    545 
    546         try {
    547             Resource res = unpackIcon(buffer, 0, dataSize);
    548             if (DEBUG) {
    549                 Log.d(TAG, "unpacked " + res.dpi + " dpi icon");
    550             }
    551             if (DEBUG_PAYLOAD) {
    552                 Log.d(TAG, "read " +
    553                         Base64.encodeToString(res.data, 0, res.data.length,
    554                                 Base64.NO_WRAP));
    555             }
    556             Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
    557             if (icon == null) {
    558                 Log.w(TAG, "failed to unpack icon for " + key.name);
    559             }
    560 
    561             if (!mRestoreEnabled) {
    562                 if (VERBOSE) {
    563                     Log.v(TAG, "restore not enabled: skipping database mutation");
    564                 }
    565                 return;
    566             } else {
    567                 if (VERBOSE) Log.v(TAG, "saving restored icon as: " + key.name);
    568                 IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(key.name),
    569                         icon, res.dpi);
    570             }
    571         } catch (IOException e) {
    572             Log.d(TAG, "failed to save restored icon for: " + key.name, e);
    573         }
    574     }
    575 
    576     /**
    577      * Write all the static widget resources we need to render placeholders
    578      * for a package that is not installed.
    579      *
    580      * @param in notes from last backup
    581      * @param data output stream for key/value pairs
    582      * @param out notes about this backup
    583      * @param keys keys to mark as clean in the notes for next backup
    584      * @throws IOException
    585      */
    586     private void backupWidgets(Journal in, BackupDataOutput data, Journal out,
    587             ArrayList<Key> keys) throws IOException {
    588         // persist static widget info that hasn't been persisted yet
    589         final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
    590         if (appState == null || !initializeIconCache()) {
    591             Log.w(TAG, "Failed to get icon cache during restore");
    592             return;
    593         }
    594         final ContentResolver cr = mContext.getContentResolver();
    595         final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext);
    596         final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(mContext);
    597         final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
    598         final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
    599         if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
    600 
    601         // read the old ID set
    602         Set<String> savedIds = getSavedIdsByType(Key.WIDGET, in);
    603         if (DEBUG) Log.d(TAG, "widgets savedIds.size()=" + savedIds.size());
    604 
    605         int startRows = out.rows;
    606         if (DEBUG) Log.d(TAG, "starting here: " + startRows);
    607         String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPWIDGET + " AND "
    608                 + getUserSelectionArg();
    609         Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
    610                 where, null, null);
    611         Set<String> currentIds = new HashSet<String>(cursor.getCount());
    612         try {
    613             cursor.moveToPosition(-1);
    614             while(cursor.moveToNext()) {
    615                 final long id = cursor.getLong(ID_INDEX);
    616                 final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX);
    617                 final int spanX = cursor.getInt(SPANX_INDEX);
    618                 final int spanY = cursor.getInt(SPANY_INDEX);
    619                 final ComponentName provider = ComponentName.unflattenFromString(providerName);
    620                 Key key = null;
    621                 String backupKey = null;
    622                 if (provider != null) {
    623                     key = getKey(Key.WIDGET, providerName);
    624                     backupKey = keyToBackupKey(key);
    625                     currentIds.add(backupKey);
    626                 } else {
    627                     Log.w(TAG, "empty intent on appwidget: " + id);
    628                 }
    629                 if (savedIds.contains(backupKey)) {
    630                     if (VERBOSE) Log.v(TAG, "already saved widget " + backupKey);
    631 
    632                     // remember that we already backed this up previously
    633                     keys.add(key);
    634                 } else if (backupKey != null) {
    635                     if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
    636                     if ((out.rows - startRows) < MAX_WIDGETS_PER_PASS) {
    637                         if (VERBOSE) Log.v(TAG, "saving widget " + backupKey);
    638                         previewLoader.setPreviewSize(spanX * profile.cellWidthPx,
    639                                 spanY * profile.cellHeightPx, widgetSpacingLayout);
    640                         byte[] blob = packWidget(dpi, previewLoader, mIconCache, provider);
    641                         keys.add(key);
    642                         writeRowToBackup(key, blob, out, data);
    643 
    644                     } else {
    645                         if (VERBOSE) Log.d(TAG, "deferring widget backup " + backupKey);
    646                         // too many widgets for this pass, request another.
    647                         dataChanged();
    648                     }
    649                 }
    650             }
    651         } finally {
    652             cursor.close();
    653         }
    654         if (DEBUG) Log.d(TAG, "widget currentIds.size()=" + currentIds.size());
    655 
    656         // these IDs must have been deleted
    657         savedIds.removeAll(currentIds);
    658         out.rows += removeDeletedKeysFromBackup(savedIds, data);
    659     }
    660 
    661     /**
    662      * Read a widget from the stream.
    663      *
    664      * <P>Keys arrive in any order, so widgets that use this data may already exist.
    665      *
    666      * @param key identifier for the row
    667      * @param buffer the serialized proto from the stream, may be larger than dataSize
    668      * @param dataSize the size of the proto from the stream
    669      * @param keys keys to mark as clean in the notes for next backup
    670      */
    671     private void restoreWidget(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
    672         if (VERBOSE) Log.v(TAG, "unpacking widget " + key.id);
    673         if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
    674                 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
    675         try {
    676             Widget widget = unpackWidget(buffer, 0, dataSize);
    677             if (DEBUG) Log.d(TAG, "unpacked " + widget.provider);
    678             if (widget.icon.data != null)  {
    679                 Bitmap icon = BitmapFactory
    680                         .decodeByteArray(widget.icon.data, 0, widget.icon.data.length);
    681                 if (icon == null) {
    682                     Log.w(TAG, "failed to unpack widget icon for " + key.name);
    683                 } else {
    684                     IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(widget.provider),
    685                             icon, widget.icon.dpi);
    686                 }
    687             }
    688 
    689             if (!mRestoreEnabled) {
    690                 if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
    691                 return;
    692             } else {
    693                 // future site of widget table mutation
    694             }
    695         } catch (InvalidProtocolBufferNanoException e) {
    696             Log.e(TAG, "failed to decode widget", e);
    697         }
    698     }
    699 
    700     /** create a new key, with an integer ID.
    701      *
    702      * <P> Keys contain their own checksum instead of using
    703      * the heavy-weight CheckedMessage wrapper.
    704      */
    705     private Key getKey(int type, long id) {
    706         Key key = new Key();
    707         key.type = type;
    708         key.id = id;
    709         key.checksum = checkKey(key);
    710         return key;
    711     }
    712 
    713     /** create a new key for a named object.
    714      *
    715      * <P> Keys contain their own checksum instead of using
    716      * the heavy-weight CheckedMessage wrapper.
    717      */
    718     private Key getKey(int type, String name) {
    719         Key key = new Key();
    720         key.type = type;
    721         key.name = name;
    722         key.checksum = checkKey(key);
    723         return key;
    724     }
    725 
    726     /** keys need to be strings, serialize and encode. */
    727     private String keyToBackupKey(Key key) {
    728         return Base64.encodeToString(Key.toByteArray(key), Base64.NO_WRAP);
    729     }
    730 
    731     /** keys need to be strings, decode and parse. */
    732     private Key backupKeyToKey(String backupKey) throws KeyParsingException {
    733         try {
    734             Key key = Key.parseFrom(Base64.decode(backupKey, Base64.DEFAULT));
    735             if (key.checksum != checkKey(key)) {
    736                 key = null;
    737                 throw new KeyParsingException("invalid key read from stream" + backupKey);
    738             }
    739             return key;
    740         } catch (InvalidProtocolBufferNanoException e) {
    741             throw new KeyParsingException(e);
    742         } catch (IllegalArgumentException e) {
    743             throw new KeyParsingException(e);
    744         }
    745     }
    746 
    747     private String getKeyName(Key key) {
    748         if (TextUtils.isEmpty(key.name)) {
    749             return Long.toString(key.id);
    750         } else {
    751             return key.name;
    752         }
    753 
    754     }
    755 
    756     private String geKeyType(Key key) {
    757         switch (key.type) {
    758             case Key.FAVORITE:
    759                 return "favorite";
    760             case Key.SCREEN:
    761                 return "screen";
    762             case Key.ICON:
    763                 return "icon";
    764             case Key.WIDGET:
    765                 return "widget";
    766             default:
    767                 return "anonymous";
    768         }
    769     }
    770 
    771     /** Compute the checksum over the important bits of a key. */
    772     private long checkKey(Key key) {
    773         CRC32 checksum = new CRC32();
    774         checksum.update(key.type);
    775         checksum.update((int) (key.id & 0xffff));
    776         checksum.update((int) ((key.id >> 32) & 0xffff));
    777         if (!TextUtils.isEmpty(key.name)) {
    778             checksum.update(key.name.getBytes());
    779         }
    780         return checksum.getValue();
    781     }
    782 
    783     /** Serialize a Favorite for persistence, including a checksum wrapper. */
    784     private byte[] packFavorite(Cursor c) {
    785         Favorite favorite = new Favorite();
    786         favorite.id = c.getLong(ID_INDEX);
    787         favorite.screen = c.getInt(SCREEN_INDEX);
    788         favorite.container = c.getInt(CONTAINER_INDEX);
    789         favorite.cellX = c.getInt(CELLX_INDEX);
    790         favorite.cellY = c.getInt(CELLY_INDEX);
    791         favorite.spanX = c.getInt(SPANX_INDEX);
    792         favorite.spanY = c.getInt(SPANY_INDEX);
    793         favorite.iconType = c.getInt(ICON_TYPE_INDEX);
    794         if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
    795             String iconPackage = c.getString(ICON_PACKAGE_INDEX);
    796             if (!TextUtils.isEmpty(iconPackage)) {
    797                 favorite.iconPackage = iconPackage;
    798             }
    799             String iconResource = c.getString(ICON_RESOURCE_INDEX);
    800             if (!TextUtils.isEmpty(iconResource)) {
    801                 favorite.iconResource = iconResource;
    802             }
    803         }
    804         if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
    805             byte[] blob = c.getBlob(ICON_INDEX);
    806             if (blob != null && blob.length > 0) {
    807                 favorite.icon = blob;
    808             }
    809         }
    810         String title = c.getString(TITLE_INDEX);
    811         if (!TextUtils.isEmpty(title)) {
    812             favorite.title = title;
    813         }
    814         String intentDescription = c.getString(INTENT_INDEX);
    815         if (!TextUtils.isEmpty(intentDescription)) {
    816             try {
    817                 Intent intent = Intent.parseUri(intentDescription, 0);
    818                 intent.removeExtra(ItemInfo.EXTRA_PROFILE);
    819                 favorite.intent = intent.toUri(0);
    820             } catch (URISyntaxException e) {
    821                 Log.e(TAG, "Invalid intent", e);
    822            }
    823         }
    824         favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
    825         if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
    826             favorite.appWidgetId = c.getInt(APPWIDGET_ID_INDEX);
    827             String appWidgetProvider = c.getString(APPWIDGET_PROVIDER_INDEX);
    828             if (!TextUtils.isEmpty(appWidgetProvider)) {
    829                 favorite.appWidgetProvider = appWidgetProvider;
    830             }
    831         }
    832 
    833         return writeCheckedBytes(favorite);
    834     }
    835 
    836     /** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
    837     private ContentValues unpackFavorite(byte[] buffer, int offset, int dataSize)
    838             throws InvalidProtocolBufferNanoException {
    839         Favorite favorite = new Favorite();
    840         MessageNano.mergeFrom(favorite, readCheckedBytes(buffer, offset, dataSize));
    841         if (VERBOSE) Log.v(TAG, "unpacked favorite " + favorite.itemType + ", " +
    842                 (TextUtils.isEmpty(favorite.title) ? favorite.id : favorite.title));
    843         ContentValues values = new ContentValues();
    844         values.put(Favorites._ID, favorite.id);
    845         values.put(Favorites.SCREEN, favorite.screen);
    846         values.put(Favorites.CONTAINER, favorite.container);
    847         values.put(Favorites.CELLX, favorite.cellX);
    848         values.put(Favorites.CELLY, favorite.cellY);
    849         values.put(Favorites.SPANX, favorite.spanX);
    850         values.put(Favorites.SPANY, favorite.spanY);
    851         values.put(Favorites.ICON_TYPE, favorite.iconType);
    852         if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
    853             values.put(Favorites.ICON_PACKAGE, favorite.iconPackage);
    854             values.put(Favorites.ICON_RESOURCE, favorite.iconResource);
    855         }
    856         if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
    857             values.put(Favorites.ICON, favorite.icon);
    858         }
    859         if (!TextUtils.isEmpty(favorite.title)) {
    860             values.put(Favorites.TITLE, favorite.title);
    861         } else {
    862             values.put(Favorites.TITLE, "");
    863         }
    864         if (!TextUtils.isEmpty(favorite.intent)) {
    865             values.put(Favorites.INTENT, favorite.intent);
    866         }
    867         values.put(Favorites.ITEM_TYPE, favorite.itemType);
    868 
    869         UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
    870         long userSerialNumber =
    871                 UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
    872         values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
    873 
    874         if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
    875             if (!TextUtils.isEmpty(favorite.appWidgetProvider)) {
    876                 values.put(Favorites.APPWIDGET_PROVIDER, favorite.appWidgetProvider);
    877             }
    878             values.put(Favorites.APPWIDGET_ID, favorite.appWidgetId);
    879             values.put(LauncherSettings.Favorites.RESTORED,
    880                     LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
    881                     LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY |
    882                     LauncherAppWidgetInfo.FLAG_UI_NOT_READY);
    883         } else {
    884             // Let LauncherModel know we've been here.
    885             values.put(LauncherSettings.Favorites.RESTORED, 1);
    886         }
    887 
    888         return values;
    889     }
    890 
    891     /** Serialize a Screen for persistence, including a checksum wrapper. */
    892     private byte[] packScreen(Cursor c) {
    893         Screen screen = new Screen();
    894         screen.id = c.getLong(ID_INDEX);
    895         screen.rank = c.getInt(SCREEN_RANK_INDEX);
    896 
    897         return writeCheckedBytes(screen);
    898     }
    899 
    900     /** Deserialize a Screen from persistence, after verifying checksum wrapper. */
    901     private ContentValues unpackScreen(byte[] buffer, int offset, int dataSize)
    902             throws InvalidProtocolBufferNanoException {
    903         Screen screen = new Screen();
    904         MessageNano.mergeFrom(screen, readCheckedBytes(buffer, offset, dataSize));
    905         if (VERBOSE) Log.v(TAG, "unpacked screen " + screen.id + "/" + screen.rank);
    906         ContentValues values = new ContentValues();
    907         values.put(WorkspaceScreens._ID, screen.id);
    908         values.put(WorkspaceScreens.SCREEN_RANK, screen.rank);
    909         return values;
    910     }
    911 
    912     /** Serialize an icon Resource for persistence, including a checksum wrapper. */
    913     private byte[] packIcon(int dpi, Bitmap icon) {
    914         Resource res = new Resource();
    915         res.dpi = dpi;
    916         ByteArrayOutputStream os = new ByteArrayOutputStream();
    917         if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
    918             res.data = os.toByteArray();
    919         }
    920         return writeCheckedBytes(res);
    921     }
    922 
    923     /** Deserialize an icon resource from persistence, after verifying checksum wrapper. */
    924     private static Resource unpackIcon(byte[] buffer, int offset, int dataSize)
    925             throws InvalidProtocolBufferNanoException {
    926         Resource res = new Resource();
    927         MessageNano.mergeFrom(res, readCheckedBytes(buffer, offset, dataSize));
    928         if (VERBOSE) Log.v(TAG, "unpacked icon " + res.dpi + "/" + res.data.length);
    929         return res;
    930     }
    931 
    932     /** Serialize a widget for persistence, including a checksum wrapper. */
    933     private byte[] packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache,
    934             ComponentName provider) {
    935         final AppWidgetProviderInfo info = findAppWidgetProviderInfo(provider);
    936         Widget widget = new Widget();
    937         widget.provider = provider.flattenToShortString();
    938         widget.label = info.label;
    939         widget.configure = info.configure != null;
    940         if (info.icon != 0) {
    941             widget.icon = new Resource();
    942             Drawable fullResIcon = iconCache.getFullResIcon(provider.getPackageName(), info.icon);
    943             Bitmap icon = Utilities.createIconBitmap(fullResIcon, mContext);
    944             ByteArrayOutputStream os = new ByteArrayOutputStream();
    945             if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
    946                 widget.icon.data = os.toByteArray();
    947                 widget.icon.dpi = dpi;
    948             }
    949         }
    950         if (info.previewImage != 0) {
    951             widget.preview = new Resource();
    952             Bitmap preview = previewLoader.generateWidgetPreview(info, null);
    953             ByteArrayOutputStream os = new ByteArrayOutputStream();
    954             if (preview.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
    955                 widget.preview.data = os.toByteArray();
    956                 widget.preview.dpi = dpi;
    957             }
    958         }
    959         return writeCheckedBytes(widget);
    960     }
    961 
    962     /** Deserialize a widget from persistence, after verifying checksum wrapper. */
    963     private Widget unpackWidget(byte[] buffer, int offset, int dataSize)
    964             throws InvalidProtocolBufferNanoException {
    965         Widget widget = new Widget();
    966         MessageNano.mergeFrom(widget, readCheckedBytes(buffer, offset, dataSize));
    967         if (VERBOSE) Log.v(TAG, "unpacked widget " + widget.provider);
    968         return widget;
    969     }
    970 
    971     /**
    972      * Read the old journal from the input file.
    973      *
    974      * In the event of any error, just pretend we didn't have a journal,
    975      * in that case, do a full backup.
    976      *
    977      * @param oldState the read-0only file descriptor pointing to the old journal
    978      * @return a Journal protocol buffer
    979      */
    980     private Journal readJournal(ParcelFileDescriptor oldState) {
    981         Journal journal = new Journal();
    982         if (oldState == null) {
    983             return journal;
    984         }
    985         FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor());
    986         try {
    987             int availableBytes = inStream.available();
    988             if (DEBUG) Log.d(TAG, "available " + availableBytes);
    989             if (availableBytes < MAX_JOURNAL_SIZE) {
    990                 byte[] buffer = new byte[availableBytes];
    991                 int bytesRead = 0;
    992                 boolean valid = false;
    993                 InvalidProtocolBufferNanoException lastProtoException = null;
    994                 while (availableBytes > 0) {
    995                     try {
    996                         // OMG what are you doing? This is crazy inefficient!
    997                         // If we read a byte that is not ours, we will cause trouble: b/12491813
    998                         // However, we don't know how many bytes to expect (oops).
    999                         // So we have to step through *slowly*, watching for the end.
   1000                         int result = inStream.read(buffer, bytesRead, 1);
   1001                         if (result > 0) {
   1002                             availableBytes -= result;
   1003                             bytesRead += result;
   1004                             if (DEBUG && (bytesRead % 100 == 0)) {
   1005                                 Log.d(TAG, "read some bytes: " + bytesRead);
   1006                             }
   1007                         } else {
   1008                             Log.w(TAG, "unexpected end of file while reading journal.");
   1009                             // stop reading and see what there is to parse
   1010                             availableBytes = 0;
   1011                         }
   1012                     } catch (IOException e) {
   1013                         buffer = null;
   1014                         availableBytes = 0;
   1015                     }
   1016 
   1017                     // check the buffer to see if we have a valid journal
   1018                     try {
   1019                         MessageNano.mergeFrom(journal, readCheckedBytes(buffer, 0, bytesRead));
   1020                         // if we are here, then we have read a valid, checksum-verified journal
   1021                         valid = true;
   1022                         availableBytes = 0;
   1023                         if (VERBOSE) Log.v(TAG, "read " + bytesRead + " bytes of journal");
   1024                     } catch (InvalidProtocolBufferNanoException e) {
   1025                         // if we don't have the whole journal yet, mergeFrom will throw. keep going.
   1026                         lastProtoException = e;
   1027                         journal.clear();
   1028                     }
   1029                 }
   1030                 if (DEBUG) Log.d(TAG, "journal bytes read: " + bytesRead);
   1031                 if (!valid) {
   1032                     Log.w(TAG, "could not find a valid journal", lastProtoException);
   1033                 }
   1034             }
   1035         } catch (IOException e) {
   1036             Log.w(TAG, "failed to close the journal", e);
   1037         } finally {
   1038             try {
   1039                 inStream.close();
   1040             } catch (IOException e) {
   1041                 Log.w(TAG, "failed to close the journal", e);
   1042             }
   1043         }
   1044         return journal;
   1045     }
   1046 
   1047     private void writeRowToBackup(Key key, byte[] blob, Journal out,
   1048             BackupDataOutput data) throws IOException {
   1049         String backupKey = keyToBackupKey(key);
   1050         data.writeEntityHeader(backupKey, blob.length);
   1051         data.writeEntityData(blob, blob.length);
   1052         out.rows++;
   1053         out.bytes += blob.length;
   1054         if (VERBOSE) Log.v(TAG, "saving " + geKeyType(key) + " " + backupKey + ": " +
   1055                 getKeyName(key) + "/" + blob.length);
   1056         if(DEBUG_PAYLOAD) {
   1057             String encoded = Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP);
   1058             final int chunkSize = 1024;
   1059             for (int offset = 0; offset < encoded.length(); offset += chunkSize) {
   1060                 int end = offset + chunkSize;
   1061                 end = Math.min(end, encoded.length());
   1062                 Log.w(TAG, "wrote " + encoded.substring(offset, end));
   1063             }
   1064         }
   1065     }
   1066 
   1067     private Set<String> getSavedIdsByType(int type, Journal in) {
   1068         Set<String> savedIds = new HashSet<String>();
   1069         for(int i = 0; i < in.key.length; i++) {
   1070             Key key = in.key[i];
   1071             if (key.type == type) {
   1072                 savedIds.add(keyToBackupKey(key));
   1073             }
   1074         }
   1075         return savedIds;
   1076     }
   1077 
   1078     private int removeDeletedKeysFromBackup(Set<String> deletedIds, BackupDataOutput data)
   1079             throws IOException {
   1080         int rows = 0;
   1081         for(String deleted: deletedIds) {
   1082             if (VERBOSE) Log.v(TAG, "dropping deleted item " + deleted);
   1083             data.writeEntityHeader(deleted, -1);
   1084             rows++;
   1085         }
   1086         return rows;
   1087     }
   1088 
   1089     /**
   1090      * Write the new journal to the output file.
   1091      *
   1092      * In the event of any error, just pretend we didn't have a journal,
   1093      * in that case, do a full backup.
   1094 
   1095      * @param newState the write-only file descriptor pointing to the new journal
   1096      * @param journal a Journal protocol buffer
   1097      */
   1098     private void writeJournal(ParcelFileDescriptor newState, Journal journal) {
   1099         FileOutputStream outStream = null;
   1100         try {
   1101             outStream = new FileOutputStream(newState.getFileDescriptor());
   1102             final byte[] journalBytes = writeCheckedBytes(journal);
   1103             outStream.write(journalBytes);
   1104             outStream.close();
   1105             if (VERBOSE) Log.v(TAG, "wrote " + journalBytes.length + " bytes of journal");
   1106         } catch (IOException e) {
   1107             Log.w(TAG, "failed to write backup journal", e);
   1108         }
   1109     }
   1110 
   1111     /** Wrap a proto in a CheckedMessage and compute the checksum. */
   1112     private byte[] writeCheckedBytes(MessageNano proto) {
   1113         CheckedMessage wrapper = new CheckedMessage();
   1114         wrapper.payload = MessageNano.toByteArray(proto);
   1115         CRC32 checksum = new CRC32();
   1116         checksum.update(wrapper.payload);
   1117         wrapper.checksum = checksum.getValue();
   1118         return MessageNano.toByteArray(wrapper);
   1119     }
   1120 
   1121     /** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
   1122     private static byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
   1123             throws InvalidProtocolBufferNanoException {
   1124         CheckedMessage wrapper = new CheckedMessage();
   1125         MessageNano.mergeFrom(wrapper, buffer, offset, dataSize);
   1126         CRC32 checksum = new CRC32();
   1127         checksum.update(wrapper.payload);
   1128         if (wrapper.checksum != checksum.getValue()) {
   1129             throw new InvalidProtocolBufferNanoException("checksum does not match");
   1130         }
   1131         return wrapper.payload;
   1132     }
   1133 
   1134     private AppWidgetProviderInfo findAppWidgetProviderInfo(ComponentName component) {
   1135         if (mWidgetMap == null) {
   1136             List<AppWidgetProviderInfo> widgets =
   1137                     AppWidgetManager.getInstance(mContext).getInstalledProviders();
   1138             mWidgetMap = new HashMap<ComponentName, AppWidgetProviderInfo>(widgets.size());
   1139             for (AppWidgetProviderInfo info : widgets) {
   1140                 mWidgetMap.put(info.provider, info);
   1141             }
   1142         }
   1143         return mWidgetMap.get(component);
   1144     }
   1145 
   1146 
   1147     private boolean initializeIconCache() {
   1148         if (mIconCache != null) {
   1149             return true;
   1150         }
   1151 
   1152         final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
   1153         if (appState == null) {
   1154             Throwable stackTrace = new Throwable();
   1155             stackTrace.fillInStackTrace();
   1156             Log.w(TAG, "Failed to get app state during backup/restore", stackTrace);
   1157             return false;
   1158         }
   1159         mIconCache = appState.getIconCache();
   1160         return mIconCache != null;
   1161     }
   1162 
   1163 
   1164    // check if the launcher is in a state to support backup
   1165     private boolean launcherIsReady() {
   1166         ContentResolver cr = mContext.getContentResolver();
   1167         Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION, null, null, null);
   1168         if (cursor == null) {
   1169             // launcher data has been wiped, do nothing
   1170             return false;
   1171         }
   1172         cursor.close();
   1173 
   1174         if (!initializeIconCache()) {
   1175             // launcher services are unavailable, try again later
   1176             dataChanged();
   1177             return false;
   1178         }
   1179 
   1180         return true;
   1181     }
   1182 
   1183     private String getUserSelectionArg() {
   1184         return Favorites.PROFILE_ID + '=' + UserManagerCompat.getInstance(mContext)
   1185                 .getSerialNumberForUser(UserHandleCompat.myUserHandle());
   1186     }
   1187 
   1188     private class KeyParsingException extends Throwable {
   1189         private KeyParsingException(Throwable cause) {
   1190             super(cause);
   1191         }
   1192 
   1193         public KeyParsingException(String reason) {
   1194             super(reason);
   1195         }
   1196     }
   1197 }
   1198