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.RestoreSet;
20import android.app.backup.IBackupManager;
21import android.app.backup.IRestoreObserver;
22import android.app.backup.IRestoreSession;
23import android.os.RemoteException;
24import android.os.ServiceManager;
25
26import java.util.ArrayList;
27import java.util.HashSet;
28
29public final class Bmgr {
30    IBackupManager mBmgr;
31    IRestoreSession mRestore;
32
33    static final String BMGR_NOT_RUNNING_ERR =
34            "Error: Could not access the Backup Manager.  Is the system running?";
35    static final String TRANSPORT_NOT_RUNNING_ERR =
36        "Error: Could not access the backup transport.  Is the system running?";
37
38    private String[] mArgs;
39    private int mNextArg;
40
41    public static void main(String[] args) {
42        try {
43            new Bmgr().run(args);
44        } catch (Exception e) {
45            System.err.println("Exception caught:");
46            e.printStackTrace();
47        }
48    }
49
50    public void run(String[] args) {
51        if (args.length < 1) {
52            showUsage();
53            return;
54        }
55
56        mBmgr = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
57        if (mBmgr == null) {
58            System.err.println(BMGR_NOT_RUNNING_ERR);
59            return;
60        }
61
62        mArgs = args;
63        String op = args[0];
64        mNextArg = 1;
65
66        if ("enabled".equals(op)) {
67            doEnabled();
68            return;
69        }
70
71        if ("enable".equals(op)) {
72            doEnable();
73            return;
74        }
75
76        if ("run".equals(op)) {
77            doRun();
78            return;
79        }
80
81        if ("backup".equals(op)) {
82            doBackup();
83            return;
84        }
85
86        if ("list".equals(op)) {
87            doList();
88            return;
89        }
90
91        if ("restore".equals(op)) {
92            doRestore();
93            return;
94        }
95
96        if ("transport".equals(op)) {
97            doTransport();
98            return;
99        }
100
101        if ("wipe".equals(op)) {
102            doWipe();
103            return;
104        }
105
106        if ("fullbackup".equals(op)) {
107            doFullTransportBackup();
108            return;
109        }
110
111        System.err.println("Unknown command");
112        showUsage();
113    }
114
115    private String enableToString(boolean enabled) {
116        return enabled ? "enabled" : "disabled";
117    }
118
119    private void doEnabled() {
120        try {
121            boolean isEnabled = mBmgr.isBackupEnabled();
122            System.out.println("Backup Manager currently "
123                    + enableToString(isEnabled));
124        } catch (RemoteException e) {
125            System.err.println(e.toString());
126            System.err.println(BMGR_NOT_RUNNING_ERR);
127        }
128    }
129
130    private void doEnable() {
131        String arg = nextArg();
132        if (arg == null) {
133            showUsage();
134            return;
135        }
136
137        try {
138            boolean enable = Boolean.parseBoolean(arg);
139            mBmgr.setBackupEnabled(enable);
140            System.out.println("Backup Manager now " + enableToString(enable));
141        } catch (NumberFormatException e) {
142            showUsage();
143            return;
144        } catch (RemoteException e) {
145            System.err.println(e.toString());
146            System.err.println(BMGR_NOT_RUNNING_ERR);
147        }
148    }
149
150    private void doRun() {
151        try {
152            mBmgr.backupNow();
153        } catch (RemoteException e) {
154            System.err.println(e.toString());
155            System.err.println(BMGR_NOT_RUNNING_ERR);
156        }
157    }
158
159    private void doBackup() {
160        String pkg = nextArg();
161        if (pkg == null) {
162            showUsage();
163            return;
164        }
165
166        try {
167            mBmgr.dataChanged(pkg);
168        } catch (RemoteException e) {
169            System.err.println(e.toString());
170            System.err.println(BMGR_NOT_RUNNING_ERR);
171        }
172    }
173
174    private void doFullTransportBackup() {
175        System.out.println("Performing full transport backup");
176
177        String pkg;
178        ArrayList<String> allPkgs = new ArrayList<String>();
179        while ((pkg = nextArg()) != null) {
180            allPkgs.add(pkg);
181        }
182        if (allPkgs.size() > 0) {
183            try {
184                mBmgr.fullTransportBackup(allPkgs.toArray(new String[allPkgs.size()]));
185            } catch (RemoteException e) {
186                System.err.println(e.toString());
187                System.err.println(BMGR_NOT_RUNNING_ERR);
188            }
189        }
190    }
191
192    private void doTransport() {
193        try {
194            String which = nextArg();
195            if (which == null) {
196                showUsage();
197                return;
198            }
199
200            String old = mBmgr.selectBackupTransport(which);
201            if (old == null) {
202                System.out.println("Unknown transport '" + which
203                        + "' specified; no changes made.");
204            } else {
205                System.out.println("Selected transport " + which + " (formerly " + old + ")");
206            }
207        } catch (RemoteException e) {
208            System.err.println(e.toString());
209            System.err.println(BMGR_NOT_RUNNING_ERR);
210        }
211    }
212
213    private void doWipe() {
214        String transport = nextArg();
215        if (transport == null) {
216            showUsage();
217            return;
218        }
219
220        String pkg = nextArg();
221        if (pkg == null) {
222            showUsage();
223            return;
224        }
225
226        try {
227            mBmgr.clearBackupData(transport, pkg);
228            System.out.println("Wiped backup data for " + pkg + " on " + transport);
229        } catch (RemoteException e) {
230            System.err.println(e.toString());
231            System.err.println(BMGR_NOT_RUNNING_ERR);
232        }
233    }
234
235    private void doList() {
236        String arg = nextArg();     // sets, transports, packages set#
237        if ("transports".equals(arg)) {
238            doListTransports();
239            return;
240        }
241
242        // The rest of the 'list' options work with a restore session on the current transport
243        try {
244            mRestore = mBmgr.beginRestoreSession(null, null);
245            if (mRestore == null) {
246                System.err.println(BMGR_NOT_RUNNING_ERR);
247                return;
248            }
249
250            if ("sets".equals(arg)) {
251                doListRestoreSets();
252            } else if ("transports".equals(arg)) {
253                doListTransports();
254            }
255
256            mRestore.endRestoreSession();
257        } catch (RemoteException e) {
258            System.err.println(e.toString());
259            System.err.println(BMGR_NOT_RUNNING_ERR);
260        }
261    }
262
263    private void doListTransports() {
264        try {
265            String current = mBmgr.getCurrentTransport();
266            String[] transports = mBmgr.listAllTransports();
267            if (transports == null || transports.length == 0) {
268                System.out.println("No transports available.");
269                return;
270            }
271
272            for (String t : transports) {
273                String pad = (t.equals(current)) ? "  * " : "    ";
274                System.out.println(pad + t);
275            }
276        } catch (RemoteException e) {
277            System.err.println(e.toString());
278            System.err.println(BMGR_NOT_RUNNING_ERR);
279        }
280    }
281
282    private void doListRestoreSets() {
283        try {
284            RestoreObserver observer = new RestoreObserver();
285            int err = mRestore.getAvailableRestoreSets(observer);
286            if (err != 0) {
287                System.out.println("Unable to request restore sets");
288            } else {
289                observer.waitForCompletion();
290                printRestoreSets(observer.sets);
291            }
292        } catch (RemoteException e) {
293            System.err.println(e.toString());
294            System.err.println(TRANSPORT_NOT_RUNNING_ERR);
295        }
296    }
297
298    private void printRestoreSets(RestoreSet[] sets) {
299        if (sets == null || sets.length == 0) {
300            System.out.println("No restore sets");
301            return;
302        }
303        for (RestoreSet s : sets) {
304            System.out.println("  " + Long.toHexString(s.token) + " : " + s.name);
305        }
306    }
307
308    class RestoreObserver extends IRestoreObserver.Stub {
309        boolean done;
310        RestoreSet[] sets = null;
311
312        public void restoreSetsAvailable(RestoreSet[] result) {
313            synchronized (this) {
314                sets = result;
315                done = true;
316                this.notify();
317            }
318        }
319
320        public void restoreStarting(int numPackages) {
321            System.out.println("restoreStarting: " + numPackages + " packages");
322        }
323
324        public void onUpdate(int nowBeingRestored, String currentPackage) {
325            System.out.println("onUpdate: " + nowBeingRestored + " = " + currentPackage);
326        }
327
328        public void restoreFinished(int error) {
329            System.out.println("restoreFinished: " + error);
330            synchronized (this) {
331                done = true;
332                this.notify();
333            }
334        }
335
336        public void waitForCompletion() {
337            // The restoreFinished() callback will throw the 'done' flag; we
338            // just sit and wait on that notification.
339            synchronized (this) {
340                while (!this.done) {
341                    try {
342                        this.wait();
343                    } catch (InterruptedException ex) {
344                    }
345                }
346            }
347        }
348    }
349
350    private void doRestore() {
351        String arg = nextArg();
352        if (arg == null) {
353            showUsage();
354            return;
355        }
356
357        if (arg.indexOf('.') >= 0) {
358            // it's a package name
359            doRestorePackage(arg);
360        } else {
361            try {
362                long token = Long.parseLong(arg, 16);
363                HashSet<String> filter = null;
364                while ((arg = nextArg()) != null) {
365                    if (filter == null) filter = new HashSet<String>();
366                    filter.add(arg);
367                }
368
369                doRestoreAll(token, filter);
370            } catch (NumberFormatException e) {
371                showUsage();
372                return;
373            }
374        }
375
376        System.out.println("done");
377    }
378
379    private void doRestorePackage(String pkg) {
380        try {
381            mRestore = mBmgr.beginRestoreSession(pkg, null);
382            if (mRestore == null) {
383                System.err.println(BMGR_NOT_RUNNING_ERR);
384                return;
385            }
386
387            RestoreObserver observer = new RestoreObserver();
388            int err = mRestore.restorePackage(pkg, observer);
389            if (err == 0) {
390                // Off and running -- wait for the restore to complete
391                observer.waitForCompletion();
392            } else {
393                System.err.println("Unable to restore package " + pkg);
394            }
395
396            // And finally shut down the session
397            mRestore.endRestoreSession();
398        } catch (RemoteException e) {
399            System.err.println(e.toString());
400            System.err.println(BMGR_NOT_RUNNING_ERR);
401        }
402    }
403
404    private void doRestoreAll(long token, HashSet<String> filter) {
405        RestoreObserver observer = new RestoreObserver();
406
407        try {
408            boolean didRestore = false;
409            mRestore = mBmgr.beginRestoreSession(null, null);
410            if (mRestore == null) {
411                System.err.println(BMGR_NOT_RUNNING_ERR);
412                return;
413            }
414            RestoreSet[] sets = null;
415            int err = mRestore.getAvailableRestoreSets(observer);
416            if (err == 0) {
417                observer.waitForCompletion();
418                sets = observer.sets;
419                if (sets != null) {
420                    for (RestoreSet s : sets) {
421                        if (s.token == token) {
422                            System.out.println("Scheduling restore: " + s.name);
423                            if (filter == null) {
424                                didRestore = (mRestore.restoreAll(token, observer) == 0);
425                            } else {
426                                String[] names = new String[filter.size()];
427                                filter.toArray(names);
428                                didRestore = (mRestore.restoreSome(token, observer, names) == 0);
429                            }
430                            break;
431                        }
432                    }
433                }
434            }
435            if (!didRestore) {
436                if (sets == null || sets.length == 0) {
437                    System.out.println("No available restore sets; no restore performed");
438                } else {
439                    System.out.println("No matching restore set token.  Available sets:");
440                    printRestoreSets(sets);
441                }
442            }
443
444            // if we kicked off a restore successfully, we have to wait for it
445            // to complete before we can shut down the restore session safely
446            if (didRestore) {
447                observer.waitForCompletion();
448            }
449
450            // once the restore has finished, close down the session and we're done
451            mRestore.endRestoreSession();
452        } catch (RemoteException e) {
453            System.err.println(e.toString());
454            System.err.println(BMGR_NOT_RUNNING_ERR);
455        }
456    }
457
458    private String nextArg() {
459        if (mNextArg >= mArgs.length) {
460            return null;
461        }
462        String arg = mArgs[mNextArg];
463        mNextArg++;
464        return arg;
465    }
466
467    private static void showUsage() {
468        System.err.println("usage: bmgr [backup|restore|list|transport|run]");
469        System.err.println("       bmgr backup PACKAGE");
470        System.err.println("       bmgr enable BOOL");
471        System.err.println("       bmgr enabled");
472        System.err.println("       bmgr list transports");
473        System.err.println("       bmgr list sets");
474        System.err.println("       bmgr transport WHICH");
475        System.err.println("       bmgr restore TOKEN");
476        System.err.println("       bmgr restore TOKEN PACKAGE...");
477        System.err.println("       bmgr restore PACKAGE");
478        System.err.println("       bmgr run");
479        System.err.println("       bmgr wipe TRANSPORT PACKAGE");
480        System.err.println("       bmgr fullbackup PACKAGE...");
481        System.err.println("");
482        System.err.println("The 'backup' command schedules a backup pass for the named package.");
483        System.err.println("Note that the backup pass will effectively be a no-op if the package");
484        System.err.println("does not actually have changed data to store.");
485        System.err.println("");
486        System.err.println("The 'enable' command enables or disables the entire backup mechanism.");
487        System.err.println("If the argument is 'true' it will be enabled, otherwise it will be");
488        System.err.println("disabled.  When disabled, neither backup or restore operations will");
489        System.err.println("be performed.");
490        System.err.println("");
491        System.err.println("The 'enabled' command reports the current enabled/disabled state of");
492        System.err.println("the backup mechanism.");
493        System.err.println("");
494        System.err.println("The 'list transports' command reports the names of the backup transports");
495        System.err.println("currently available on the device.  These names can be passed as arguments");
496        System.err.println("to the 'transport' and 'wipe' commands.  The currently active transport");
497        System.err.println("is indicated with a '*' character.");
498        System.err.println("");
499        System.err.println("The 'list sets' command reports the token and name of each restore set");
500        System.err.println("available to the device via the currently active transport.");
501        System.err.println("");
502        System.err.println("The 'transport' command designates the named transport as the currently");
503        System.err.println("active one.  This setting is persistent across reboots.");
504        System.err.println("");
505        System.err.println("The 'restore' command when given just a restore token initiates a full-system");
506        System.err.println("restore operation from the currently active transport.  It will deliver");
507        System.err.println("the restore set designated by the TOKEN argument to each application");
508        System.err.println("that had contributed data to that restore set.");
509        System.err.println("");
510        System.err.println("The 'restore' command when given a token and one or more package names");
511        System.err.println("initiates a restore operation of just those given packages from the restore");
512        System.err.println("set designated by the TOKEN argument.  It is effectively the same as the");
513        System.err.println("'restore' operation supplying only a token, but applies a filter to the");
514        System.err.println("set of applications to be restored.");
515        System.err.println("");
516        System.err.println("The 'restore' command when given just a package name intiates a restore of");
517        System.err.println("just that one package according to the restore set selection algorithm");
518        System.err.println("used by the RestoreSession.restorePackage() method.");
519        System.err.println("");
520        System.err.println("The 'run' command causes any scheduled backup operation to be initiated");
521        System.err.println("immediately, without the usual waiting period for batching together");
522        System.err.println("data changes.");
523        System.err.println("");
524        System.err.println("The 'wipe' command causes all backed-up data for the given package to be");
525        System.err.println("erased from the given transport's storage.  The next backup operation");
526        System.err.println("that the given application performs will rewrite its entire data set.");
527        System.err.println("Transport names to use here are those reported by 'list transports'.");
528        System.err.println("");
529        System.err.println("The 'fullbackup' command induces a full-data stream backup for one or more");
530        System.err.println("packages.  The data is sent via the currently active transport.");
531    }
532}
533