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