OSDN Git Service

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