/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.commands.bmgr; import android.app.backup.BackupManager; import android.app.backup.BackupProgress; import android.app.backup.BackupTransport; import android.app.backup.IBackupManager; import android.app.backup.IBackupObserver; import android.app.backup.IRestoreObserver; import android.app.backup.IRestoreSession; import android.app.backup.RestoreSet; import android.app.backup.ISelectBackupTransportCallback; import android.content.ComponentName; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.util.ArraySet; import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.concurrent.CountDownLatch; public final class Bmgr { IBackupManager mBmgr; IRestoreSession mRestore; static final String BMGR_NOT_RUNNING_ERR = "Error: Could not access the Backup Manager. Is the system running?"; static final String TRANSPORT_NOT_RUNNING_ERR = "Error: Could not access the backup transport. Is the system running?"; static final String PM_NOT_RUNNING_ERR = "Error: Could not access the Package Manager. Is the system running?"; private String[] mArgs; private int mNextArg; public static void main(String[] args) { try { new Bmgr().run(args); } catch (Exception e) { System.err.println("Exception caught:"); e.printStackTrace(); } } public void run(String[] args) { if (args.length < 1) { showUsage(); return; } mBmgr = IBackupManager.Stub.asInterface(ServiceManager.getService("backup")); if (mBmgr == null) { System.err.println(BMGR_NOT_RUNNING_ERR); return; } mArgs = args; String op = args[0]; mNextArg = 1; if ("enabled".equals(op)) { doEnabled(); return; } if ("enable".equals(op)) { doEnable(); return; } if ("run".equals(op)) { doRun(); return; } if ("backup".equals(op)) { doBackup(); return; } if ("init".equals(op)) { doInit(); return; } if ("list".equals(op)) { doList(); return; } if ("restore".equals(op)) { doRestore(); return; } if ("transport".equals(op)) { doTransport(); return; } if ("wipe".equals(op)) { doWipe(); return; } if ("fullbackup".equals(op)) { doFullTransportBackup(); return; } if ("backupnow".equals(op)) { doBackupNow(); return; } if ("cancel".equals(op)) { doCancel(); return; } if ("whitelist".equals(op)) { doPrintWhitelist(); return; } System.err.println("Unknown command"); showUsage(); } private String enableToString(boolean enabled) { return enabled ? "enabled" : "disabled"; } private void doEnabled() { try { boolean isEnabled = mBmgr.isBackupEnabled(); System.out.println("Backup Manager currently " + enableToString(isEnabled)); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } private void doEnable() { String arg = nextArg(); if (arg == null) { showUsage(); return; } try { boolean enable = Boolean.parseBoolean(arg); mBmgr.setBackupEnabled(enable); System.out.println("Backup Manager now " + enableToString(enable)); } catch (NumberFormatException e) { showUsage(); return; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } private void doRun() { try { mBmgr.backupNow(); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } private void doBackup() { String pkg = nextArg(); if (pkg == null) { showUsage(); return; } try { mBmgr.dataChanged(pkg); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } private void doFullTransportBackup() { System.out.println("Performing full transport backup"); String pkg; ArraySet allPkgs = new ArraySet(); while ((pkg = nextArg()) != null) { allPkgs.add(pkg); } if (allPkgs.size() > 0) { try { mBmgr.fullTransportBackup(allPkgs.toArray(new String[allPkgs.size()])); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } } // IBackupObserver generically usable for any backup/init operation abstract class Observer extends IBackupObserver.Stub { private final Object trigger = new Object(); @GuardedBy("trigger") private volatile boolean done = false; @Override public void onUpdate(String currentPackage, BackupProgress backupProgress) { } @Override public void onResult(String currentPackage, int status) { } @Override public void backupFinished(int status) { synchronized (trigger) { done = true; trigger.notify(); } } public boolean done() { return this.done; } // Wait forever public void waitForCompletion() { waitForCompletion(0); } // Wait for a given time and then give up public void waitForCompletion(long timeout) { // The backupFinished() callback will throw the 'done' flag; we // just sit and wait on that notification. final long targetTime = SystemClock.elapsedRealtime() + timeout; synchronized (trigger) { // Wait until either we're done, or we've reached a stated positive timeout while (!done && (timeout <= 0 || SystemClock.elapsedRealtime() < targetTime)) { try { trigger.wait(1000L); } catch (InterruptedException ex) { } } } } } class BackupObserver extends Observer { @Override public void onUpdate(String currentPackage, BackupProgress backupProgress) { super.onUpdate(currentPackage, backupProgress); System.out.println( "Package " + currentPackage + " with progress: " + backupProgress.bytesTransferred + "/" + backupProgress.bytesExpected); } @Override public void onResult(String currentPackage, int status) { super.onResult(currentPackage, status); System.out.println("Package " + currentPackage + " with result: " + convertBackupStatusToString(status)); } @Override public void backupFinished(int status) { super.backupFinished(status); System.out.println("Backup finished with result: " + convertBackupStatusToString(status)); } } private static String convertBackupStatusToString(int errorCode) { switch (errorCode) { case BackupManager.SUCCESS: return "Success"; case BackupManager.ERROR_BACKUP_NOT_ALLOWED: return "Backup is not allowed"; case BackupManager.ERROR_PACKAGE_NOT_FOUND: return "Package not found"; case BackupManager.ERROR_TRANSPORT_ABORTED: return "Transport error"; case BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED: return "Transport rejected package because it wasn't able to process it" + " at the time"; case BackupManager.ERROR_AGENT_FAILURE: return "Agent error"; case BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED: return "Size quota exceeded"; case BackupManager.ERROR_BACKUP_CANCELLED: return "Backup Cancelled"; default: return "Unknown error"; } } private void backupNowAllPackages(boolean nonIncrementalBackup) { int userId = UserHandle.USER_SYSTEM; IPackageManager mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); if (mPm == null) { System.err.println(PM_NOT_RUNNING_ERR); return; } List installedPackages = null; try { installedPackages = mPm.getInstalledPackages(0, userId).getList(); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); } if (installedPackages != null) { List packages = new ArrayList<>(); for (PackageInfo pi : installedPackages) { try { if (mBmgr.isAppEligibleForBackup(pi.packageName)) { packages.add(pi.packageName); } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } backupNowPackages(packages, nonIncrementalBackup); } } private void backupNowPackages(List packages, boolean nonIncrementalBackup) { int flags = 0; if (nonIncrementalBackup) { flags |= BackupManager.FLAG_NON_INCREMENTAL_BACKUP; } try { BackupObserver observer = new BackupObserver(); // TODO: implement monitor here? int err = mBmgr.requestBackup(packages.toArray(new String[packages.size()]), observer, null, flags); if (err == 0) { // Off and running -- wait for the backup to complete observer.waitForCompletion(); } else { System.err.println("Unable to run backup"); } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } private void doBackupNow() { String pkg; boolean backupAll = false; boolean nonIncrementalBackup = false; ArrayList allPkgs = new ArrayList(); while ((pkg = nextArg()) != null) { if (pkg.equals("--all")) { backupAll = true; } else if (pkg.equals("--non-incremental")) { nonIncrementalBackup = true; } else if (pkg.equals("--incremental")) { nonIncrementalBackup = false; } else { if (!allPkgs.contains(pkg)) { allPkgs.add(pkg); } } } if (backupAll) { if (allPkgs.size() == 0) { System.out.println("Running " + (nonIncrementalBackup ? "non-" : "") + "incremental backup for all packages."); backupNowAllPackages(nonIncrementalBackup); } else { System.err.println("Provide only '--all' flag or list of packages."); } } else if (allPkgs.size() > 0) { System.out.println("Running " + (nonIncrementalBackup ? "non-" : "") + "incremental backup for " + allPkgs.size() +" requested packages."); backupNowPackages(allPkgs, nonIncrementalBackup); } else { System.err.println("Provide '--all' flag or list of packages."); } } private void doCancel() { String arg = nextArg(); if ("backups".equals(arg)) { try { mBmgr.cancelBackups(); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } return; } System.err.println("Unknown command."); } private void doTransport() { try { String which = nextArg(); if (which == null) { showUsage(); return; } if ("-c".equals(which)) { doTransportByComponent(); return; } String old = mBmgr.selectBackupTransport(which); if (old == null) { System.out.println("Unknown transport '" + which + "' specified; no changes made."); } else { System.out.println("Selected transport " + which + " (formerly " + old + ")"); } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } private void doTransportByComponent() { String which = nextArg(); if (which == null) { showUsage(); return; } final CountDownLatch latch = new CountDownLatch(1); try { mBmgr.selectBackupTransportAsync(ComponentName.unflattenFromString(which), new ISelectBackupTransportCallback.Stub() { @Override public void onSuccess(String transportName) { System.out.println("Success. Selected transport: " + transportName); latch.countDown(); } @Override public void onFailure(int reason) { System.err.println("Failure. error=" + reason); latch.countDown(); } }); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); return; } try { latch.await(); } catch (InterruptedException e) { System.err.println("Operation interrupted."); } } private void doWipe() { String transport = nextArg(); if (transport == null) { showUsage(); return; } String pkg = nextArg(); if (pkg == null) { showUsage(); return; } try { mBmgr.clearBackupData(transport, pkg); System.out.println("Wiped backup data for " + pkg + " on " + transport); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } class InitObserver extends Observer { public int result = BackupTransport.TRANSPORT_ERROR; @Override public void backupFinished(int status) { super.backupFinished(status); result = status; } } private void doInit() { ArraySet transports = new ArraySet<>(); String transport; while ((transport = nextArg()) != null) { transports.add(transport); } if (transports.size() == 0) { showUsage(); return; } InitObserver observer = new InitObserver(); try { System.out.println("Initializing transports: " + transports); mBmgr.initializeTransports(transports.toArray(new String[transports.size()]), observer); observer.waitForCompletion(30*1000L); System.out.println("Initialization result: " + observer.result); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } private void doList() { String arg = nextArg(); // sets, transports, packages set# if ("transports".equals(arg)) { doListTransports(); return; } // The rest of the 'list' options work with a restore session on the current transport try { mRestore = mBmgr.beginRestoreSession(null, null); if (mRestore == null) { System.err.println(BMGR_NOT_RUNNING_ERR); return; } if ("sets".equals(arg)) { doListRestoreSets(); } else if ("transports".equals(arg)) { doListTransports(); } mRestore.endRestoreSession(); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } private void doListTransports() { String arg = nextArg(); try { if ("-c".equals(arg)) { for (ComponentName transport : mBmgr.listAllTransportComponents()) { System.out.println(transport.flattenToShortString()); } return; } String current = mBmgr.getCurrentTransport(); String[] transports = mBmgr.listAllTransports(); if (transports == null || transports.length == 0) { System.out.println("No transports available."); return; } for (String t : transports) { String pad = (t.equals(current)) ? " * " : " "; System.out.println(pad + t); } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } private void doListRestoreSets() { try { RestoreObserver observer = new RestoreObserver(); // TODO implement monitor here int err = mRestore.getAvailableRestoreSets(observer, null); if (err != 0) { System.out.println("Unable to request restore sets"); } else { observer.waitForCompletion(); printRestoreSets(observer.sets); } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(TRANSPORT_NOT_RUNNING_ERR); } } private void printRestoreSets(RestoreSet[] sets) { if (sets == null || sets.length == 0) { System.out.println("No restore sets"); return; } for (RestoreSet s : sets) { System.out.println(" " + Long.toHexString(s.token) + " : " + s.name); } } class RestoreObserver extends IRestoreObserver.Stub { boolean done; RestoreSet[] sets = null; public void restoreSetsAvailable(RestoreSet[] result) { synchronized (this) { sets = result; done = true; this.notify(); } } public void restoreStarting(int numPackages) { System.out.println("restoreStarting: " + numPackages + " packages"); } public void onUpdate(int nowBeingRestored, String currentPackage) { System.out.println("onUpdate: " + nowBeingRestored + " = " + currentPackage); } public void restoreFinished(int error) { System.out.println("restoreFinished: " + error); synchronized (this) { done = true; this.notify(); } } /** * Wait until either {@link #restoreFinished} or {@link #restoreStarting} is called. * Once one is called, it clears the internal flag again, so that the same observer intance * can be reused for a next operation. */ public void waitForCompletion() { // The restoreFinished() callback will throw the 'done' flag; we // just sit and wait on that notification. synchronized (this) { while (!this.done) { try { this.wait(); } catch (InterruptedException ex) { } } done = false; } } } private void doRestore() { String arg = nextArg(); if (arg == null) { showUsage(); return; } if (arg.indexOf('.') >= 0 || arg.equals("android")) { // it's a package name doRestorePackage(arg); } else { try { long token = Long.parseLong(arg, 16); HashSet filter = null; while ((arg = nextArg()) != null) { if (filter == null) filter = new HashSet(); filter.add(arg); } doRestoreAll(token, filter); } catch (NumberFormatException e) { showUsage(); return; } } System.out.println("done"); } private void doRestorePackage(String pkg) { try { mRestore = mBmgr.beginRestoreSession(pkg, null); if (mRestore == null) { System.err.println(BMGR_NOT_RUNNING_ERR); return; } RestoreObserver observer = new RestoreObserver(); // TODO implement monitor here int err = mRestore.restorePackage(pkg, observer, null ); if (err == 0) { // Off and running -- wait for the restore to complete observer.waitForCompletion(); } else { System.err.println("Unable to restore package " + pkg); } // And finally shut down the session mRestore.endRestoreSession(); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } private void doRestoreAll(long token, HashSet filter) { RestoreObserver observer = new RestoreObserver(); try { boolean didRestore = false; mRestore = mBmgr.beginRestoreSession(null, null); if (mRestore == null) { System.err.println(BMGR_NOT_RUNNING_ERR); return; } RestoreSet[] sets = null; // TODO implement monitor here int err = mRestore.getAvailableRestoreSets(observer, null); if (err == 0) { observer.waitForCompletion(); sets = observer.sets; if (sets != null) { for (RestoreSet s : sets) { if (s.token == token) { System.out.println("Scheduling restore: " + s.name); if (filter == null) { didRestore = (mRestore.restoreAll(token, observer, null) == 0); } else { String[] names = new String[filter.size()]; filter.toArray(names); didRestore = (mRestore.restoreSome(token, observer, null, names) == 0); } break; } } } } if (!didRestore) { if (sets == null || sets.length == 0) { System.out.println("No available restore sets; no restore performed"); } else { System.out.println("No matching restore set token. Available sets:"); printRestoreSets(sets); } } // if we kicked off a restore successfully, we have to wait for it // to complete before we can shut down the restore session safely if (didRestore) { observer.waitForCompletion(); } // once the restore has finished, close down the session and we're done mRestore.endRestoreSession(); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } private void doPrintWhitelist() { try { final String[] whitelist = mBmgr.getTransportWhitelist(); if (whitelist != null) { for (String transport : whitelist) { System.out.println(transport); } } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } private String nextArg() { if (mNextArg >= mArgs.length) { return null; } String arg = mArgs[mNextArg]; mNextArg++; return arg; } private static void showUsage() { System.err.println("usage: bmgr [backup|restore|list|transport|run]"); System.err.println(" bmgr backup PACKAGE"); System.err.println(" bmgr enable BOOL"); System.err.println(" bmgr enabled"); System.err.println(" bmgr list transports [-c]"); System.err.println(" bmgr list sets"); System.err.println(" bmgr transport WHICH|-c WHICH_COMPONENT"); System.err.println(" bmgr restore TOKEN"); System.err.println(" bmgr restore TOKEN PACKAGE..."); System.err.println(" bmgr restore PACKAGE"); System.err.println(" bmgr run"); System.err.println(" bmgr wipe TRANSPORT PACKAGE"); System.err.println(" bmgr fullbackup PACKAGE..."); System.err.println(" bmgr backupnow --all|PACKAGE..."); System.err.println(" bmgr cancel backups"); System.err.println(""); System.err.println("The 'backup' command schedules a backup pass for the named package."); System.err.println("Note that the backup pass will effectively be a no-op if the package"); System.err.println("does not actually have changed data to store."); System.err.println(""); System.err.println("The 'enable' command enables or disables the entire backup mechanism."); System.err.println("If the argument is 'true' it will be enabled, otherwise it will be"); System.err.println("disabled. When disabled, neither backup or restore operations will"); System.err.println("be performed."); System.err.println(""); System.err.println("The 'enabled' command reports the current enabled/disabled state of"); System.err.println("the backup mechanism."); System.err.println(""); System.err.println("The 'list transports' command reports the names of the backup transports"); System.err.println("BackupManager is currently bound to. These names can be passed as arguments"); System.err.println("to the 'transport' and 'wipe' commands. The currently active transport"); System.err.println("is indicated with a '*' character. If -c flag is used, all available"); System.err.println("transport components on the device are listed. These can be used with"); System.err.println("the component variant of 'transport' command."); System.err.println(""); System.err.println("The 'list sets' command reports the token and name of each restore set"); System.err.println("available to the device via the currently active transport."); System.err.println(""); System.err.println("The 'transport' command designates the named transport as the currently"); System.err.println("active one. This setting is persistent across reboots. If -c flag is"); System.err.println("specified, the following string is treated as a component name."); System.err.println(""); System.err.println("The 'restore' command when given just a restore token initiates a full-system"); System.err.println("restore operation from the currently active transport. It will deliver"); System.err.println("the restore set designated by the TOKEN argument to each application"); System.err.println("that had contributed data to that restore set."); System.err.println(""); System.err.println("The 'restore' command when given a token and one or more package names"); System.err.println("initiates a restore operation of just those given packages from the restore"); System.err.println("set designated by the TOKEN argument. It is effectively the same as the"); System.err.println("'restore' operation supplying only a token, but applies a filter to the"); System.err.println("set of applications to be restored."); System.err.println(""); System.err.println("The 'restore' command when given just a package name intiates a restore of"); System.err.println("just that one package according to the restore set selection algorithm"); System.err.println("used by the RestoreSession.restorePackage() method."); System.err.println(""); System.err.println("The 'run' command causes any scheduled backup operation to be initiated"); System.err.println("immediately, without the usual waiting period for batching together"); System.err.println("data changes."); System.err.println(""); System.err.println("The 'wipe' command causes all backed-up data for the given package to be"); System.err.println("erased from the given transport's storage. The next backup operation"); System.err.println("that the given application performs will rewrite its entire data set."); System.err.println("Transport names to use here are those reported by 'list transports'."); System.err.println(""); System.err.println("The 'fullbackup' command induces a full-data stream backup for one or more"); System.err.println("packages. The data is sent via the currently active transport."); System.err.println(""); System.err.println("The 'backupnow' command runs an immediate backup for one or more packages."); System.err.println(" --all flag runs backup for all eligible packages."); System.err.println("For each package it will run key/value or full data backup "); System.err.println("depending on the package's manifest declarations."); System.err.println("The data is sent via the currently active transport."); System.err.println("The 'cancel backups' command cancels all running backups."); } }