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