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