LocalTransport.java revision d5f70b748351eb82a5f3567e2a1edea2de458951
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.internal.backup; 18 19import android.app.backup.BackupDataInput; 20import android.app.backup.BackupDataOutput; 21import android.app.backup.BackupTransport; 22import android.app.backup.RestoreDescription; 23import android.app.backup.RestoreSet; 24import android.content.ComponentName; 25import android.content.Context; 26import android.content.Intent; 27import android.content.pm.PackageInfo; 28import android.os.Environment; 29import android.os.ParcelFileDescriptor; 30import android.system.ErrnoException; 31import android.system.Os; 32import android.system.StructStat; 33import android.util.Log; 34 35import com.android.org.bouncycastle.util.encoders.Base64; 36 37import libcore.io.IoUtils; 38 39import java.io.BufferedOutputStream; 40import java.io.File; 41import java.io.FileInputStream; 42import java.io.FileNotFoundException; 43import java.io.FileOutputStream; 44import java.io.IOException; 45import java.util.ArrayList; 46import java.util.Collections; 47import static android.system.OsConstants.SEEK_CUR; 48 49/** 50 * Backup transport for stashing stuff into a known location on disk, and 51 * later restoring from there. For testing only. 52 */ 53 54public class LocalTransport extends BackupTransport { 55 private static final String TAG = "LocalTransport"; 56 private static final boolean DEBUG = false; 57 58 private static final String TRANSPORT_DIR_NAME 59 = "com.android.internal.backup.LocalTransport"; 60 61 private static final String TRANSPORT_DESTINATION_STRING 62 = "Backing up to debug-only private cache"; 63 64 private static final String TRANSPORT_DATA_MANAGEMENT_LABEL 65 = ""; 66 67 private static final String INCREMENTAL_DIR = "_delta"; 68 private static final String FULL_DATA_DIR = "_full"; 69 70 // The currently-active restore set always has the same (nonzero!) token 71 private static final long CURRENT_SET_TOKEN = 1; 72 73 // Full backup size quota is set to reasonable value. 74 private static final long FULL_BACKUP_SIZE_QUOTA = 25 * 1024 * 1024; 75 76 private Context mContext; 77 private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup"); 78 private File mCurrentSetDir = new File(mDataDir, Long.toString(CURRENT_SET_TOKEN)); 79 private File mCurrentSetIncrementalDir = new File(mCurrentSetDir, INCREMENTAL_DIR); 80 private File mCurrentSetFullDir = new File(mCurrentSetDir, FULL_DATA_DIR); 81 82 private PackageInfo[] mRestorePackages = null; 83 private int mRestorePackage = -1; // Index into mRestorePackages 84 private int mRestoreType; 85 private File mRestoreSetDir; 86 private File mRestoreSetIncrementalDir; 87 private File mRestoreSetFullDir; 88 89 // Additional bookkeeping for full backup 90 private String mFullTargetPackage; 91 private ParcelFileDescriptor mSocket; 92 private FileInputStream mSocketInputStream; 93 private BufferedOutputStream mFullBackupOutputStream; 94 private byte[] mFullBackupBuffer; 95 private long mFullBackupSize; 96 97 private FileInputStream mCurFullRestoreStream; 98 private FileOutputStream mFullRestoreSocketStream; 99 private byte[] mFullRestoreBuffer; 100 101 private void makeDataDirs() { 102 mCurrentSetDir.mkdirs(); 103 mCurrentSetFullDir.mkdir(); 104 mCurrentSetIncrementalDir.mkdir(); 105 } 106 107 public LocalTransport(Context context) { 108 mContext = context; 109 makeDataDirs(); 110 } 111 112 @Override 113 public String name() { 114 return new ComponentName(mContext, this.getClass()).flattenToShortString(); 115 } 116 117 @Override 118 public Intent configurationIntent() { 119 // The local transport is not user-configurable 120 return null; 121 } 122 123 @Override 124 public String currentDestinationString() { 125 return TRANSPORT_DESTINATION_STRING; 126 } 127 128 public Intent dataManagementIntent() { 129 // The local transport does not present a data-management UI 130 // TODO: consider adding simple UI to wipe the archives entirely, 131 // for cleaning up the cache partition. 132 return null; 133 } 134 135 public String dataManagementLabel() { 136 return TRANSPORT_DATA_MANAGEMENT_LABEL; 137 } 138 139 @Override 140 public String transportDirName() { 141 return TRANSPORT_DIR_NAME; 142 } 143 144 @Override 145 public long requestBackupTime() { 146 // any time is a good time for local backup 147 return 0; 148 } 149 150 @Override 151 public int initializeDevice() { 152 if (DEBUG) Log.v(TAG, "wiping all data"); 153 deleteContents(mCurrentSetDir); 154 makeDataDirs(); 155 return TRANSPORT_OK; 156 } 157 158 @Override 159 public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) { 160 if (DEBUG) { 161 try { 162 StructStat ss = Os.fstat(data.getFileDescriptor()); 163 Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName 164 + " size=" + ss.st_size); 165 } catch (ErrnoException e) { 166 Log.w(TAG, "Unable to stat input file in performBackup() on " 167 + packageInfo.packageName); 168 } 169 } 170 171 File packageDir = new File(mCurrentSetIncrementalDir, packageInfo.packageName); 172 packageDir.mkdirs(); 173 174 // Each 'record' in the restore set is kept in its own file, named by 175 // the record key. Wind through the data file, extracting individual 176 // record operations and building a set of all the updates to apply 177 // in this update. 178 BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor()); 179 try { 180 int bufSize = 512; 181 byte[] buf = new byte[bufSize]; 182 while (changeSet.readNextHeader()) { 183 String key = changeSet.getKey(); 184 String base64Key = new String(Base64.encode(key.getBytes())); 185 File entityFile = new File(packageDir, base64Key); 186 187 int dataSize = changeSet.getDataSize(); 188 189 if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize 190 + " key64=" + base64Key); 191 192 if (dataSize >= 0) { 193 if (entityFile.exists()) { 194 entityFile.delete(); 195 } 196 FileOutputStream entity = new FileOutputStream(entityFile); 197 198 if (dataSize > bufSize) { 199 bufSize = dataSize; 200 buf = new byte[bufSize]; 201 } 202 changeSet.readEntityData(buf, 0, dataSize); 203 if (DEBUG) { 204 try { 205 long cur = Os.lseek(data.getFileDescriptor(), 0, SEEK_CUR); 206 Log.v(TAG, " read entity data; new pos=" + cur); 207 } 208 catch (ErrnoException e) { 209 Log.w(TAG, "Unable to stat input file in performBackup() on " 210 + packageInfo.packageName); 211 } 212 } 213 214 try { 215 entity.write(buf, 0, dataSize); 216 } catch (IOException e) { 217 Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath()); 218 return TRANSPORT_ERROR; 219 } finally { 220 entity.close(); 221 } 222 } else { 223 entityFile.delete(); 224 } 225 } 226 return TRANSPORT_OK; 227 } catch (IOException e) { 228 // oops, something went wrong. abort the operation and return error. 229 Log.v(TAG, "Exception reading backup input:", e); 230 return TRANSPORT_ERROR; 231 } 232 } 233 234 // Deletes the contents but not the given directory 235 private void deleteContents(File dirname) { 236 File[] contents = dirname.listFiles(); 237 if (contents != null) { 238 for (File f : contents) { 239 if (f.isDirectory()) { 240 // delete the directory's contents then fall through 241 // and delete the directory itself. 242 deleteContents(f); 243 } 244 f.delete(); 245 } 246 } 247 } 248 249 @Override 250 public int clearBackupData(PackageInfo packageInfo) { 251 if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName); 252 253 File packageDir = new File(mCurrentSetIncrementalDir, packageInfo.packageName); 254 final File[] fileset = packageDir.listFiles(); 255 if (fileset != null) { 256 for (File f : fileset) { 257 f.delete(); 258 } 259 packageDir.delete(); 260 } 261 262 packageDir = new File(mCurrentSetFullDir, packageInfo.packageName); 263 final File[] tarballs = packageDir.listFiles(); 264 if (tarballs != null) { 265 for (File f : tarballs) { 266 f.delete(); 267 } 268 packageDir.delete(); 269 } 270 271 return TRANSPORT_OK; 272 } 273 274 @Override 275 public int finishBackup() { 276 if (DEBUG) Log.v(TAG, "finishBackup() of " + mFullTargetPackage); 277 return tearDownFullBackup(); 278 } 279 280 // ------------------------------------------------------------------------------------ 281 // Full backup handling 282 283 private int tearDownFullBackup() { 284 if (mSocket != null) { 285 try { 286 if (mFullBackupOutputStream != null) { 287 mFullBackupOutputStream.flush(); 288 mFullBackupOutputStream.close(); 289 } 290 mSocketInputStream = null; 291 mFullTargetPackage = null; 292 mSocket.close(); 293 } catch (IOException e) { 294 if (DEBUG) { 295 Log.w(TAG, "Exception caught in tearDownFullBackup()", e); 296 } 297 return TRANSPORT_ERROR; 298 } finally { 299 mSocket = null; 300 mFullBackupOutputStream = null; 301 } 302 } 303 return TRANSPORT_OK; 304 } 305 306 private File tarballFile(String pkgName) { 307 return new File(mCurrentSetFullDir, pkgName); 308 } 309 310 @Override 311 public long requestFullBackupTime() { 312 return 0; 313 } 314 315 @Override 316 public int checkFullBackupSize(long size) { 317 int result = TRANSPORT_OK; 318 // Decline zero-size "backups" 319 if (size <= 0) { 320 result = TRANSPORT_PACKAGE_REJECTED; 321 } else if (size > FULL_BACKUP_SIZE_QUOTA) { 322 result = TRANSPORT_QUOTA_EXCEEDED; 323 } 324 if (result != TRANSPORT_OK) { 325 if (DEBUG) { 326 Log.v(TAG, "Declining backup of size " + size); 327 } 328 } 329 return result; 330 } 331 332 @Override 333 public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) { 334 if (mSocket != null) { 335 Log.e(TAG, "Attempt to initiate full backup while one is in progress"); 336 return TRANSPORT_ERROR; 337 } 338 339 if (DEBUG) { 340 Log.i(TAG, "performFullBackup : " + targetPackage); 341 } 342 343 // We know a priori that we run in the system process, so we need to make 344 // sure to dup() our own copy of the socket fd. Transports which run in 345 // their own processes must not do this. 346 try { 347 mFullBackupSize = 0; 348 mSocket = ParcelFileDescriptor.dup(socket.getFileDescriptor()); 349 mSocketInputStream = new FileInputStream(mSocket.getFileDescriptor()); 350 } catch (IOException e) { 351 Log.e(TAG, "Unable to process socket for full backup"); 352 return TRANSPORT_ERROR; 353 } 354 355 mFullTargetPackage = targetPackage.packageName; 356 mFullBackupBuffer = new byte[4096]; 357 358 return TRANSPORT_OK; 359 } 360 361 @Override 362 public int sendBackupData(final int numBytes) { 363 if (mSocket == null) { 364 Log.w(TAG, "Attempted sendBackupData before performFullBackup"); 365 return TRANSPORT_ERROR; 366 } 367 368 mFullBackupSize += numBytes; 369 if (mFullBackupSize > FULL_BACKUP_SIZE_QUOTA) { 370 return TRANSPORT_QUOTA_EXCEEDED; 371 } 372 373 if (numBytes > mFullBackupBuffer.length) { 374 mFullBackupBuffer = new byte[numBytes]; 375 } 376 377 if (mFullBackupOutputStream == null) { 378 FileOutputStream tarstream; 379 try { 380 File tarball = tarballFile(mFullTargetPackage); 381 tarstream = new FileOutputStream(tarball); 382 } catch (FileNotFoundException e) { 383 return TRANSPORT_ERROR; 384 } 385 mFullBackupOutputStream = new BufferedOutputStream(tarstream); 386 } 387 388 int bytesLeft = numBytes; 389 while (bytesLeft > 0) { 390 try { 391 int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft); 392 if (nRead < 0) { 393 // Something went wrong if we expect data but saw EOD 394 Log.w(TAG, "Unexpected EOD; failing backup"); 395 return TRANSPORT_ERROR; 396 } 397 mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead); 398 bytesLeft -= nRead; 399 } catch (IOException e) { 400 Log.e(TAG, "Error handling backup data for " + mFullTargetPackage); 401 return TRANSPORT_ERROR; 402 } 403 } 404 if (DEBUG) { 405 Log.v(TAG, " stored " + numBytes + " of data"); 406 } 407 return TRANSPORT_OK; 408 } 409 410 // For now we can't roll back, so just tear everything down. 411 @Override 412 public void cancelFullBackup() { 413 if (DEBUG) { 414 Log.i(TAG, "Canceling full backup of " + mFullTargetPackage); 415 } 416 File archive = tarballFile(mFullTargetPackage); 417 tearDownFullBackup(); 418 if (archive.exists()) { 419 archive.delete(); 420 } 421 } 422 423 // ------------------------------------------------------------------------------------ 424 // Restore handling 425 static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 }; 426 427 @Override 428 public RestoreSet[] getAvailableRestoreSets() { 429 long[] existing = new long[POSSIBLE_SETS.length + 1]; 430 int num = 0; 431 432 // see which possible non-current sets exist... 433 for (long token : POSSIBLE_SETS) { 434 if ((new File(mDataDir, Long.toString(token))).exists()) { 435 existing[num++] = token; 436 } 437 } 438 // ...and always the currently-active set last 439 existing[num++] = CURRENT_SET_TOKEN; 440 441 RestoreSet[] available = new RestoreSet[num]; 442 for (int i = 0; i < available.length; i++) { 443 available[i] = new RestoreSet("Local disk image", "flash", existing[i]); 444 } 445 return available; 446 } 447 448 @Override 449 public long getCurrentRestoreSet() { 450 // The current restore set always has the same token 451 return CURRENT_SET_TOKEN; 452 } 453 454 @Override 455 public int startRestore(long token, PackageInfo[] packages) { 456 if (DEBUG) Log.v(TAG, "start restore " + token + " : " + packages.length 457 + " matching packages"); 458 mRestorePackages = packages; 459 mRestorePackage = -1; 460 mRestoreSetDir = new File(mDataDir, Long.toString(token)); 461 mRestoreSetIncrementalDir = new File(mRestoreSetDir, INCREMENTAL_DIR); 462 mRestoreSetFullDir = new File(mRestoreSetDir, FULL_DATA_DIR); 463 return TRANSPORT_OK; 464 } 465 466 @Override 467 public RestoreDescription nextRestorePackage() { 468 if (DEBUG) { 469 Log.v(TAG, "nextRestorePackage() : mRestorePackage=" + mRestorePackage 470 + " length=" + mRestorePackages.length); 471 } 472 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called"); 473 474 boolean found = false; 475 while (++mRestorePackage < mRestorePackages.length) { 476 String name = mRestorePackages[mRestorePackage].packageName; 477 478 // If we have key/value data for this package, deliver that 479 // skip packages where we have a data dir but no actual contents 480 String[] contents = (new File(mRestoreSetIncrementalDir, name)).list(); 481 if (contents != null && contents.length > 0) { 482 if (DEBUG) { 483 Log.v(TAG, " nextRestorePackage(TYPE_KEY_VALUE) @ " 484 + mRestorePackage + " = " + name); 485 } 486 mRestoreType = RestoreDescription.TYPE_KEY_VALUE; 487 found = true; 488 } 489 490 if (!found) { 491 // No key/value data; check for [non-empty] full data 492 File maybeFullData = new File(mRestoreSetFullDir, name); 493 if (maybeFullData.length() > 0) { 494 if (DEBUG) { 495 Log.v(TAG, " nextRestorePackage(TYPE_FULL_STREAM) @ " 496 + mRestorePackage + " = " + name); 497 } 498 mRestoreType = RestoreDescription.TYPE_FULL_STREAM; 499 mCurFullRestoreStream = null; // ensure starting from the ground state 500 found = true; 501 } 502 } 503 504 if (found) { 505 return new RestoreDescription(name, mRestoreType); 506 } 507 508 if (DEBUG) { 509 Log.v(TAG, " ... package @ " + mRestorePackage + " = " + name 510 + " has no data; skipping"); 511 } 512 } 513 514 if (DEBUG) Log.v(TAG, " no more packages to restore"); 515 return RestoreDescription.NO_MORE_PACKAGES; 516 } 517 518 @Override 519 public int getRestoreData(ParcelFileDescriptor outFd) { 520 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called"); 521 if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called"); 522 if (mRestoreType != RestoreDescription.TYPE_KEY_VALUE) { 523 throw new IllegalStateException("getRestoreData(fd) for non-key/value dataset"); 524 } 525 File packageDir = new File(mRestoreSetIncrementalDir, 526 mRestorePackages[mRestorePackage].packageName); 527 528 // The restore set is the concatenation of the individual record blobs, 529 // each of which is a file in the package's directory. We return the 530 // data in lexical order sorted by key, so that apps which use synthetic 531 // keys like BLOB_1, BLOB_2, etc will see the date in the most obvious 532 // order. 533 ArrayList<DecodedFilename> blobs = contentsByKey(packageDir); 534 if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error 535 Log.e(TAG, "No keys for package: " + packageDir); 536 return TRANSPORT_ERROR; 537 } 538 539 // We expect at least some data if the directory exists in the first place 540 if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.size() + " key files"); 541 BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor()); 542 try { 543 for (DecodedFilename keyEntry : blobs) { 544 File f = keyEntry.file; 545 FileInputStream in = new FileInputStream(f); 546 try { 547 int size = (int) f.length(); 548 byte[] buf = new byte[size]; 549 in.read(buf); 550 if (DEBUG) Log.v(TAG, " ... key=" + keyEntry.key + " size=" + size); 551 out.writeEntityHeader(keyEntry.key, size); 552 out.writeEntityData(buf, size); 553 } finally { 554 in.close(); 555 } 556 } 557 return TRANSPORT_OK; 558 } catch (IOException e) { 559 Log.e(TAG, "Unable to read backup records", e); 560 return TRANSPORT_ERROR; 561 } 562 } 563 564 static class DecodedFilename implements Comparable<DecodedFilename> { 565 public File file; 566 public String key; 567 568 public DecodedFilename(File f) { 569 file = f; 570 key = new String(Base64.decode(f.getName())); 571 } 572 573 @Override 574 public int compareTo(DecodedFilename other) { 575 // sorts into ascending lexical order by decoded key 576 return key.compareTo(other.key); 577 } 578 } 579 580 // Return a list of the files in the given directory, sorted lexically by 581 // the Base64-decoded file name, not by the on-disk filename 582 private ArrayList<DecodedFilename> contentsByKey(File dir) { 583 File[] allFiles = dir.listFiles(); 584 if (allFiles == null || allFiles.length == 0) { 585 return null; 586 } 587 588 // Decode the filenames into keys then sort lexically by key 589 ArrayList<DecodedFilename> contents = new ArrayList<DecodedFilename>(); 590 for (File f : allFiles) { 591 contents.add(new DecodedFilename(f)); 592 } 593 Collections.sort(contents); 594 return contents; 595 } 596 597 @Override 598 public void finishRestore() { 599 if (DEBUG) Log.v(TAG, "finishRestore()"); 600 if (mRestoreType == RestoreDescription.TYPE_FULL_STREAM) { 601 resetFullRestoreState(); 602 } 603 mRestoreType = 0; 604 } 605 606 // ------------------------------------------------------------------------------------ 607 // Full restore handling 608 609 private void resetFullRestoreState() { 610 IoUtils.closeQuietly(mCurFullRestoreStream); 611 mCurFullRestoreStream = null; 612 mFullRestoreSocketStream = null; 613 mFullRestoreBuffer = null; 614 } 615 616 /** 617 * Ask the transport to provide data for the "current" package being restored. The 618 * transport then writes some data to the socket supplied to this call, and returns 619 * the number of bytes written. The system will then read that many bytes and 620 * stream them to the application's agent for restore, then will call this method again 621 * to receive the next chunk of the archive. This sequence will be repeated until the 622 * transport returns zero indicating that all of the package's data has been delivered 623 * (or returns a negative value indicating some sort of hard error condition at the 624 * transport level). 625 * 626 * <p>After this method returns zero, the system will then call 627 * {@link #getNextFullRestorePackage()} to begin the restore process for the next 628 * application, and the sequence begins again. 629 * 630 * @param socket The file descriptor that the transport will use for delivering the 631 * streamed archive. 632 * @return 0 when no more data for the current package is available. A positive value 633 * indicates the presence of that much data to be delivered to the app. A negative 634 * return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR}, 635 * indicating a fatal error condition that precludes further restore operations 636 * on the current dataset. 637 */ 638 @Override 639 public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) { 640 if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) { 641 throw new IllegalStateException("Asked for full restore data for non-stream package"); 642 } 643 644 // first chunk? 645 if (mCurFullRestoreStream == null) { 646 final String name = mRestorePackages[mRestorePackage].packageName; 647 if (DEBUG) Log.i(TAG, "Starting full restore of " + name); 648 File dataset = new File(mRestoreSetFullDir, name); 649 try { 650 mCurFullRestoreStream = new FileInputStream(dataset); 651 } catch (IOException e) { 652 // If we can't open the target package's tarball, we return the single-package 653 // error code and let the caller go on to the next package. 654 Log.e(TAG, "Unable to read archive for " + name); 655 return TRANSPORT_PACKAGE_REJECTED; 656 } 657 mFullRestoreSocketStream = new FileOutputStream(socket.getFileDescriptor()); 658 mFullRestoreBuffer = new byte[2*1024]; 659 } 660 661 int nRead; 662 try { 663 nRead = mCurFullRestoreStream.read(mFullRestoreBuffer); 664 if (nRead < 0) { 665 // EOF: tell the caller we're done 666 nRead = NO_MORE_DATA; 667 } else if (nRead == 0) { 668 // This shouldn't happen when reading a FileInputStream; we should always 669 // get either a positive nonzero byte count or -1. Log the situation and 670 // treat it as EOF. 671 Log.w(TAG, "read() of archive file returned 0; treating as EOF"); 672 nRead = NO_MORE_DATA; 673 } else { 674 if (DEBUG) { 675 Log.i(TAG, " delivering restore chunk: " + nRead); 676 } 677 mFullRestoreSocketStream.write(mFullRestoreBuffer, 0, nRead); 678 } 679 } catch (IOException e) { 680 return TRANSPORT_ERROR; // Hard error accessing the file; shouldn't happen 681 } finally { 682 // Most transports will need to explicitly close 'socket' here, but this transport 683 // is in the same process as the caller so it can leave it up to the backup manager 684 // to manage both socket fds. 685 } 686 687 return nRead; 688 } 689 690 /** 691 * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM} 692 * data for restore, it will invoke this method to tell the transport that it should 693 * abandon the data download for the current package. The OS will then either call 694 * {@link #nextRestorePackage()} again to move on to restoring the next package in the 695 * set being iterated over, or will call {@link #finishRestore()} to shut down the restore 696 * operation. 697 * 698 * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the 699 * current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious 700 * transport-level failure. If the transport reports an error here, the entire restore 701 * operation will immediately be finished with no further attempts to restore app data. 702 */ 703 @Override 704 public int abortFullRestore() { 705 if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) { 706 throw new IllegalStateException("abortFullRestore() but not currently restoring"); 707 } 708 resetFullRestoreState(); 709 mRestoreType = 0; 710 return TRANSPORT_OK; 711 } 712 713 @Override 714 public long getBackupQuota(String packageName, boolean isFullBackup) { 715 return isFullBackup ? FULL_BACKUP_SIZE_QUOTA : Long.MAX_VALUE; 716 } 717} 718