Bmgr.java revision 865303fce57b968f5bd7efb4dd23ccb1a3747b93
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.commands.bmgr; 18 19import android.app.backup.BackupManager; 20import android.app.backup.BackupProgress; 21import android.app.backup.IBackupManager; 22import android.app.backup.IBackupObserver; 23import android.app.backup.IRestoreObserver; 24import android.app.backup.IRestoreSession; 25import android.app.backup.RestoreSet; 26import android.app.backup.ISelectBackupTransportCallback; 27import android.content.ComponentName; 28import android.content.pm.IPackageManager; 29import android.content.pm.PackageInfo; 30import android.os.RemoteException; 31import android.os.ServiceManager; 32import android.os.UserHandle; 33import android.util.Log; 34 35import java.util.ArrayList; 36import java.util.HashSet; 37import java.util.List; 38import java.util.concurrent.CountDownLatch; 39 40public final class Bmgr { 41 IBackupManager mBmgr; 42 IRestoreSession mRestore; 43 44 static final String BMGR_NOT_RUNNING_ERR = 45 "Error: Could not access the Backup Manager. Is the system running?"; 46 static final String TRANSPORT_NOT_RUNNING_ERR = 47 "Error: Could not access the backup transport. Is the system running?"; 48 static final String PM_NOT_RUNNING_ERR = 49 "Error: Could not access the Package Manager. Is the system running?"; 50 51 private String[] mArgs; 52 private int mNextArg; 53 54 public static void main(String[] args) { 55 try { 56 new Bmgr().run(args); 57 } catch (Exception e) { 58 System.err.println("Exception caught:"); 59 e.printStackTrace(); 60 } 61 } 62 63 public void run(String[] args) { 64 if (args.length < 1) { 65 showUsage(); 66 return; 67 } 68 69 mBmgr = IBackupManager.Stub.asInterface(ServiceManager.getService("backup")); 70 if (mBmgr == null) { 71 System.err.println(BMGR_NOT_RUNNING_ERR); 72 return; 73 } 74 75 mArgs = args; 76 String op = args[0]; 77 mNextArg = 1; 78 79 if ("enabled".equals(op)) { 80 doEnabled(); 81 return; 82 } 83 84 if ("enable".equals(op)) { 85 doEnable(); 86 return; 87 } 88 89 if ("run".equals(op)) { 90 doRun(); 91 return; 92 } 93 94 if ("backup".equals(op)) { 95 doBackup(); 96 return; 97 } 98 99 if ("list".equals(op)) { 100 doList(); 101 return; 102 } 103 104 if ("restore".equals(op)) { 105 doRestore(); 106 return; 107 } 108 109 if ("transport".equals(op)) { 110 doTransport(); 111 return; 112 } 113 114 if ("wipe".equals(op)) { 115 doWipe(); 116 return; 117 } 118 119 if ("fullbackup".equals(op)) { 120 doFullTransportBackup(); 121 return; 122 } 123 124 if ("backupnow".equals(op)) { 125 doBackupNow(); 126 return; 127 } 128 129 if ("whitelist".equals(op)) { 130 doPrintWhitelist(); 131 return; 132 } 133 134 System.err.println("Unknown command"); 135 showUsage(); 136 } 137 138 private String enableToString(boolean enabled) { 139 return enabled ? "enabled" : "disabled"; 140 } 141 142 private void doEnabled() { 143 try { 144 boolean isEnabled = mBmgr.isBackupEnabled(); 145 System.out.println("Backup Manager currently " 146 + enableToString(isEnabled)); 147 } catch (RemoteException e) { 148 System.err.println(e.toString()); 149 System.err.println(BMGR_NOT_RUNNING_ERR); 150 } 151 } 152 153 private void doEnable() { 154 String arg = nextArg(); 155 if (arg == null) { 156 showUsage(); 157 return; 158 } 159 160 try { 161 boolean enable = Boolean.parseBoolean(arg); 162 mBmgr.setBackupEnabled(enable); 163 System.out.println("Backup Manager now " + enableToString(enable)); 164 } catch (NumberFormatException e) { 165 showUsage(); 166 return; 167 } catch (RemoteException e) { 168 System.err.println(e.toString()); 169 System.err.println(BMGR_NOT_RUNNING_ERR); 170 } 171 } 172 173 private void doRun() { 174 try { 175 mBmgr.backupNow(); 176 } catch (RemoteException e) { 177 System.err.println(e.toString()); 178 System.err.println(BMGR_NOT_RUNNING_ERR); 179 } 180 } 181 182 private void doBackup() { 183 String pkg = nextArg(); 184 if (pkg == null) { 185 showUsage(); 186 return; 187 } 188 189 try { 190 mBmgr.dataChanged(pkg); 191 } catch (RemoteException e) { 192 System.err.println(e.toString()); 193 System.err.println(BMGR_NOT_RUNNING_ERR); 194 } 195 } 196 197 private void doFullTransportBackup() { 198 System.out.println("Performing full transport backup"); 199 200 String pkg; 201 ArrayList<String> allPkgs = new ArrayList<String>(); 202 while ((pkg = nextArg()) != null) { 203 allPkgs.add(pkg); 204 } 205 if (allPkgs.size() > 0) { 206 try { 207 mBmgr.fullTransportBackup(allPkgs.toArray(new String[allPkgs.size()])); 208 } catch (RemoteException e) { 209 System.err.println(e.toString()); 210 System.err.println(BMGR_NOT_RUNNING_ERR); 211 } 212 } 213 } 214 215 class BackupObserver extends IBackupObserver.Stub { 216 boolean done = false; 217 218 @Override 219 public void onUpdate(String currentPackage, BackupProgress backupProgress) { 220 System.out.println( 221 "Package " + currentPackage + " with progress: " + backupProgress.bytesTransferred 222 + "/" + backupProgress.bytesExpected); 223 } 224 225 @Override 226 public void onResult(String currentPackage, int status) { 227 System.out.println("Package " + currentPackage + " with result: " 228 + convertBackupStatusToString(status)); 229 } 230 231 @Override 232 public void backupFinished(int status) { 233 System.out.println("Backup finished with result: " 234 + convertBackupStatusToString(status)); 235 synchronized (this) { 236 done = true; 237 this.notify(); 238 } 239 } 240 241 public void waitForCompletion() { 242 // The backupFinished() callback will throw the 'done' flag; we 243 // just sit and wait on that notification. 244 synchronized (this) { 245 while (!this.done) { 246 try { 247 this.wait(); 248 } catch (InterruptedException ex) { 249 } 250 } 251 } 252 } 253 254 } 255 256 private static String convertBackupStatusToString(int errorCode) { 257 switch (errorCode) { 258 case BackupManager.SUCCESS: 259 return "Success"; 260 case BackupManager.ERROR_BACKUP_NOT_ALLOWED: 261 return "Backup is not allowed"; 262 case BackupManager.ERROR_PACKAGE_NOT_FOUND: 263 return "Package not found"; 264 case BackupManager.ERROR_TRANSPORT_ABORTED: 265 return "Transport error"; 266 case BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED: 267 return "Transport rejected package"; 268 case BackupManager.ERROR_AGENT_FAILURE: 269 return "Agent error"; 270 case BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED: 271 return "Size quota exceeded"; 272 default: 273 return "Unknown error"; 274 } 275 } 276 277 private void backupNowAllPackages(boolean nonIncrementalBackup) { 278 int userId = UserHandle.USER_SYSTEM; 279 IPackageManager mPm = 280 IPackageManager.Stub.asInterface(ServiceManager.getService("package")); 281 if (mPm == null) { 282 System.err.println(PM_NOT_RUNNING_ERR); 283 return; 284 } 285 List<PackageInfo> installedPackages = null; 286 try { 287 installedPackages = mPm.getInstalledPackages(0, userId).getList(); 288 } catch (RemoteException e) { 289 System.err.println(e.toString()); 290 System.err.println(PM_NOT_RUNNING_ERR); 291 } 292 if (installedPackages != null) { 293 List<String> packages = new ArrayList<>(); 294 for (PackageInfo pi : installedPackages) { 295 try { 296 if (mBmgr.isAppEligibleForBackup(pi.packageName)) { 297 packages.add(pi.packageName); 298 } 299 } catch (RemoteException e) { 300 System.err.println(e.toString()); 301 System.err.println(BMGR_NOT_RUNNING_ERR); 302 } 303 } 304 backupNowPackages(packages, nonIncrementalBackup); 305 } 306 } 307 308 private void backupNowPackages(List<String> packages, boolean nonIncrementalBackup) { 309 int flags = 0; 310 if (nonIncrementalBackup) { 311 flags |= BackupManager.FLAG_NON_INCREMENTAL_BACKUP; 312 } 313 try { 314 BackupObserver observer = new BackupObserver(); 315 int err = mBmgr.requestBackup(packages.toArray(new String[packages.size()]), observer, 316 flags); 317 if (err == 0) { 318 // Off and running -- wait for the backup to complete 319 observer.waitForCompletion(); 320 } else { 321 System.err.println("Unable to run backup"); 322 } 323 } catch (RemoteException e) { 324 System.err.println(e.toString()); 325 System.err.println(BMGR_NOT_RUNNING_ERR); 326 } 327 } 328 329 private void doBackupNow() { 330 String pkg; 331 boolean backupAll = false; 332 boolean nonIncrementalBackup = false; 333 ArrayList<String> allPkgs = new ArrayList<String>(); 334 while ((pkg = nextArg()) != null) { 335 if (pkg.equals("--all")) { 336 backupAll = true; 337 } else if (pkg.equals("--non-incremental")) { 338 nonIncrementalBackup = true; 339 } else if (pkg.equals("--incremental")) { 340 nonIncrementalBackup = false; 341 } else { 342 allPkgs.add(pkg); 343 } 344 } 345 if (backupAll) { 346 if (allPkgs.size() == 0) { 347 System.out.println("Running " + (nonIncrementalBackup ? "non-" : "") + 348 "incremental backup for all packages."); 349 backupNowAllPackages(nonIncrementalBackup); 350 } else { 351 System.err.println("Provide only '--all' flag or list of packages."); 352 } 353 } else if (allPkgs.size() > 0) { 354 System.out.println("Running " + (nonIncrementalBackup ? "non-" : "") + 355 "incremental backup for " + allPkgs.size() +" requested packages."); 356 backupNowPackages(allPkgs, nonIncrementalBackup); 357 } else { 358 System.err.println("Provide '--all' flag or list of packages."); 359 } 360 } 361 362 private void doTransport() { 363 try { 364 String which = nextArg(); 365 if (which == null) { 366 showUsage(); 367 return; 368 } 369 370 if ("-c".equals(which)) { 371 doTransportByComponent(); 372 return; 373 } 374 375 String old = mBmgr.selectBackupTransport(which); 376 if (old == null) { 377 System.out.println("Unknown transport '" + which 378 + "' specified; no changes made."); 379 } else { 380 System.out.println("Selected transport " + which + " (formerly " + old + ")"); 381 } 382 383 } catch (RemoteException e) { 384 System.err.println(e.toString()); 385 System.err.println(BMGR_NOT_RUNNING_ERR); 386 } 387 } 388 389 private void doTransportByComponent() { 390 String which = nextArg(); 391 if (which == null) { 392 showUsage(); 393 return; 394 } 395 396 final CountDownLatch latch = new CountDownLatch(1); 397 398 try { 399 mBmgr.selectBackupTransportAsync(ComponentName.unflattenFromString(which), 400 new ISelectBackupTransportCallback.Stub() { 401 @Override 402 public void onSuccess(String transportName) { 403 System.out.println("Success. Selected transport: " + transportName); 404 latch.countDown(); 405 } 406 407 @Override 408 public void onFailure(int reason) { 409 System.err.println("Failure. error=" + reason); 410 latch.countDown(); 411 } 412 }); 413 } catch (RemoteException e) { 414 System.err.println(e.toString()); 415 System.err.println(BMGR_NOT_RUNNING_ERR); 416 return; 417 } 418 419 try { 420 latch.await(); 421 } catch (InterruptedException e) { 422 System.err.println("Operation interrupted."); 423 } 424 } 425 426 private void doWipe() { 427 String transport = nextArg(); 428 if (transport == null) { 429 showUsage(); 430 return; 431 } 432 433 String pkg = nextArg(); 434 if (pkg == null) { 435 showUsage(); 436 return; 437 } 438 439 try { 440 mBmgr.clearBackupData(transport, pkg); 441 System.out.println("Wiped backup data for " + pkg + " on " + transport); 442 } catch (RemoteException e) { 443 System.err.println(e.toString()); 444 System.err.println(BMGR_NOT_RUNNING_ERR); 445 } 446 } 447 448 private void doList() { 449 String arg = nextArg(); // sets, transports, packages set# 450 if ("transports".equals(arg)) { 451 doListTransports(); 452 return; 453 } 454 455 // The rest of the 'list' options work with a restore session on the current transport 456 try { 457 mRestore = mBmgr.beginRestoreSession(null, null); 458 if (mRestore == null) { 459 System.err.println(BMGR_NOT_RUNNING_ERR); 460 return; 461 } 462 463 if ("sets".equals(arg)) { 464 doListRestoreSets(); 465 } else if ("transports".equals(arg)) { 466 doListTransports(); 467 } 468 469 mRestore.endRestoreSession(); 470 } catch (RemoteException e) { 471 System.err.println(e.toString()); 472 System.err.println(BMGR_NOT_RUNNING_ERR); 473 } 474 } 475 476 private void doListTransports() { 477 String arg = nextArg(); 478 479 try { 480 if ("-c".equals(arg)) { 481 for (ComponentName transport : mBmgr.listAllTransportComponents()) { 482 System.out.println(transport.flattenToShortString()); 483 } 484 return; 485 } 486 487 String current = mBmgr.getCurrentTransport(); 488 String[] transports = mBmgr.listAllTransports(); 489 if (transports == null || transports.length == 0) { 490 System.out.println("No transports available."); 491 return; 492 } 493 494 for (String t : transports) { 495 String pad = (t.equals(current)) ? " * " : " "; 496 System.out.println(pad + t); 497 } 498 } catch (RemoteException e) { 499 System.err.println(e.toString()); 500 System.err.println(BMGR_NOT_RUNNING_ERR); 501 } 502 } 503 504 private void doListRestoreSets() { 505 try { 506 RestoreObserver observer = new RestoreObserver(); 507 int err = mRestore.getAvailableRestoreSets(observer); 508 if (err != 0) { 509 System.out.println("Unable to request restore sets"); 510 } else { 511 observer.waitForCompletion(); 512 printRestoreSets(observer.sets); 513 } 514 } catch (RemoteException e) { 515 System.err.println(e.toString()); 516 System.err.println(TRANSPORT_NOT_RUNNING_ERR); 517 } 518 } 519 520 private void printRestoreSets(RestoreSet[] sets) { 521 if (sets == null || sets.length == 0) { 522 System.out.println("No restore sets"); 523 return; 524 } 525 for (RestoreSet s : sets) { 526 System.out.println(" " + Long.toHexString(s.token) + " : " + s.name); 527 } 528 } 529 530 class RestoreObserver extends IRestoreObserver.Stub { 531 boolean done; 532 RestoreSet[] sets = null; 533 534 public void restoreSetsAvailable(RestoreSet[] result) { 535 synchronized (this) { 536 sets = result; 537 done = true; 538 this.notify(); 539 } 540 } 541 542 public void restoreStarting(int numPackages) { 543 System.out.println("restoreStarting: " + numPackages + " packages"); 544 } 545 546 public void onUpdate(int nowBeingRestored, String currentPackage) { 547 System.out.println("onUpdate: " + nowBeingRestored + " = " + currentPackage); 548 } 549 550 public void restoreFinished(int error) { 551 System.out.println("restoreFinished: " + error); 552 synchronized (this) { 553 done = true; 554 this.notify(); 555 } 556 } 557 558 public void waitForCompletion() { 559 // The restoreFinished() callback will throw the 'done' flag; we 560 // just sit and wait on that notification. 561 synchronized (this) { 562 while (!this.done) { 563 try { 564 this.wait(); 565 } catch (InterruptedException ex) { 566 } 567 } 568 } 569 } 570 } 571 572 private void doRestore() { 573 String arg = nextArg(); 574 if (arg == null) { 575 showUsage(); 576 return; 577 } 578 579 if (arg.indexOf('.') >= 0 || arg.equals("android")) { 580 // it's a package name 581 doRestorePackage(arg); 582 } else { 583 try { 584 long token = Long.parseLong(arg, 16); 585 HashSet<String> filter = null; 586 while ((arg = nextArg()) != null) { 587 if (filter == null) filter = new HashSet<String>(); 588 filter.add(arg); 589 } 590 591 doRestoreAll(token, filter); 592 } catch (NumberFormatException e) { 593 showUsage(); 594 return; 595 } 596 } 597 598 System.out.println("done"); 599 } 600 601 private void doRestorePackage(String pkg) { 602 try { 603 mRestore = mBmgr.beginRestoreSession(pkg, null); 604 if (mRestore == null) { 605 System.err.println(BMGR_NOT_RUNNING_ERR); 606 return; 607 } 608 609 RestoreObserver observer = new RestoreObserver(); 610 int err = mRestore.restorePackage(pkg, observer); 611 if (err == 0) { 612 // Off and running -- wait for the restore to complete 613 observer.waitForCompletion(); 614 } else { 615 System.err.println("Unable to restore package " + pkg); 616 } 617 618 // And finally shut down the session 619 mRestore.endRestoreSession(); 620 } catch (RemoteException e) { 621 System.err.println(e.toString()); 622 System.err.println(BMGR_NOT_RUNNING_ERR); 623 } 624 } 625 626 private void doRestoreAll(long token, HashSet<String> filter) { 627 RestoreObserver observer = new RestoreObserver(); 628 629 try { 630 boolean didRestore = false; 631 mRestore = mBmgr.beginRestoreSession(null, null); 632 if (mRestore == null) { 633 System.err.println(BMGR_NOT_RUNNING_ERR); 634 return; 635 } 636 RestoreSet[] sets = null; 637 int err = mRestore.getAvailableRestoreSets(observer); 638 if (err == 0) { 639 observer.waitForCompletion(); 640 sets = observer.sets; 641 if (sets != null) { 642 for (RestoreSet s : sets) { 643 if (s.token == token) { 644 System.out.println("Scheduling restore: " + s.name); 645 if (filter == null) { 646 didRestore = (mRestore.restoreAll(token, observer) == 0); 647 } else { 648 String[] names = new String[filter.size()]; 649 filter.toArray(names); 650 didRestore = (mRestore.restoreSome(token, observer, names) == 0); 651 } 652 break; 653 } 654 } 655 } 656 } 657 if (!didRestore) { 658 if (sets == null || sets.length == 0) { 659 System.out.println("No available restore sets; no restore performed"); 660 } else { 661 System.out.println("No matching restore set token. Available sets:"); 662 printRestoreSets(sets); 663 } 664 } 665 666 // if we kicked off a restore successfully, we have to wait for it 667 // to complete before we can shut down the restore session safely 668 if (didRestore) { 669 observer.waitForCompletion(); 670 } 671 672 // once the restore has finished, close down the session and we're done 673 mRestore.endRestoreSession(); 674 } catch (RemoteException e) { 675 System.err.println(e.toString()); 676 System.err.println(BMGR_NOT_RUNNING_ERR); 677 } 678 } 679 680 private void doPrintWhitelist() { 681 try { 682 final String[] whitelist = mBmgr.getTransportWhitelist(); 683 if (whitelist != null) { 684 for (String transport : whitelist) { 685 System.out.println(transport); 686 } 687 } 688 } catch (RemoteException e) { 689 System.err.println(e.toString()); 690 System.err.println(BMGR_NOT_RUNNING_ERR); 691 } 692 } 693 694 private String nextArg() { 695 if (mNextArg >= mArgs.length) { 696 return null; 697 } 698 String arg = mArgs[mNextArg]; 699 mNextArg++; 700 return arg; 701 } 702 703 private static void showUsage() { 704 System.err.println("usage: bmgr [backup|restore|list|transport|run]"); 705 System.err.println(" bmgr backup PACKAGE"); 706 System.err.println(" bmgr enable BOOL"); 707 System.err.println(" bmgr enabled"); 708 System.err.println(" bmgr list transports [-c]"); 709 System.err.println(" bmgr list sets"); 710 System.err.println(" bmgr transport WHICH|-c WHICH_COMPONENT"); 711 System.err.println(" bmgr restore TOKEN"); 712 System.err.println(" bmgr restore TOKEN PACKAGE..."); 713 System.err.println(" bmgr restore PACKAGE"); 714 System.err.println(" bmgr run"); 715 System.err.println(" bmgr wipe TRANSPORT PACKAGE"); 716 System.err.println(" bmgr fullbackup PACKAGE..."); 717 System.err.println(" bmgr backupnow --all|PACKAGE..."); 718 System.err.println(""); 719 System.err.println("The 'backup' command schedules a backup pass for the named package."); 720 System.err.println("Note that the backup pass will effectively be a no-op if the package"); 721 System.err.println("does not actually have changed data to store."); 722 System.err.println(""); 723 System.err.println("The 'enable' command enables or disables the entire backup mechanism."); 724 System.err.println("If the argument is 'true' it will be enabled, otherwise it will be"); 725 System.err.println("disabled. When disabled, neither backup or restore operations will"); 726 System.err.println("be performed."); 727 System.err.println(""); 728 System.err.println("The 'enabled' command reports the current enabled/disabled state of"); 729 System.err.println("the backup mechanism."); 730 System.err.println(""); 731 System.err.println("The 'list transports' command reports the names of the backup transports"); 732 System.err.println("BackupManager is currently bound to. These names can be passed as arguments"); 733 System.err.println("to the 'transport' and 'wipe' commands. The currently active transport"); 734 System.err.println("is indicated with a '*' character. If -c flag is used, all available"); 735 System.err.println("transport components on the device are listed. These can be used with"); 736 System.err.println("the component variant of 'transport' command."); 737 System.err.println(""); 738 System.err.println("The 'list sets' command reports the token and name of each restore set"); 739 System.err.println("available to the device via the currently active transport."); 740 System.err.println(""); 741 System.err.println("The 'transport' command designates the named transport as the currently"); 742 System.err.println("active one. This setting is persistent across reboots. If -c flag is"); 743 System.err.println("specified, the following string is treated as a component name."); 744 System.err.println(""); 745 System.err.println("The 'restore' command when given just a restore token initiates a full-system"); 746 System.err.println("restore operation from the currently active transport. It will deliver"); 747 System.err.println("the restore set designated by the TOKEN argument to each application"); 748 System.err.println("that had contributed data to that restore set."); 749 System.err.println(""); 750 System.err.println("The 'restore' command when given a token and one or more package names"); 751 System.err.println("initiates a restore operation of just those given packages from the restore"); 752 System.err.println("set designated by the TOKEN argument. It is effectively the same as the"); 753 System.err.println("'restore' operation supplying only a token, but applies a filter to the"); 754 System.err.println("set of applications to be restored."); 755 System.err.println(""); 756 System.err.println("The 'restore' command when given just a package name intiates a restore of"); 757 System.err.println("just that one package according to the restore set selection algorithm"); 758 System.err.println("used by the RestoreSession.restorePackage() method."); 759 System.err.println(""); 760 System.err.println("The 'run' command causes any scheduled backup operation to be initiated"); 761 System.err.println("immediately, without the usual waiting period for batching together"); 762 System.err.println("data changes."); 763 System.err.println(""); 764 System.err.println("The 'wipe' command causes all backed-up data for the given package to be"); 765 System.err.println("erased from the given transport's storage. The next backup operation"); 766 System.err.println("that the given application performs will rewrite its entire data set."); 767 System.err.println("Transport names to use here are those reported by 'list transports'."); 768 System.err.println(""); 769 System.err.println("The 'fullbackup' command induces a full-data stream backup for one or more"); 770 System.err.println("packages. The data is sent via the currently active transport."); 771 System.err.println(""); 772 System.err.println("The 'backupnow' command runs an immediate backup for one or more packages."); 773 System.err.println(" --all flag runs backup for all eligible packages."); 774 System.err.println("For each package it will run key/value or full data backup "); 775 System.err.println("depending on the package's manifest declarations."); 776 System.err.println("The data is sent via the currently active transport."); 777 } 778} 779