FileDocCategorySizeDatePackage
BackupManagerService.javaAPI DocAndroid 5.1 API425127Thu Mar 12 22:22:42 GMT 2015com.android.server.backup

BackupManagerService

public class BackupManagerService extends Object

Fields Summary
private static final String
TAG
private static final boolean
DEBUG
private static final boolean
MORE_DEBUG
private static final boolean
DEBUG_SCHEDULING
static final String
KEY_WIDGET_STATE
static final String
PBKDF_CURRENT
static final String
PBKDF_FALLBACK
static final String
BACKUP_MANIFEST_FILENAME
static final int
BACKUP_MANIFEST_VERSION
static final int
BACKUP_FILE_VERSION
static final String
BACKUP_FILE_HEADER_MAGIC
static final int
BACKUP_PW_FILE_VERSION
static final String
BACKUP_METADATA_FILENAME
static final int
BACKUP_METADATA_VERSION
static final int
BACKUP_WIDGET_METADATA_TOKEN
static final boolean
COMPRESS_FULL_BACKUPS
static final String
SETTINGS_PACKAGE
static final String
SHARED_BACKUP_AGENT_PACKAGE
static final String
SERVICE_ACTION_TRANSPORT_HOST
private static final long
BACKUP_INTERVAL
private static final int
FUZZ_MILLIS
private static final long
FIRST_BACKUP_INTERVAL
private static final long
TRANSPORT_RETRY_INTERVAL
private static final String
RUN_BACKUP_ACTION
private static final String
RUN_INITIALIZE_ACTION
private static final String
RUN_CLEAR_ACTION
private static final int
MSG_RUN_BACKUP
private static final int
MSG_RUN_ADB_BACKUP
private static final int
MSG_RUN_RESTORE
private static final int
MSG_RUN_CLEAR
private static final int
MSG_RUN_INITIALIZE
private static final int
MSG_RUN_GET_RESTORE_SETS
private static final int
MSG_TIMEOUT
private static final int
MSG_RESTORE_TIMEOUT
private static final int
MSG_FULL_CONFIRMATION_TIMEOUT
private static final int
MSG_RUN_ADB_RESTORE
private static final int
MSG_RETRY_INIT
private static final int
MSG_RETRY_CLEAR
private static final int
MSG_WIDGET_BROADCAST
private static final int
MSG_RUN_FULL_TRANSPORT_BACKUP
static final int
MSG_BACKUP_RESTORE_STEP
static final int
MSG_OP_COMPLETE
static final long
TIMEOUT_INTERVAL
static final long
TIMEOUT_BACKUP_INTERVAL
static final long
TIMEOUT_FULL_BACKUP_INTERVAL
static final long
TIMEOUT_SHARED_BACKUP_INTERVAL
static final long
TIMEOUT_RESTORE_INTERVAL
static final long
TIMEOUT_RESTORE_FINISHED_INTERVAL
static final long
TIMEOUT_FULL_CONFIRMATION
static final long
MIN_FULL_BACKUP_INTERVAL
android.content.Context
mContext
private android.content.pm.PackageManager
mPackageManager
android.content.pm.IPackageManager
mPackageManagerBinder
private android.app.IActivityManager
mActivityManager
private android.os.PowerManager
mPowerManager
private android.app.AlarmManager
mAlarmManager
private android.os.storage.IMountService
mMountService
android.app.backup.IBackupManager
mBackupManagerBinder
boolean
mEnabled
boolean
mProvisioned
boolean
mAutoRestore
PowerManager.WakeLock
mWakelock
android.os.HandlerThread
mHandlerThread
BackupHandler
mBackupHandler
android.app.PendingIntent
mRunBackupIntent
android.app.PendingIntent
mRunInitIntent
android.content.BroadcastReceiver
mRunBackupReceiver
android.content.BroadcastReceiver
mRunInitReceiver
final android.util.SparseArray
mBackupParticipants
HashMap
mPendingBackups
static final String
PACKAGE_MANAGER_SENTINEL
final Object
mQueueLock
final Object
mAgentConnectLock
android.app.IBackupAgent
mConnectedAgent
volatile boolean
mBackupRunning
volatile boolean
mConnecting
volatile long
mLastBackupPass
volatile long
mNextBackupPass
static final boolean
DEBUG_BACKUP_TRACE
final List
mBackupTrace
final Object
mClearDataLock
volatile boolean
mClearingData
final android.content.Intent
mTransportServiceIntent
final android.util.ArrayMap
mTransportNames
final android.util.ArrayMap
mTransports
final android.util.ArrayMap
mTransportConnections
String
mCurrentTransport
ActiveRestoreSession
mActiveRestoreSession
android.database.ContentObserver
mProvisionedObserver
static Trampoline
sInstance
static final int
OP_PENDING
static final int
OP_ACKNOWLEDGED
static final int
OP_TIMEOUT
final android.util.SparseArray
mCurrentOperations
final Object
mCurrentOpLock
final Random
mTokenGenerator
final android.util.SparseArray
mFullConfirmations
File
mBaseStateDir
File
mDataDir
File
mJournalDir
File
mJournal
private final SecureRandom
mRng
private String
mPasswordHash
private File
mPasswordHashFile
private int
mPasswordVersion
private File
mPasswordVersionFile
private byte[]
mPasswordSalt
static final int
PBKDF2_HASH_ROUNDS
static final int
PBKDF2_KEY_SIZE
static final int
PBKDF2_SALT_SIZE
static final String
ENCRYPTION_ALGORITHM_NAME
private File
mEverStored
HashSet
mEverStoredApps
static final int
CURRENT_ANCESTRAL_RECORD_VERSION
File
mTokenFile
Set
mAncestralPackages
long
mAncestralToken
long
mCurrentToken
static final String
INIT_SENTINEL_FILE_NAME
HashSet
mPendingInits
static final int
SCHEDULE_FILE_VERSION
File
mFullBackupScheduleFile
PerformFullTransportBackupTask
mRunningFullBackupTask
ArrayList
mFullBackupQueue
Runnable
mFullBackupScheduleWriter
android.content.BroadcastReceiver
mBroadcastReceiver
Constructors Summary
public BackupManagerService(android.content.Context context, Trampoline parent)

        mContext = context;
        mPackageManager = context.getPackageManager();
        mPackageManagerBinder = AppGlobals.getPackageManager();
        mActivityManager = ActivityManagerNative.getDefault();

        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));

        mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());

        // spin up the backup/restore handler thread
        mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND);
        mHandlerThread.start();
        mBackupHandler = new BackupHandler(mHandlerThread.getLooper());

        // Set up our bookkeeping
        final ContentResolver resolver = context.getContentResolver();
        mProvisioned = Settings.Global.getInt(resolver,
                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
        mAutoRestore = Settings.Secure.getInt(resolver,
                Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0;

        mProvisionedObserver = new ProvisionedObserver(mBackupHandler);
        resolver.registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
                false, mProvisionedObserver);

        // If Encrypted file systems is enabled or disabled, this call will return the
        // correct directory.
        mBaseStateDir = new File(Environment.getSecureDataDirectory(), "backup");
        mBaseStateDir.mkdirs();
        if (!SELinux.restorecon(mBaseStateDir)) {
            Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir);
        }
        mDataDir = Environment.getDownloadCacheDirectory();

        mPasswordVersion = 1;       // unless we hear otherwise
        mPasswordVersionFile = new File(mBaseStateDir, "pwversion");
        if (mPasswordVersionFile.exists()) {
            FileInputStream fin = null;
            DataInputStream in = null;
            try {
                fin = new FileInputStream(mPasswordVersionFile);
                in = new DataInputStream(fin);
                mPasswordVersion = in.readInt();
            } catch (IOException e) {
                Slog.e(TAG, "Unable to read backup pw version");
            } finally {
                try {
                    if (in != null) in.close();
                    if (fin != null) fin.close();
                } catch (IOException e) {
                    Slog.w(TAG, "Error closing pw version files");
                }
            }
        }

        mPasswordHashFile = new File(mBaseStateDir, "pwhash");
        if (mPasswordHashFile.exists()) {
            FileInputStream fin = null;
            DataInputStream in = null;
            try {
                fin = new FileInputStream(mPasswordHashFile);
                in = new DataInputStream(new BufferedInputStream(fin));
                // integer length of the salt array, followed by the salt,
                // then the hex pw hash string
                int saltLen = in.readInt();
                byte[] salt = new byte[saltLen];
                in.readFully(salt);
                mPasswordHash = in.readUTF();
                mPasswordSalt = salt;
            } catch (IOException e) {
                Slog.e(TAG, "Unable to read saved backup pw hash");
            } finally {
                try {
                    if (in != null) in.close();
                    if (fin != null) fin.close();
                } catch (IOException e) {
                    Slog.w(TAG, "Unable to close streams");
                }
            }
        }

        // Alarm receivers for scheduled backups & initialization operations
        mRunBackupReceiver = new RunBackupReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(RUN_BACKUP_ACTION);
        context.registerReceiver(mRunBackupReceiver, filter,
                android.Manifest.permission.BACKUP, null);

        mRunInitReceiver = new RunInitializeReceiver();
        filter = new IntentFilter();
        filter.addAction(RUN_INITIALIZE_ACTION);
        context.registerReceiver(mRunInitReceiver, filter,
                android.Manifest.permission.BACKUP, null);

        Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
        backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
        mRunBackupIntent = PendingIntent.getBroadcast(context, MSG_RUN_BACKUP, backupIntent, 0);

        Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
        backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
        mRunInitIntent = PendingIntent.getBroadcast(context, MSG_RUN_INITIALIZE, initIntent, 0);

        // Set up the backup-request journaling
        mJournalDir = new File(mBaseStateDir, "pending");
        mJournalDir.mkdirs();   // creates mBaseStateDir along the way
        mJournal = null;        // will be created on first use

        // Set up the various sorts of package tracking we do
        mFullBackupScheduleFile = new File(mBaseStateDir, "fb-schedule");
        initPackageTracking();

        // Build our mapping of uid to backup client services.  This implicitly
        // schedules a backup pass on the Package Manager metadata the first
        // time anything needs to be backed up.
        synchronized (mBackupParticipants) {
            addPackageParticipantsLocked(null);
        }

        // Set up our transport options and initialize the default transport
        // TODO: Don't create transports that we don't need to?
        mCurrentTransport = Settings.Secure.getString(context.getContentResolver(),
                Settings.Secure.BACKUP_TRANSPORT);
        if ("".equals(mCurrentTransport)) {
            mCurrentTransport = null;
        }
        if (DEBUG) Slog.v(TAG, "Starting with transport " + mCurrentTransport);

        // Find all transport hosts and bind to their services
        List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
                mTransportServiceIntent, 0, UserHandle.USER_OWNER);
        if (DEBUG) {
            Slog.v(TAG, "Found transports: " + ((hosts == null) ? "null" : hosts.size()));
        }
        if (hosts != null) {
            for (int i = 0; i < hosts.size(); i++) {
                final ServiceInfo transport = hosts.get(i).serviceInfo;
                if (MORE_DEBUG) {
                    Slog.v(TAG, "   " + transport.packageName + "/" + transport.name);
                }
                tryBindTransport(transport);
            }
        }

        // Now that we know about valid backup participants, parse any
        // leftover journal files into the pending backup set
        parseLeftoverJournals();

        // Power management
        mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
    
Methods Summary
public voidacknowledgeFullBackupOrRestore(int token, boolean allow, java.lang.String curPassword, java.lang.String encPpassword, android.app.backup.IFullBackupRestoreObserver observer)

        if (DEBUG) Slog.d(TAG, "acknowledgeFullBackupOrRestore : token=" + token
                + " allow=" + allow);

        // TODO: possibly require not just this signature-only permission, but even
        // require that the specific designated confirmation-UI app uid is the caller?
        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "acknowledgeFullBackupOrRestore");

        long oldId = Binder.clearCallingIdentity();
        try {

            FullParams params;
            synchronized (mFullConfirmations) {
                params = mFullConfirmations.get(token);
                if (params != null) {
                    mBackupHandler.removeMessages(MSG_FULL_CONFIRMATION_TIMEOUT, params);
                    mFullConfirmations.delete(token);

                    if (allow) {
                        final int verb = params instanceof FullBackupParams
                                ? MSG_RUN_ADB_BACKUP
                                : MSG_RUN_ADB_RESTORE;

                        params.observer = observer;
                        params.curPassword = curPassword;

                        params.encryptPassword = encPpassword;

                        if (DEBUG) Slog.d(TAG, "Sending conf message with verb " + verb);
                        mWakelock.acquire();
                        Message msg = mBackupHandler.obtainMessage(verb, params);
                        mBackupHandler.sendMessage(msg);
                    } else {
                        Slog.w(TAG, "User rejected full backup/restore operation");
                        // indicate completion without having actually transferred any data
                        signalFullBackupRestoreCompletion(params);
                    }
                } else {
                    Slog.w(TAG, "Attempted to ack full backup/restore with invalid token");
                }
            }
        } finally {
            Binder.restoreCallingIdentity(oldId);
        }
    
voidaddBackupTrace(java.lang.String s)

        if (DEBUG_BACKUP_TRACE) {
            synchronized (mBackupTrace) {
                mBackupTrace.add(s);
            }
        }
    
voidaddPackageParticipantsLocked(java.lang.String[] packageNames)

        // Look for apps that define the android:backupAgent attribute
        List<PackageInfo> targetApps = allAgentPackages();
        if (packageNames != null) {
            if (MORE_DEBUG) Slog.v(TAG, "addPackageParticipantsLocked: #" + packageNames.length);
            for (String packageName : packageNames) {
                addPackageParticipantsLockedInner(packageName, targetApps);
            }
        } else {
            if (MORE_DEBUG) Slog.v(TAG, "addPackageParticipantsLocked: all");
            addPackageParticipantsLockedInner(null, targetApps);
        }
    
private voidaddPackageParticipantsLockedInner(java.lang.String packageName, java.util.List targetPkgs)

        if (MORE_DEBUG) {
            Slog.v(TAG, "Examining " + packageName + " for backup agent");
        }

        for (PackageInfo pkg : targetPkgs) {
            if (packageName == null || pkg.packageName.equals(packageName)) {
                int uid = pkg.applicationInfo.uid;
                HashSet<String> set = mBackupParticipants.get(uid);
                if (set == null) {
                    set = new HashSet<String>();
                    mBackupParticipants.put(uid, set);
                }
                set.add(pkg.packageName);
                if (MORE_DEBUG) Slog.v(TAG, "Agent found; added");

                // Schedule a backup for it on general principles
                if (MORE_DEBUG) Slog.i(TAG, "Scheduling backup for new app " + pkg.packageName);
                dataChangedImpl(pkg.packageName);
            }
        }
    
public voidagentConnected(java.lang.String packageName, android.os.IBinder agentBinder)

        synchronized(mAgentConnectLock) {
            if (Binder.getCallingUid() == Process.SYSTEM_UID) {
                Slog.d(TAG, "agentConnected pkg=" + packageName + " agent=" + agentBinder);
                IBackupAgent agent = IBackupAgent.Stub.asInterface(agentBinder);
                mConnectedAgent = agent;
                mConnecting = false;
            } else {
                Slog.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
                        + " claiming agent connected");
            }
            mAgentConnectLock.notifyAll();
        }
    
public voidagentDisconnected(java.lang.String packageName)

        // TODO: handle backup being interrupted
        synchronized(mAgentConnectLock) {
            if (Binder.getCallingUid() == Process.SYSTEM_UID) {
                mConnectedAgent = null;
                mConnecting = false;
            } else {
                Slog.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
                        + " claiming agent disconnected");
            }
            mAgentConnectLock.notifyAll();
        }
    
java.util.ListallAgentPackages()

        // !!! TODO: cache this and regenerate only when necessary
        int flags = PackageManager.GET_SIGNATURES;
        List<PackageInfo> packages = mPackageManager.getInstalledPackages(flags);
        int N = packages.size();
        for (int a = N-1; a >= 0; a--) {
            PackageInfo pkg = packages.get(a);
            try {
                ApplicationInfo app = pkg.applicationInfo;
                if (((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0)
                        || app.backupAgentName == null) {
                    packages.remove(a);
                }
                else {
                    // we will need the shared library path, so look that up and store it here.
                    // This is used implicitly when we pass the PackageInfo object off to
                    // the Activity Manager to launch the app for backup/restore purposes.
                    app = mPackageManager.getApplicationInfo(pkg.packageName,
                            PackageManager.GET_SHARED_LIBRARY_FILES);
                    pkg.applicationInfo.sharedLibraryFiles = app.sharedLibraryFiles;
                }
            } catch (NameNotFoundException e) {
                packages.remove(a);
            }
        }
        return packages;
    
public static booleanappGetsFullBackup(android.content.pm.PackageInfo pkg)

        if (pkg.applicationInfo.backupAgentName != null) {
            // If it has an agent, it gets full backups only if it says so
            return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0;
        }

        // No agent means we do full backups for it
        return true;
    
public static booleanappIsEligibleForBackup(android.content.pm.ApplicationInfo app)

        // 1. their manifest states android:allowBackup="false"
        if ((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) {
            return false;
        }

        // 2. they run as a system-level uid but do not supply their own backup agent
        if ((app.uid < Process.FIRST_APPLICATION_UID) && (app.backupAgentName == null)) {
            return false;
        }

        // 3. it is the special shared-storage backup package used for 'adb backup'
        if (app.packageName.equals(BackupManagerService.SHARED_BACKUP_AGENT_PACKAGE)) {
            return false;
        }

        return true;
    
public voidbackupNow()

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "backupNow");

        if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
        synchronized (mQueueLock) {
            // Because the alarms we are using can jitter, and we want an *immediate*
            // backup pass to happen, we restart the timer beginning with "next time,"
            // then manually fire the backup trigger intent ourselves.
            startBackupAlarmsLocked(BACKUP_INTERVAL);
            try {
                mRunBackupIntent.send();
            } catch (PendingIntent.CanceledException e) {
                // should never happen
                Slog.e(TAG, "run-backup intent cancelled!");
            }
        }
    
private booleanbackupPasswordMatches(java.lang.String currentPw)

        if (hasBackupPassword()) {
            final boolean pbkdf2Fallback = (mPasswordVersion < BACKUP_PW_FILE_VERSION);
            if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PBKDF2_HASH_ROUNDS)
                    && !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK,
                            currentPw, PBKDF2_HASH_ROUNDS))) {
                if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
                return false;
            }
        }
        return true;
    
booleanbeginFullBackup(FullBackupJob scheduledJob)
Conditions are right for a full backup operation, so run one. The model we use is to perform one app backup per scheduled job execution, and to reschedule the job with zero latency as long as conditions remain right and we still have work to do.

return
Whether ongoing work will continue. The return value here will be passed along as the return value to the scheduled job's onStartJob() callback.

        long now = System.currentTimeMillis();
        FullBackupEntry entry = null;

        if (!mEnabled || !mProvisioned) {
            // Backups are globally disabled, so don't proceed.  We also don't reschedule
            // the job driving automatic backups; that job will be scheduled again when
            // the user enables backup.
            if (MORE_DEBUG) {
                Slog.i(TAG, "beginFullBackup but e=" + mEnabled
                        + " p=" + mProvisioned + "; ignoring");
            }
            return false;
        }

        if (DEBUG_SCHEDULING) {
            Slog.i(TAG, "Beginning scheduled full backup operation");
        }

        // Great; we're able to run full backup jobs now.  See if we have any work to do.
        synchronized (mQueueLock) {
            if (mRunningFullBackupTask != null) {
                Slog.e(TAG, "Backup triggered but one already/still running!");
                return false;
            }

            if (mFullBackupQueue.size() == 0) {
                // no work to do so just bow out
                if (DEBUG) {
                    Slog.i(TAG, "Backup queue empty; doing nothing");
                }
                return false;
            }

            entry = mFullBackupQueue.get(0);
            long timeSinceRun = now - entry.lastBackup;
            if (timeSinceRun < MIN_FULL_BACKUP_INTERVAL) {
                // It's too early to back up the next thing in the queue, so bow out
                if (MORE_DEBUG) {
                    Slog.i(TAG, "Device ready but too early to back up next app");
                }
                final long latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun;
                mBackupHandler.post(new Runnable() {
                    @Override public void run() {
                        FullBackupJob.schedule(mContext, latency);
                    }
                });
                return false;
            }

            // Okay, the top thing is runnable now.  Pop it off and get going.
            mFullBackupQueue.remove(0);
            AtomicBoolean latch = new AtomicBoolean(false);
            String[] pkg = new String[] {entry.packageName};
            mRunningFullBackupTask = new PerformFullTransportBackupTask(null, pkg, true,
                    scheduledJob, latch);
            (new Thread(mRunningFullBackupTask)).start();
        }

        return true;
    
public android.app.backup.IRestoreSessionbeginRestoreSession(java.lang.String packageName, java.lang.String transport)

        if (DEBUG) Slog.v(TAG, "beginRestoreSession: pkg=" + packageName
                + " transport=" + transport);

        boolean needPermission = true;
        if (transport == null) {
            transport = mCurrentTransport;

            if (packageName != null) {
                PackageInfo app = null;
                try {
                    app = mPackageManager.getPackageInfo(packageName, 0);
                } catch (NameNotFoundException nnf) {
                    Slog.w(TAG, "Asked to restore nonexistent pkg " + packageName);
                    throw new IllegalArgumentException("Package " + packageName + " not found");
                }

                if (app.applicationInfo.uid == Binder.getCallingUid()) {
                    // So: using the current active transport, and the caller has asked
                    // that its own package will be restored.  In this narrow use case
                    // we do not require the caller to hold the permission.
                    needPermission = false;
                }
            }
        }

        if (needPermission) {
            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                    "beginRestoreSession");
        } else {
            if (DEBUG) Slog.d(TAG, "restoring self on current transport; no permission needed");
        }

        synchronized(this) {
            if (mActiveRestoreSession != null) {
                Slog.d(TAG, "Restore session requested but one already active");
                return null;
            }
            mActiveRestoreSession = new ActiveRestoreSession(packageName, transport);
            mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT, TIMEOUT_RESTORE_INTERVAL);
        }
        return mActiveRestoreSession;
    
android.app.IBackupAgentbindToAgentSynchronous(android.content.pm.ApplicationInfo app, int mode)

        IBackupAgent agent = null;
        synchronized(mAgentConnectLock) {
            mConnecting = true;
            mConnectedAgent = null;
            try {
                if (mActivityManager.bindBackupAgent(app, mode)) {
                    Slog.d(TAG, "awaiting agent for " + app);

                    // success; wait for the agent to arrive
                    // only wait 10 seconds for the bind to happen
                    long timeoutMark = System.currentTimeMillis() + TIMEOUT_INTERVAL;
                    while (mConnecting && mConnectedAgent == null
                            && (System.currentTimeMillis() < timeoutMark)) {
                        try {
                            mAgentConnectLock.wait(5000);
                        } catch (InterruptedException e) {
                            // just bail
                            if (DEBUG) Slog.w(TAG, "Interrupted: " + e);
                            mActivityManager.clearPendingBackup();
                            return null;
                        }
                    }

                    // if we timed out with no connect, abort and move on
                    if (mConnecting == true) {
                        Slog.w(TAG, "Timeout waiting for agent " + app);
                        mActivityManager.clearPendingBackup();
                        return null;
                    }
                    if (DEBUG) Slog.i(TAG, "got agent " + mConnectedAgent);
                    agent = mConnectedAgent;
                }
            } catch (RemoteException e) {
                // can't happen - ActivityManager is local
            }
        }
        return agent;
    
booleanbindTransport(android.content.pm.ServiceInfo transport)

        ComponentName svcName = new ComponentName(transport.packageName, transport.name);
        if (DEBUG) {
            Slog.i(TAG, "Binding to transport host " + svcName);
        }
        Intent intent = new Intent(mTransportServiceIntent);
        intent.setComponent(svcName);

        TransportConnection connection;
        synchronized (mTransports) {
            connection = mTransportConnections.get(transport.packageName);
            if (null == connection) {
                connection = new TransportConnection(transport);
                mTransportConnections.put(transport.packageName, connection);
            } else {
                // This is a rebind due to package upgrade.  The service won't be
                // automatically relaunched for us until we explicitly rebind, but
                // we need to unbind the now-orphaned original connection.
                mContext.unbindService(connection);
            }
        }
        return mContext.bindServiceAsUser(intent,
                connection, Context.BIND_AUTO_CREATE,
                UserHandle.OWNER);
    
private javax.crypto.SecretKeybuildCharArrayKey(java.lang.String algorithm, char[] pwArray, byte[] salt, int rounds)

        try {
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
            KeySpec ks = new PBEKeySpec(pwArray, salt, rounds, PBKDF2_KEY_SIZE);
            return keyFactory.generateSecret(ks);
        } catch (InvalidKeySpecException e) {
            Slog.e(TAG, "Invalid key spec for PBKDF2!");
        } catch (NoSuchAlgorithmException e) {
            Slog.e(TAG, "PBKDF2 unavailable!");
        }
        return null;
    
private java.lang.StringbuildPasswordHash(java.lang.String algorithm, java.lang.String pw, byte[] salt, int rounds)

        SecretKey key = buildPasswordKey(algorithm, pw, salt, rounds);
        if (key != null) {
            return byteArrayToHex(key.getEncoded());
        }
        return null;
    
private javax.crypto.SecretKeybuildPasswordKey(java.lang.String algorithm, java.lang.String pw, byte[] salt, int rounds)

        return buildCharArrayKey(algorithm, pw.toCharArray(), salt, rounds);
    
private java.lang.StringbyteArrayToHex(byte[] data)

        StringBuilder buf = new StringBuilder(data.length * 2);
        for (int i = 0; i < data.length; i++) {
            buf.append(Byte.toHexString(data[i], true));
        }
        return buf.toString();
    
voidcheckForTransportAndBind(android.content.pm.PackageInfo pkgInfo)

        Intent intent = new Intent(mTransportServiceIntent)
                .setPackage(pkgInfo.packageName);
        List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
                intent, 0, UserHandle.USER_OWNER);
        final int N = hosts.size();
        for (int i = 0; i < N; i++) {
            final ServiceInfo info = hosts.get(i).serviceInfo;
            tryBindTransport(info);
        }
    
voidclearApplicationDataSynchronous(java.lang.String packageName)

        // Don't wipe packages marked allowClearUserData=false
        try {
            PackageInfo info = mPackageManager.getPackageInfo(packageName, 0);
            if ((info.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) == 0) {
                if (MORE_DEBUG) Slog.i(TAG, "allowClearUserData=false so not wiping "
                        + packageName);
                return;
            }
        } catch (NameNotFoundException e) {
            Slog.w(TAG, "Tried to clear data for " + packageName + " but not found");
            return;
        }

        ClearDataObserver observer = new ClearDataObserver();

        synchronized(mClearDataLock) {
            mClearingData = true;
            try {
                mActivityManager.clearApplicationUserData(packageName, observer, 0);
            } catch (RemoteException e) {
                // can't happen because the activity manager is in this process
            }

            // only wait 10 seconds for the clear data to happen
            long timeoutMark = System.currentTimeMillis() + TIMEOUT_INTERVAL;
            while (mClearingData && (System.currentTimeMillis() < timeoutMark)) {
                try {
                    mClearDataLock.wait(5000);
                } catch (InterruptedException e) {
                    // won't happen, but still.
                    mClearingData = false;
                }
            }
        }
    
public voidclearBackupData(java.lang.String transportName, java.lang.String packageName)

        if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName + " on " + transportName);
        PackageInfo info;
        try {
            info = mPackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
        } catch (NameNotFoundException e) {
            Slog.d(TAG, "No such package '" + packageName + "' - not clearing backup data");
            return;
        }

        // If the caller does not hold the BACKUP permission, it can only request a
        // wipe of its own backed-up data.
        HashSet<String> apps;
        if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(),
                Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) {
            apps = mBackupParticipants.get(Binder.getCallingUid());
        } else {
            // a caller with full permission can ask to back up any participating app
            // !!! TODO: allow data-clear of ANY app?
            if (DEBUG) Slog.v(TAG, "Privileged caller, allowing clear of other apps");
            apps = new HashSet<String>();
            int N = mBackupParticipants.size();
            for (int i = 0; i < N; i++) {
                HashSet<String> s = mBackupParticipants.valueAt(i);
                if (s != null) {
                    apps.addAll(s);
                }
            }
        }

        // Is the given app an available participant?
        if (apps.contains(packageName)) {
            // found it; fire off the clear request
            if (DEBUG) Slog.v(TAG, "Found the app - running clear process");
            mBackupHandler.removeMessages(MSG_RETRY_CLEAR);
            synchronized (mQueueLock) {
                final IBackupTransport transport = getTransport(transportName);
                if (transport == null) {
                    // transport is currently unavailable -- make sure to retry
                    Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR,
                            new ClearRetryParams(transportName, packageName));
                    mBackupHandler.sendMessageDelayed(msg, TRANSPORT_RETRY_INTERVAL);
                    return;
                }
                long oldId = Binder.clearCallingIdentity();
                mWakelock.acquire();
                Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR,
                        new ClearParams(transport, info));
                mBackupHandler.sendMessage(msg);
                Binder.restoreCallingIdentity(oldId);
            }
        }
    
voidclearBackupTrace()

        if (DEBUG_BACKUP_TRACE) {
            synchronized (mBackupTrace) {
                mBackupTrace.clear();
            }
        }
    
voidclearRestoreSession(com.android.server.backup.BackupManagerService$ActiveRestoreSession currentSession)

        synchronized(this) {
            if (currentSession != mActiveRestoreSession) {
                Slog.e(TAG, "ending non-current restore session");
            } else {
                if (DEBUG) Slog.v(TAG, "Clearing restore session and halting timeout");
                mActiveRestoreSession = null;
                mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
            }
        }
    
public voiddataChanged(java.lang.String packageName)

        final int callingUserHandle = UserHandle.getCallingUserId();
        if (callingUserHandle != UserHandle.USER_OWNER) {
            // App is running under a non-owner user profile.  For now, we do not back
            // up data from secondary user profiles.
            // TODO: backups for all user profiles.
            if (MORE_DEBUG) {
                Slog.v(TAG, "dataChanged(" + packageName + ") ignored because it's user "
                        + callingUserHandle);
            }
            return;
        }

        final HashSet<String> targets = dataChangedTargets(packageName);
        if (targets == null) {
            Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
                   + " uid=" + Binder.getCallingUid());
            return;
        }

        mBackupHandler.post(new Runnable() {
                public void run() {
                    dataChangedImpl(packageName, targets);
                }
            });
    
private voiddataChangedImpl(java.lang.String packageName)

        HashSet<String> targets = dataChangedTargets(packageName);
        dataChangedImpl(packageName, targets);
    
private voiddataChangedImpl(java.lang.String packageName, java.util.HashSet targets)

        // Record that we need a backup pass for the caller.  Since multiple callers
        // may share a uid, we need to note all candidates within that uid and schedule
        // a backup pass for each of them.
        if (targets == null) {
            Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
                   + " uid=" + Binder.getCallingUid());
            return;
        }

        synchronized (mQueueLock) {
            // Note that this client has made data changes that need to be backed up
            if (targets.contains(packageName)) {
                // Add the caller to the set of pending backups.  If there is
                // one already there, then overwrite it, but no harm done.
                BackupRequest req = new BackupRequest(packageName);
                if (mPendingBackups.put(packageName, req) == null) {
                    if (DEBUG) Slog.d(TAG, "Now staging backup of " + packageName);

                    // Journal this request in case of crash.  The put()
                    // operation returned null when this package was not already
                    // in the set; we want to avoid touching the disk redundantly.
                    writeToJournalLocked(packageName);

                    if (MORE_DEBUG) {
                        int numKeys = mPendingBackups.size();
                        Slog.d(TAG, "Now awaiting backup for " + numKeys + " participants:");
                        for (BackupRequest b : mPendingBackups.values()) {
                            Slog.d(TAG, "    + " + b);
                        }
                    }
                }
            }
        }
    
private java.util.HashSetdataChangedTargets(java.lang.String packageName)

        // If the caller does not hold the BACKUP permission, it can only request a
        // backup of its own data.
        if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(),
                Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) {
            synchronized (mBackupParticipants) {
                return mBackupParticipants.get(Binder.getCallingUid());
            }
        }

        // a caller with full permission can ask to back up any participating app
        // !!! TODO: allow backup of ANY app?
        HashSet<String> targets = new HashSet<String>();
        synchronized (mBackupParticipants) {
            int N = mBackupParticipants.size();
            for (int i = 0; i < N; i++) {
                HashSet<String> s = mBackupParticipants.valueAt(i);
                if (s != null) {
                    targets.addAll(s);
                }
            }
        }
        return targets;
    
booleandeviceIsEncrypted()

        try {
            return mMountService.getEncryptionState()
                     != IMountService.ENCRYPTION_STATE_NONE
                && mMountService.getPasswordType()
                     != StorageManager.CRYPT_TYPE_DEFAULT;
        } catch (Exception e) {
            // If we can't talk to the mount service we have a serious problem; fail
            // "secure" i.e. assuming that the device is encrypted.
            Slog.e(TAG, "Unable to communicate with mount service: " + e.getMessage());
            return true;
        }
    
booleandeviceIsProvisioned()

        final ContentResolver resolver = mContext.getContentResolver();
        return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0);
    
public voiddump(java.io.FileDescriptor fd, java.io.PrintWriter pw, java.lang.String[] args)

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);

        long identityToken = Binder.clearCallingIdentity();
        try {
            if (args != null) {
                for (String arg : args) {
                    if ("-h".equals(arg)) {
                        pw.println("'dumpsys backup' optional arguments:");
                        pw.println("  -h       : this help text");
                        pw.println("  a[gents] : dump information about defined backup agents");
                        return;
                    } else if ("agents".startsWith(arg)) {
                        dumpAgents(pw);
                        return;
                    }
                }
            }
            dumpInternal(pw);
        } finally {
            Binder.restoreCallingIdentity(identityToken);
        }
    
private voiddumpAgents(java.io.PrintWriter pw)

        List<PackageInfo> agentPackages = allAgentPackages();
        pw.println("Defined backup agents:");
        for (PackageInfo pkg : agentPackages) {
            pw.print("  ");
            pw.print(pkg.packageName); pw.println(':");
            pw.print("      "); pw.println(pkg.applicationInfo.backupAgentName);
        }
    
private voiddumpInternal(java.io.PrintWriter pw)

        synchronized (mQueueLock) {
            pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled")
                    + " / " + (!mProvisioned ? "not " : "") + "provisioned / "
                    + (this.mPendingInits.size() == 0 ? "not " : "") + "pending init");
            pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled"));
            if (mBackupRunning) pw.println("Backup currently running");
            pw.println("Last backup pass started: " + mLastBackupPass
                    + " (now = " + System.currentTimeMillis() + ')");
            pw.println("  next scheduled: " + mNextBackupPass);

            pw.println("Available transports:");
            final String[] transports = listAllTransports();
            if (transports != null) {
                for (String t : listAllTransports()) {
                    pw.println((t.equals(mCurrentTransport) ? "  * " : "    ") + t);
                    try {
                        IBackupTransport transport = getTransport(t);
                        File dir = new File(mBaseStateDir, transport.transportDirName());
                        pw.println("       destination: " + transport.currentDestinationString());
                        pw.println("       intent: " + transport.configurationIntent());
                        for (File f : dir.listFiles()) {
                            pw.println("       " + f.getName() + " - " + f.length() + " state bytes");
                        }
                    } catch (Exception e) {
                        Slog.e(TAG, "Error in transport", e);
                        pw.println("        Error: " + e);
                    }
                }
            }

            pw.println("Pending init: " + mPendingInits.size());
            for (String s : mPendingInits) {
                pw.println("    " + s);
            }

            if (DEBUG_BACKUP_TRACE) {
                synchronized (mBackupTrace) {
                    if (!mBackupTrace.isEmpty()) {
                        pw.println("Most recent backup trace:");
                        for (String s : mBackupTrace) {
                            pw.println("   " + s);
                        }
                    }
                }
            }

            int N = mBackupParticipants.size();
            pw.println("Participants:");
            for (int i=0; i<N; i++) {
                int uid = mBackupParticipants.keyAt(i);
                pw.print("  uid: ");
                pw.println(uid);
                HashSet<String> participants = mBackupParticipants.valueAt(i);
                for (String app: participants) {
                    pw.println("    " + app);
                }
            }

            pw.println("Ancestral packages: "
                    + (mAncestralPackages == null ? "none" : mAncestralPackages.size()));
            if (mAncestralPackages != null) {
                for (String pkg : mAncestralPackages) {
                    pw.println("    " + pkg);
                }
            }

            pw.println("Ever backed up: " + mEverStoredApps.size());
            for (String pkg : mEverStoredApps) {
                pw.println("    " + pkg);
            }

            pw.println("Pending key/value backup: " + mPendingBackups.size());
            for (BackupRequest req : mPendingBackups.values()) {
                pw.println("    " + req);
            }

            pw.println("Full backup queue:" + mFullBackupQueue.size());
            for (FullBackupEntry entry : mFullBackupQueue) {
                pw.print("    "); pw.print(entry.lastBackup);
                pw.print(" : "); pw.println(entry.packageName);
            }
        }
    
voidendFullBackup()

        synchronized (mQueueLock) {
            if (mRunningFullBackupTask != null) {
                if (DEBUG_SCHEDULING) {
                    Slog.i(TAG, "Telling running backup to stop");
                }
                mRunningFullBackupTask.setRunning(false);
            }
        }
    
voidenqueueFullBackup(java.lang.String packageName, long lastBackedUp)
Enqueue full backup for the given app, with a note about when it last ran.

        FullBackupEntry newEntry = new FullBackupEntry(packageName, lastBackedUp);
        synchronized (mQueueLock) {
            int N = mFullBackupQueue.size();
            // First, sanity check that we aren't adding a duplicate.  Slow but
            // straightforward; we'll have at most on the order of a few hundred
            // items in this list.
            for (int i = N-1; i >= 0; i--) {
                final FullBackupEntry e = mFullBackupQueue.get(i);
                if (packageName.equals(e.packageName)) {
                    if (DEBUG) {
                        Slog.w(TAG, "Removing schedule queue dupe of " + packageName);
                    }
                    mFullBackupQueue.remove(i);
                }
            }

            // This is also slow but easy for modest numbers of apps: work backwards
            // from the end of the queue until we find an item whose last backup
            // time was before this one, then insert this new entry after it.
            int which;
            for (which = mFullBackupQueue.size() - 1; which >= 0; which--) {
                final FullBackupEntry entry = mFullBackupQueue.get(which);
                if (entry.lastBackup <= lastBackedUp) {
                    mFullBackupQueue.add(which + 1, newEntry);
                    break;
                }
            }
            if (which < 0) {
                // this one is earlier than any existing one, so prepend
                mFullBackupQueue.add(0, newEntry);
            }
        }
        writeFullBackupScheduleAsync();
    
public voidfullBackup(android.os.ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem, boolean compress, java.lang.String[] pkgList)

        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup");

        final int callingUserHandle = UserHandle.getCallingUserId();
        if (callingUserHandle != UserHandle.USER_OWNER) {
            throw new IllegalStateException("Backup supported only for the device owner");
        }

        // Validate
        if (!doAllApps) {
            if (!includeShared) {
                // If we're backing up shared data (sdcard or equivalent), then we can run
                // without any supplied app names.  Otherwise, we'd be doing no work, so
                // report the error.
                if (pkgList == null || pkgList.length == 0) {
                    throw new IllegalArgumentException(
                            "Backup requested but neither shared nor any apps named");
                }
            }
        }

        long oldId = Binder.clearCallingIdentity();
        try {
            // Doesn't make sense to do a full backup prior to setup
            if (!deviceIsProvisioned()) {
                Slog.i(TAG, "Full backup not supported before setup");
                return;
            }

            if (DEBUG) Slog.v(TAG, "Requesting full backup: apks=" + includeApks
                    + " obb=" + includeObbs + " shared=" + includeShared + " all=" + doAllApps
                    + " system=" + includeSystem + " pkgs=" + pkgList);
            Slog.i(TAG, "Beginning full backup...");

            FullBackupParams params = new FullBackupParams(fd, includeApks, includeObbs,
                    includeShared, doWidgets, doAllApps, includeSystem, compress, pkgList);
            final int token = generateToken();
            synchronized (mFullConfirmations) {
                mFullConfirmations.put(token, params);
            }

            // start up the confirmation UI
            if (DEBUG) Slog.d(TAG, "Starting backup confirmation UI, token=" + token);
            if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) {
                Slog.e(TAG, "Unable to launch full backup confirmation");
                mFullConfirmations.delete(token);
                return;
            }

            // make sure the screen is lit for the user interaction
            mPowerManager.userActivity(SystemClock.uptimeMillis(), false);

            // start the confirmation countdown
            startConfirmationTimeout(token, params);

            // wait for the backup to be performed
            if (DEBUG) Slog.d(TAG, "Waiting for full backup completion...");
            waitForCompletion(params);
        } finally {
            try {
                fd.close();
            } catch (IOException e) {
                // just eat it
            }
            Binder.restoreCallingIdentity(oldId);
            Slog.d(TAG, "Full backup processing complete.");
        }
    
public voidfullRestore(android.os.ParcelFileDescriptor fd)

        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullRestore");

        final int callingUserHandle = UserHandle.getCallingUserId();
        if (callingUserHandle != UserHandle.USER_OWNER) {
            throw new IllegalStateException("Restore supported only for the device owner");
        }

        long oldId = Binder.clearCallingIdentity();

        try {
            // Check whether the device has been provisioned -- we don't handle
            // full restores prior to completing the setup process.
            if (!deviceIsProvisioned()) {
                Slog.i(TAG, "Full restore not permitted before setup");
                return;
            }

            Slog.i(TAG, "Beginning full restore...");

            FullRestoreParams params = new FullRestoreParams(fd);
            final int token = generateToken();
            synchronized (mFullConfirmations) {
                mFullConfirmations.put(token, params);
            }

            // start up the confirmation UI
            if (DEBUG) Slog.d(TAG, "Starting restore confirmation UI, token=" + token);
            if (!startConfirmationUi(token, FullBackup.FULL_RESTORE_INTENT_ACTION)) {
                Slog.e(TAG, "Unable to launch full restore confirmation");
                mFullConfirmations.delete(token);
                return;
            }

            // make sure the screen is lit for the user interaction
            mPowerManager.userActivity(SystemClock.uptimeMillis(), false);

            // start the confirmation countdown
            startConfirmationTimeout(token, params);

            // wait for the restore to be performed
            if (DEBUG) Slog.d(TAG, "Waiting for full restore completion...");
            waitForCompletion(params);
        } finally {
            try {
                fd.close();
            } catch (IOException e) {
                Slog.w(TAG, "Error trying to close fd after full restore: " + e);
            }
            Binder.restoreCallingIdentity(oldId);
            Slog.i(TAG, "Full restore processing complete.");
        }
    
public voidfullTransportBackup(java.lang.String[] pkgNames)

        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
                "fullTransportBackup");

        final int callingUserHandle = UserHandle.getCallingUserId();
        if (callingUserHandle != UserHandle.USER_OWNER) {
            throw new IllegalStateException("Restore supported only for the device owner");
        }

        if (DEBUG) {
            Slog.d(TAG, "fullTransportBackup()");
        }

        AtomicBoolean latch = new AtomicBoolean(false);
        PerformFullTransportBackupTask task =
                new PerformFullTransportBackupTask(null, pkgNames, false, null, latch);
        (new Thread(task, "full-transport-master")).start();
        synchronized (latch) {
            try {
                while (latch.get() == false) {
                    latch.wait();
                }
            } catch (InterruptedException e) {}
        }
        if (DEBUG) {
            Slog.d(TAG, "Done with full transport backup.");
        }
    
intgenerateToken()

        int token;
        do {
            synchronized (mTokenGenerator) {
                token = mTokenGenerator.nextInt();
            }
        } while (token < 0);
        return token;
    
longgetAvailableRestoreToken(java.lang.String packageName)

        long token = mAncestralToken;
        synchronized (mQueueLock) {
            if (mEverStoredApps.contains(packageName)) {
                token = mCurrentToken;
            }
        }
        return token;
    
public android.content.IntentgetConfigurationIntent(java.lang.String transportName)

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                "getConfigurationIntent");

        synchronized (mTransports) {
            final IBackupTransport transport = mTransports.get(transportName);
            if (transport != null) {
                try {
                    final Intent intent = transport.configurationIntent();
                    if (MORE_DEBUG) Slog.d(TAG, "getConfigurationIntent() returning config intent "
                            + intent);
                    return intent;
                } catch (RemoteException e) {
                    /* fall through to return null */
                }
            }
        }

        return null;
    
public java.lang.StringgetCurrentTransport()

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                "getCurrentTransport");
        if (MORE_DEBUG) Slog.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport);
        return mCurrentTransport;
    
public android.content.IntentgetDataManagementIntent(java.lang.String transportName)

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                "getDataManagementIntent");

        synchronized (mTransports) {
            final IBackupTransport transport = mTransports.get(transportName);
            if (transport != null) {
                try {
                    final Intent intent = transport.dataManagementIntent();
                    if (MORE_DEBUG) Slog.d(TAG, "getDataManagementIntent() returning intent "
                            + intent);
                    return intent;
                } catch (RemoteException e) {
                    /* fall through to return null */
                }
            }
        }

        return null;
    
public java.lang.StringgetDataManagementLabel(java.lang.String transportName)

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                "getDataManagementLabel");

        synchronized (mTransports) {
            final IBackupTransport transport = mTransports.get(transportName);
            if (transport != null) {
                try {
                    final String text = transport.dataManagementLabel();
                    if (MORE_DEBUG) Slog.d(TAG, "getDataManagementLabel() returning " + text);
                    return text;
                } catch (RemoteException e) {
                    /* fall through to return null */
                }
            }
        }

        return null;
    
public java.lang.StringgetDestinationString(java.lang.String transportName)

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                "getDestinationString");

        synchronized (mTransports) {
            final IBackupTransport transport = mTransports.get(transportName);
            if (transport != null) {
                try {
                    final String text = transport.currentDestinationString();
                    if (MORE_DEBUG) Slog.d(TAG, "getDestinationString() returning " + text);
                    return text;
                } catch (RemoteException e) {
                    /* fall through to return null */
                }
            }
        }

        return null;
    
static TrampolinegetInstance()

       
        // Always constructed during system bringup, so no need to lazy-init
        return sInstance;
    
private com.android.internal.backup.IBackupTransportgetTransport(java.lang.String transportName)

        synchronized (mTransports) {
            IBackupTransport transport = mTransports.get(transportName);
            if (transport == null) {
                Slog.w(TAG, "Requested unavailable transport: " + transportName);
            }
            return transport;
        }
    
voidhandleTimeout(int token, java.lang.Object obj)

        // Notify any synchronous waiters
        Operation op = null;
        synchronized (mCurrentOpLock) {
            op = mCurrentOperations.get(token);
            if (MORE_DEBUG) {
                if (op == null) Slog.w(TAG, "Timeout of token " + Integer.toHexString(token)
                        + " but no op found");
            }
            int state = (op != null) ? op.state : OP_TIMEOUT;
            if (state == OP_PENDING) {
                if (DEBUG) Slog.v(TAG, "TIMEOUT: token=" + Integer.toHexString(token));
                op.state = OP_TIMEOUT;
                mCurrentOperations.put(token, op);
            }
            mCurrentOpLock.notifyAll();
        }

        // If there's a TimeoutHandler for this event, call it
        if (op != null && op.callback != null) {
            op.callback.handleTimeout();
        }
    
public booleanhasBackupPassword()

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                "hasBackupPassword");

        return mPasswordHash != null && mPasswordHash.length() > 0;
    
static byte[]hashSignature(android.content.pm.Signature sig)

        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            digest.update(sig.toByteArray());
            return digest.digest();
        } catch (NoSuchAlgorithmException e) {
            Slog.w(TAG, "No SHA-256 algorithm found!");
        }
        return null;
    
private byte[]hexToByteArray(java.lang.String digits)

        final int bytes = digits.length() / 2;
        if (2*bytes != digits.length()) {
            throw new IllegalArgumentException("Hex string must have an even number of digits");
        }

        byte[] result = new byte[bytes];
        for (int i = 0; i < digits.length(); i += 2) {
            result[i/2] = (byte) Integer.parseInt(digits.substring(i, i+2), 16);
        }
        return result;
    
private voidinitPackageTracking()

        if (MORE_DEBUG) Slog.v(TAG, "` tracking");

        // Remember our ancestral dataset
        mTokenFile = new File(mBaseStateDir, "ancestral");
        try {
            RandomAccessFile tf = new RandomAccessFile(mTokenFile, "r");
            int version = tf.readInt();
            if (version == CURRENT_ANCESTRAL_RECORD_VERSION) {
                mAncestralToken = tf.readLong();
                mCurrentToken = tf.readLong();

                int numPackages = tf.readInt();
                if (numPackages >= 0) {
                    mAncestralPackages = new HashSet<String>();
                    for (int i = 0; i < numPackages; i++) {
                        String pkgName = tf.readUTF();
                        mAncestralPackages.add(pkgName);
                    }
                }
            }
            tf.close();
        } catch (FileNotFoundException fnf) {
            // Probably innocuous
            Slog.v(TAG, "No ancestral data");
        } catch (IOException e) {
            Slog.w(TAG, "Unable to read token file", e);
        }

        // Keep a log of what apps we've ever backed up.  Because we might have
        // rebooted in the middle of an operation that was removing something from
        // this log, we sanity-check its contents here and reconstruct it.
        mEverStored = new File(mBaseStateDir, "processed");
        File tempProcessedFile = new File(mBaseStateDir, "processed.new");

        // If we were in the middle of removing something from the ever-backed-up
        // file, there might be a transient "processed.new" file still present.
        // Ignore it -- we'll validate "processed" against the current package set.
        if (tempProcessedFile.exists()) {
            tempProcessedFile.delete();
        }

        // If there are previous contents, parse them out then start a new
        // file to continue the recordkeeping.
        if (mEverStored.exists()) {
            RandomAccessFile temp = null;
            RandomAccessFile in = null;

            try {
                temp = new RandomAccessFile(tempProcessedFile, "rws");
                in = new RandomAccessFile(mEverStored, "r");

                while (true) {
                    PackageInfo info;
                    String pkg = in.readUTF();
                    try {
                        info = mPackageManager.getPackageInfo(pkg, 0);
                        mEverStoredApps.add(pkg);
                        temp.writeUTF(pkg);
                        if (MORE_DEBUG) Slog.v(TAG, "   + " + pkg);
                    } catch (NameNotFoundException e) {
                        // nope, this package was uninstalled; don't include it
                        if (MORE_DEBUG) Slog.v(TAG, "   - " + pkg);
                    }
                }
            } catch (EOFException e) {
                // Once we've rewritten the backup history log, atomically replace the
                // old one with the new one then reopen the file for continuing use.
                if (!tempProcessedFile.renameTo(mEverStored)) {
                    Slog.e(TAG, "Error renaming " + tempProcessedFile + " to " + mEverStored);
                }
            } catch (IOException e) {
                Slog.e(TAG, "Error in processed file", e);
            } finally {
                try { if (temp != null) temp.close(); } catch (IOException e) {}
                try { if (in != null) in.close(); } catch (IOException e) {}
            }
        }

        // Resume the full-data backup queue
        mFullBackupQueue = readFullBackupSchedule();

        // Register for broadcasts about package install, etc., so we can
        // update the provider list.
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addDataScheme("package");
        mContext.registerReceiver(mBroadcastReceiver, filter);
        // Register for events related to sdcard installation.
        IntentFilter sdFilter = new IntentFilter();
        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
        mContext.registerReceiver(mBroadcastReceiver, sdFilter);
    
public booleanisBackupEnabled()

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "isBackupEnabled");
        return mEnabled;    // no need to synchronize just to read it
    
public java.lang.String[]listAllTransports()

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransports");

        String[] list = null;
        ArrayList<String> known = new ArrayList<String>();
        for (Map.Entry<String, IBackupTransport> entry : mTransports.entrySet()) {
            if (entry.getValue() != null) {
                known.add(entry.getKey());
            }
        }

        if (known.size() > 0) {
            list = new String[known.size()];
            known.toArray(list);
        }
        return list;
    
voidlogBackupComplete(java.lang.String packageName)

        if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) return;

        synchronized (mEverStoredApps) {
            if (!mEverStoredApps.add(packageName)) return;

            RandomAccessFile out = null;
            try {
                out = new RandomAccessFile(mEverStored, "rws");
                out.seek(out.length());
                out.writeUTF(packageName);
            } catch (IOException e) {
                Slog.e(TAG, "Can't log backup of " + packageName + " to " + mEverStored);
            } finally {
                try { if (out != null) out.close(); } catch (IOException e) {}
            }
        }
    
private byte[]makeKeyChecksum(java.lang.String algorithm, byte[] pwBytes, byte[] salt, int rounds)

        char[] mkAsChar = new char[pwBytes.length];
        for (int i = 0; i < pwBytes.length; i++) {
            mkAsChar[i] = (char) pwBytes[i];
        }

        Key checksum = buildCharArrayKey(algorithm, mkAsChar, salt, rounds);
        return checksum.getEncoded();
    
public voidopComplete(int token)

        if (MORE_DEBUG) Slog.v(TAG, "opComplete: " + Integer.toHexString(token));
        Operation op = null;
        synchronized (mCurrentOpLock) {
            op = mCurrentOperations.get(token);
            if (op != null) {
                op.state = OP_ACKNOWLEDGED;
            }
            mCurrentOpLock.notifyAll();
        }

        // The completion callback, if any, is invoked on the handler
        if (op != null && op.callback != null) {
            Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, op.callback);
            mBackupHandler.sendMessage(msg);
        }
    
private voidparseLeftoverJournals()

        for (File f : mJournalDir.listFiles()) {
            if (mJournal == null || f.compareTo(mJournal) != 0) {
                // This isn't the current journal, so it must be a leftover.  Read
                // out the package names mentioned there and schedule them for
                // backup.
                RandomAccessFile in = null;
                try {
                    Slog.i(TAG, "Found stale backup journal, scheduling");
                    in = new RandomAccessFile(f, "r");
                    while (true) {
                        String packageName = in.readUTF();
                        if (MORE_DEBUG) Slog.i(TAG, "  " + packageName);
                        dataChangedImpl(packageName);
                    }
                } catch (EOFException e) {
                    // no more data; we're done
                } catch (Exception e) {
                    Slog.e(TAG, "Can't read " + f, e);
                } finally {
                    // close/delete the file
                    try { if (in != null) in.close(); } catch (IOException e) {}
                    f.delete();
                }
            }
        }
    
booleanpasswordMatchesSaved(java.lang.String algorithm, java.lang.String candidatePw, int rounds)

        if (mPasswordHash == null) {
            // no current password case -- require that 'currentPw' be null or empty
            if (candidatePw == null || "".equals(candidatePw)) {
                return true;
            } // else the non-empty candidate does not match the empty stored pw
        } else {
            // hash the stated current pw and compare to the stored one
            if (candidatePw != null && candidatePw.length() > 0) {
                String currentPwHash = buildPasswordHash(algorithm, candidatePw, mPasswordSalt, rounds);
                if (mPasswordHash.equalsIgnoreCase(currentPwHash)) {
                    // candidate hash matches the stored hash -- the password matches
                    return true;
                }
            } // else the stored pw is nonempty but the candidate is empty; no match
        }
        return false;
    
voidprepareOperationTimeout(int token, long interval, com.android.server.backup.BackupManagerService$BackupRestoreTask callback)

        if (MORE_DEBUG) Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
                + " interval=" + interval);
        synchronized (mCurrentOpLock) {
            mCurrentOperations.put(token, new Operation(OP_PENDING, callback));

            Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0, callback);
            mBackupHandler.sendMessageDelayed(msg, interval);
        }
    
private byte[]randomBytes(int bits)

        byte[] array = new byte[bits / 8];
        mRng.nextBytes(array);
        return array;
    
private java.util.ArrayListreadFullBackupSchedule()

        ArrayList<FullBackupEntry> schedule = null;
        synchronized (mQueueLock) {
            if (mFullBackupScheduleFile.exists()) {
                FileInputStream fstream = null;
                BufferedInputStream bufStream = null;
                DataInputStream in = null;
                try {
                    fstream = new FileInputStream(mFullBackupScheduleFile);
                    bufStream = new BufferedInputStream(fstream);
                    in = new DataInputStream(bufStream);

                    int version = in.readInt();
                    if (version != SCHEDULE_FILE_VERSION) {
                        Slog.e(TAG, "Unknown backup schedule version " + version);
                        return null;
                    }

                    int N = in.readInt();
                    schedule = new ArrayList<FullBackupEntry>(N);
                    for (int i = 0; i < N; i++) {
                        String pkgName = in.readUTF();
                        long lastBackup = in.readLong();
                        schedule.add(new FullBackupEntry(pkgName, lastBackup));
                    }
                    Collections.sort(schedule);
                } catch (Exception e) {
                    Slog.e(TAG, "Unable to read backup schedule", e);
                    mFullBackupScheduleFile.delete();
                    schedule = null;
                } finally {
                    IoUtils.closeQuietly(in);
                    IoUtils.closeQuietly(bufStream);
                    IoUtils.closeQuietly(fstream);
                }
            }

            if (schedule == null) {
                // no prior queue record, or unable to read it.  Set up the queue
                // from scratch.
                List<PackageInfo> apps =
                        PackageManagerBackupAgent.getStorableApplications(mPackageManager);
                final int N = apps.size();
                schedule = new ArrayList<FullBackupEntry>(N);
                for (int i = 0; i < N; i++) {
                    PackageInfo info = apps.get(i);
                    if (appGetsFullBackup(info)) {
                        schedule.add(new FullBackupEntry(info.packageName, 0));
                    }
                }
                writeFullBackupScheduleAsync();
            }
        }
        return schedule;
    
voidrecordInitPendingLocked(boolean isPending, java.lang.String transportName)

        if (DEBUG) Slog.i(TAG, "recordInitPendingLocked: " + isPending
                + " on transport " + transportName);
        mBackupHandler.removeMessages(MSG_RETRY_INIT);

        try {
            IBackupTransport transport = getTransport(transportName);
            if (transport != null) {
                String transportDirName = transport.transportDirName();
                File stateDir = new File(mBaseStateDir, transportDirName);
                File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME);

                if (isPending) {
                    // We need an init before we can proceed with sending backup data.
                    // Record that with an entry in our set of pending inits, as well as
                    // journaling it via creation of a sentinel file.
                    mPendingInits.add(transportName);
                    try {
                        (new FileOutputStream(initPendingFile)).close();
                    } catch (IOException ioe) {
                        // Something is badly wrong with our permissions; just try to move on
                    }
                } else {
                    // No more initialization needed; wipe the journal and reset our state.
                    initPendingFile.delete();
                    mPendingInits.remove(transportName);
                }
                return; // done; don't fall through to the error case
            }
        } catch (RemoteException e) {
            // transport threw when asked its name; fall through to the lookup-failed case
        }

        // The named transport doesn't exist or threw.  This operation is
        // important, so we record the need for a an init and post a message
        // to retry the init later.
        if (isPending) {
            mPendingInits.add(transportName);
            mBackupHandler.sendMessageDelayed(
                    mBackupHandler.obtainMessage(MSG_RETRY_INIT,
                            (isPending ? 1 : 0),
                            0,
                            transportName),
                    TRANSPORT_RETRY_INTERVAL);
        }
    
private voidregisterTransport(java.lang.String name, java.lang.String component, com.android.internal.backup.IBackupTransport transport)

        synchronized (mTransports) {
            if (DEBUG) Slog.v(TAG, "Registering transport "
                    + component + "::" + name + " = " + transport);
            if (transport != null) {
                mTransports.put(name, transport);
                mTransportNames.put(component, name);
            } else {
                mTransports.remove(mTransportNames.get(component));
                mTransportNames.remove(component);
                // Nothing further to do in the unregistration case
                return;
            }
        }

        // If the init sentinel file exists, we need to be sure to perform the init
        // as soon as practical.  We also create the state directory at registration
        // time to ensure it's present from the outset.
        try {
            String transportName = transport.transportDirName();
            File stateDir = new File(mBaseStateDir, transportName);
            stateDir.mkdirs();

            File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
            if (initSentinel.exists()) {
                synchronized (mQueueLock) {
                    mPendingInits.add(transportName);

                    // TODO: pick a better starting time than now + 1 minute
                    long delay = 1000 * 60; // one minute, in milliseconds
                    mAlarmManager.set(AlarmManager.RTC_WAKEUP,
                            System.currentTimeMillis() + delay, mRunInitIntent);
                }
            }
        } catch (RemoteException e) {
            // the transport threw when asked its file naming prefs; declare it invalid
            Slog.e(TAG, "Unable to register transport as " + name);
            mTransportNames.remove(component);
            mTransports.remove(name);
        }
    
voidremoveEverBackedUp(java.lang.String packageName)

        if (DEBUG) Slog.v(TAG, "Removing backed-up knowledge of " + packageName);
        if (MORE_DEBUG) Slog.v(TAG, "New set:");

        synchronized (mEverStoredApps) {
            // Rewrite the file and rename to overwrite.  If we reboot in the middle,
            // we'll recognize on initialization time that the package no longer
            // exists and fix it up then.
            File tempKnownFile = new File(mBaseStateDir, "processed.new");
            RandomAccessFile known = null;
            try {
                known = new RandomAccessFile(tempKnownFile, "rws");
                mEverStoredApps.remove(packageName);
                for (String s : mEverStoredApps) {
                    known.writeUTF(s);
                    if (MORE_DEBUG) Slog.v(TAG, "    " + s);
                }
                known.close();
                known = null;
                if (!tempKnownFile.renameTo(mEverStored)) {
                    throw new IOException("Can't rename " + tempKnownFile + " to " + mEverStored);
                }
            } catch (IOException e) {
                // Bad: we couldn't create the new copy.  For safety's sake we
                // abandon the whole process and remove all what's-backed-up
                // state entirely, meaning we'll force a backup pass for every
                // participant on the next boot or [re]install.
                Slog.w(TAG, "Error rewriting " + mEverStored, e);
                mEverStoredApps.clear();
                tempKnownFile.delete();
                mEverStored.delete();
            } finally {
                try { if (known != null) known.close(); } catch (IOException e) {}
            }
        }
    
private voidremovePackageFromSetLocked(java.util.HashSet set, java.lang.String packageName)

        if (set.contains(packageName)) {
            // Found it.  Remove this one package from the bookkeeping, and
            // if it's the last participating app under this uid we drop the
            // (now-empty) set as well.
            // Note that we deliberately leave it 'known' in the "ever backed up"
            // bookkeeping so that its current-dataset data will be retrieved
            // if the app is subsequently reinstalled
            if (MORE_DEBUG) Slog.v(TAG, "  removing participant " + packageName);
            set.remove(packageName);
            mPendingBackups.remove(packageName);
        }
    
voidremovePackageParticipantsLocked(java.lang.String[] packageNames, int oldUid)

        if (packageNames == null) {
            Slog.w(TAG, "removePackageParticipants with null list");
            return;
        }

        if (MORE_DEBUG) Slog.v(TAG, "removePackageParticipantsLocked: uid=" + oldUid
                + " #" + packageNames.length);
        for (String pkg : packageNames) {
            // Known previous UID, so we know which package set to check
            HashSet<String> set = mBackupParticipants.get(oldUid);
            if (set != null && set.contains(pkg)) {
                removePackageFromSetLocked(set, pkg);
                if (set.isEmpty()) {
                    if (MORE_DEBUG) Slog.v(TAG, "  last one of this uid; purging set");
                    mBackupParticipants.remove(oldUid);
                }
            }
        }
    
voidresetBackupState(java.io.File stateFileDir)

        synchronized (mQueueLock) {
            // Wipe the "what we've ever backed up" tracking
            mEverStoredApps.clear();
            mEverStored.delete();

            mCurrentToken = 0;
            writeRestoreTokens();

            // Remove all the state files
            for (File sf : stateFileDir.listFiles()) {
                // ... but don't touch the needs-init sentinel
                if (!sf.getName().equals(INIT_SENTINEL_FILE_NAME)) {
                    sf.delete();
                }
            }
        }

        // Enqueue a new backup of every participant
        synchronized (mBackupParticipants) {
            final int N = mBackupParticipants.size();
            for (int i=0; i<N; i++) {
                HashSet<String> participants = mBackupParticipants.valueAt(i);
                if (participants != null) {
                    for (String packageName : participants) {
                        dataChangedImpl(packageName);
                    }
                }
            }
        }
    
public voidrestoreAtInstall(java.lang.String packageName, int token)

        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
            Slog.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
                    + " attemping install-time restore");
            return;
        }

        boolean skip = false;

        long restoreSet = getAvailableRestoreToken(packageName);
        if (DEBUG) Slog.v(TAG, "restoreAtInstall pkg=" + packageName
                + " token=" + Integer.toHexString(token)
                + " restoreSet=" + Long.toHexString(restoreSet));
        if (restoreSet == 0) {
            if (MORE_DEBUG) Slog.i(TAG, "No restore set");
            skip = true;
        }

        // Do we have a transport to fetch data for us?
        IBackupTransport transport = getTransport(mCurrentTransport);
        if (transport == null) {
            if (DEBUG) Slog.w(TAG, "No transport");
            skip = true;
        }

        if (!mAutoRestore) {
            if (DEBUG) {
                Slog.w(TAG, "Non-restorable state: auto=" + mAutoRestore);
            }
            skip = true;
        }

        if (!skip) {
            try {
                // okay, we're going to attempt a restore of this package from this restore set.
                // The eventual message back into the Package Manager to run the post-install
                // steps for 'token' will be issued from the restore handling code.

                // This can throw and so *must* happen before the wakelock is acquired
                String dirName = transport.transportDirName();

                // We can use a synthetic PackageInfo here because:
                //   1. We know it's valid, since the Package Manager supplied the name
                //   2. Only the packageName field will be used by the restore code
                PackageInfo pkg = new PackageInfo();
                pkg.packageName = packageName;

                mWakelock.acquire();
                if (MORE_DEBUG) {
                    Slog.d(TAG, "Restore at install of " + packageName);
                }
                Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
                msg.obj = new RestoreParams(transport, dirName, null,
                        restoreSet, pkg, token);
                mBackupHandler.sendMessage(msg);
            } catch (RemoteException e) {
                // Binding to the transport broke; back off and proceed with the installation.
                Slog.e(TAG, "Unable to contact transport");
                skip = true;
            }
        }

        if (skip) {
            // Auto-restore disabled or no way to attempt a restore; just tell the Package
            // Manager to proceed with the post-install handling for this package.
            if (DEBUG) Slog.v(TAG, "Finishing install immediately");
            try {
                mPackageManagerBinder.finishPackageInstall(token);
            } catch (RemoteException e) { /* can't happen */ }
        }
    
voidrestoreWidgetData(java.lang.String packageName, byte[] widgetData)

        // Apply the restored widget state and generate the ID update for the app
        AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, UserHandle.USER_OWNER);
    
private voidrouteSocketDataToOutput(android.os.ParcelFileDescriptor inPipe, java.io.OutputStream out)

        FileInputStream raw = new FileInputStream(inPipe.getFileDescriptor());
        DataInputStream in = new DataInputStream(raw);

        byte[] buffer = new byte[32 * 1024];
        int chunkTotal;
        while ((chunkTotal = in.readInt()) > 0) {
            while (chunkTotal > 0) {
                int toRead = (chunkTotal > buffer.length) ? buffer.length : chunkTotal;
                int nRead = in.read(buffer, 0, toRead);
                out.write(buffer, 0, nRead);
                chunkTotal -= nRead;
            }
        }
    
voidscheduleNextFullBackupJob()
Schedule a job to tell us when it's a good time to run a full backup

        synchronized (mQueueLock) {
            if (mFullBackupQueue.size() > 0) {
                // schedule the next job at the point in the future when the least-recently
                // backed up app comes due for backup again; or immediately if it's already
                // due.
                long upcomingLastBackup = mFullBackupQueue.get(0).lastBackup;
                long timeSinceLast = System.currentTimeMillis() - upcomingLastBackup;
                final long latency = (timeSinceLast < MIN_FULL_BACKUP_INTERVAL)
                        ? (MIN_FULL_BACKUP_INTERVAL - timeSinceLast) : 0;
                Runnable r = new Runnable() {
                    @Override public void run() {
                        FullBackupJob.schedule(mContext, latency);
                    }
                };
                mBackupHandler.postDelayed(r, 2500);
            } else {
                if (DEBUG_SCHEDULING) {
                    Slog.i(TAG, "Full backup queue empty; not scheduling");
                }
            }
        }
    
public java.lang.StringselectBackupTransport(java.lang.String transport)

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                "selectBackupTransport");

        synchronized (mTransports) {
            final long oldId = Binder.clearCallingIdentity();
            try {
                String prevTransport = mCurrentTransport;
                mCurrentTransport = transport;
                Settings.Secure.putString(mContext.getContentResolver(),
                        Settings.Secure.BACKUP_TRANSPORT, transport);
                Slog.v(TAG, "selectBackupTransport() set " + mCurrentTransport
                        + " returning " + prevTransport);
                return prevTransport;
            } finally {
                Binder.restoreCallingIdentity(oldId);
            }
        }
    
public voidsetAutoRestore(boolean doAutoRestore)

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                "setAutoRestore");

        Slog.i(TAG, "Auto restore => " + doAutoRestore);

        final long oldId = Binder.clearCallingIdentity();
        try {
            synchronized (this) {
                Settings.Secure.putInt(mContext.getContentResolver(),
                        Settings.Secure.BACKUP_AUTO_RESTORE, doAutoRestore ? 1 : 0);
                mAutoRestore = doAutoRestore;
            }
        } finally {
            Binder.restoreCallingIdentity(oldId);
        }
    
public voidsetBackupEnabled(boolean enable)

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                "setBackupEnabled");

        Slog.i(TAG, "Backup enabled => " + enable);

        long oldId = Binder.clearCallingIdentity();
        try {
            boolean wasEnabled = mEnabled;
            synchronized (this) {
                Settings.Secure.putInt(mContext.getContentResolver(),
                        Settings.Secure.BACKUP_ENABLED, enable ? 1 : 0);
                mEnabled = enable;
            }

            synchronized (mQueueLock) {
                if (enable && !wasEnabled && mProvisioned) {
                    // if we've just been enabled, start scheduling backup passes
                    startBackupAlarmsLocked(BACKUP_INTERVAL);
                    scheduleNextFullBackupJob();
                } else if (!enable) {
                    // No longer enabled, so stop running backups
                    if (DEBUG) Slog.i(TAG, "Opting out of backup");

                    mAlarmManager.cancel(mRunBackupIntent);

                    // This also constitutes an opt-out, so we wipe any data for
                    // this device from the backend.  We start that process with
                    // an alarm in order to guarantee wakelock states.
                    if (wasEnabled && mProvisioned) {
                        // NOTE: we currently flush every registered transport, not just
                        // the currently-active one.
                        HashSet<String> allTransports;
                        synchronized (mTransports) {
                            allTransports = new HashSet<String>(mTransports.keySet());
                        }
                        // build the set of transports for which we are posting an init
                        for (String transport : allTransports) {
                            recordInitPendingLocked(true, transport);
                        }
                        mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
                                mRunInitIntent);
                    }
                }
            }
        } finally {
            Binder.restoreCallingIdentity(oldId);
        }
    
public booleansetBackupPassword(java.lang.String currentPw, java.lang.String newPw)

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                "setBackupPassword");

        // When processing v1 passwords we may need to try two different PBKDF2 checksum regimes
        final boolean pbkdf2Fallback = (mPasswordVersion < BACKUP_PW_FILE_VERSION);

        // If the supplied pw doesn't hash to the the saved one, fail.  The password
        // might be caught in the legacy crypto mismatch; verify that too.
        if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PBKDF2_HASH_ROUNDS)
                && !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK,
                        currentPw, PBKDF2_HASH_ROUNDS))) {
            return false;
        }

        // Snap up to current on the pw file version
        mPasswordVersion = BACKUP_PW_FILE_VERSION;
        FileOutputStream pwFout = null;
        DataOutputStream pwOut = null;
        try {
            pwFout = new FileOutputStream(mPasswordVersionFile);
            pwOut = new DataOutputStream(pwFout);
            pwOut.writeInt(mPasswordVersion);
        } catch (IOException e) {
            Slog.e(TAG, "Unable to write backup pw version; password not changed");
            return false;
        } finally {
            try {
                if (pwOut != null) pwOut.close();
                if (pwFout != null) pwFout.close();
            } catch (IOException e) {
                Slog.w(TAG, "Unable to close pw version record");
            }
        }

        // Clearing the password is okay
        if (newPw == null || newPw.isEmpty()) {
            if (mPasswordHashFile.exists()) {
                if (!mPasswordHashFile.delete()) {
                    // Unable to delete the old pw file, so fail
                    Slog.e(TAG, "Unable to clear backup password");
                    return false;
                }
            }
            mPasswordHash = null;
            mPasswordSalt = null;
            return true;
        }

        try {
            // Okay, build the hash of the new backup password
            byte[] salt = randomBytes(PBKDF2_SALT_SIZE);
            String newPwHash = buildPasswordHash(PBKDF_CURRENT, newPw, salt, PBKDF2_HASH_ROUNDS);

            OutputStream pwf = null, buffer = null;
            DataOutputStream out = null;
            try {
                pwf = new FileOutputStream(mPasswordHashFile);
                buffer = new BufferedOutputStream(pwf);
                out = new DataOutputStream(buffer);
                // integer length of the salt array, followed by the salt,
                // then the hex pw hash string
                out.writeInt(salt.length);
                out.write(salt);
                out.writeUTF(newPwHash);
                out.flush();
                mPasswordHash = newPwHash;
                mPasswordSalt = salt;
                return true;
            } finally {
                if (out != null) out.close();
                if (buffer != null) buffer.close();
                if (pwf != null) pwf.close();
            }
        } catch (IOException e) {
            Slog.e(TAG, "Unable to set backup password");
        }
        return false;
    
public voidsetBackupProvisioned(boolean available)

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                "setBackupProvisioned");
        /*
         * This is now a no-op; provisioning is simply the device's own setup state.
         */
    
voidsignalFullBackupRestoreCompletion(com.android.server.backup.BackupManagerService$FullParams params)

        synchronized (params.latch) {
            params.latch.set(true);
            params.latch.notifyAll();
        }
    
static booleansignaturesMatch(java.util.ArrayList storedSigHashes, android.content.pm.PackageInfo target)

        if (target == null) {
            return false;
        }

        // If the target resides on the system partition, we allow it to restore
        // data from the like-named package in a restore set even if the signatures
        // do not match.  (Unlike general applications, those flashed to the system
        // partition will be signed with the device's platform certificate, so on
        // different phones the same system app will have different signatures.)
        if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
            if (DEBUG) Slog.v(TAG, "System app " + target.packageName + " - skipping sig check");
            return true;
        }

        // Allow unsigned apps, but not signed on one device and unsigned on the other
        // !!! TODO: is this the right policy?
        Signature[] deviceSigs = target.signatures;
        if (MORE_DEBUG) Slog.v(TAG, "signaturesMatch(): stored=" + storedSigHashes
                + " device=" + deviceSigs);
        if ((storedSigHashes == null || storedSigHashes.size() == 0)
                && (deviceSigs == null || deviceSigs.length == 0)) {
            return true;
        }
        if (storedSigHashes == null || deviceSigs == null) {
            return false;
        }

        // !!! TODO: this demands that every stored signature match one
        // that is present on device, and does not demand the converse.
        // Is this this right policy?
        final int nStored = storedSigHashes.size();
        final int nDevice = deviceSigs.length;

        // hash each on-device signature
        ArrayList<byte[]> deviceHashes = new ArrayList<byte[]>(nDevice);
        for (int i = 0; i < nDevice; i++) {
            deviceHashes.add(hashSignature(deviceSigs[i]));
        }

        // now ensure that each stored sig (hash) matches an on-device sig (hash)
        for (int n = 0; n < nStored; n++) {
            boolean match = false;
            final byte[] storedHash = storedSigHashes.get(n);
            for (int i = 0; i < nDevice; i++) {
                if (Arrays.equals(storedHash, deviceHashes.get(i))) {
                    match = true;
                    break;
                }
            }
            // match is false when no on-device sig matched one of the stored ones
            if (!match) {
                return false;
            }
        }

        return true;
    
static booleansignaturesMatch(android.content.pm.Signature[] storedSigs, android.content.pm.PackageInfo target)

        if (target == null) {
            return false;
        }

        // If the target resides on the system partition, we allow it to restore
        // data from the like-named package in a restore set even if the signatures
        // do not match.  (Unlike general applications, those flashed to the system
        // partition will be signed with the device's platform certificate, so on
        // different phones the same system app will have different signatures.)
        if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
            if (DEBUG) Slog.v(TAG, "System app " + target.packageName + " - skipping sig check");
            return true;
        }

        // Allow unsigned apps, but not signed on one device and unsigned on the other
        // !!! TODO: is this the right policy?
        Signature[] deviceSigs = target.signatures;
        if (MORE_DEBUG) Slog.v(TAG, "signaturesMatch(): stored=" + storedSigs
                + " device=" + deviceSigs);
        if ((storedSigs == null || storedSigs.length == 0)
                && (deviceSigs == null || deviceSigs.length == 0)) {
            return true;
        }
        if (storedSigs == null || deviceSigs == null) {
            return false;
        }

        // !!! TODO: this demands that every stored signature match one
        // that is present on device, and does not demand the converse.
        // Is this this right policy?
        int nStored = storedSigs.length;
        int nDevice = deviceSigs.length;

        for (int i=0; i < nStored; i++) {
            boolean match = false;
            for (int j=0; j < nDevice; j++) {
                if (storedSigs[i].equals(deviceSigs[j])) {
                    match = true;
                    break;
                }
            }
            if (!match) {
                return false;
            }
        }
        return true;
    
private voidstartBackupAlarmsLocked(long delayBeforeFirstBackup)

        // We used to use setInexactRepeating(), but that may be linked to
        // backups running at :00 more often than not, creating load spikes.
        // Schedule at an exact time for now, and also add a bit of "fuzz".

        Random random = new Random();
        long when = System.currentTimeMillis() + delayBeforeFirstBackup +
                random.nextInt(FUZZ_MILLIS);
        mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, when,
                BACKUP_INTERVAL + random.nextInt(FUZZ_MILLIS), mRunBackupIntent);
        mNextBackupPass = when;
    
voidstartConfirmationTimeout(int token, com.android.server.backup.BackupManagerService$FullParams params)

        if (MORE_DEBUG) Slog.d(TAG, "Posting conf timeout msg after "
                + TIMEOUT_FULL_CONFIRMATION + " millis");
        Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT,
                token, 0, params);
        mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION);
    
booleanstartConfirmationUi(int token, java.lang.String action)

        try {
            Intent confIntent = new Intent(action);
            confIntent.setClassName("com.android.backupconfirm",
                    "com.android.backupconfirm.BackupRestoreConfirmation");
            confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token);
            confIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            mContext.startActivity(confIntent);
        } catch (ActivityNotFoundException e) {
            return false;
        }
        return true;
    
booleantryBindTransport(android.content.pm.ServiceInfo info)

        try {
            PackageInfo packInfo = mPackageManager.getPackageInfo(info.packageName, 0);
            if ((packInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
                return bindTransport(info);
            } else {
                Slog.w(TAG, "Transport package " + info.packageName + " not privileged");
            }
        } catch (NameNotFoundException e) {
            Slog.w(TAG, "Problem resolving transport package " + info.packageName);
        }
        return false;
    
voidwaitForCompletion(com.android.server.backup.BackupManagerService$FullParams params)

        synchronized (params.latch) {
            while (params.latch.get() == false) {
                try {
                    params.latch.wait();
                } catch (InterruptedException e) { /* never interrupted */ }
            }
        }
    
booleanwaitUntilOperationComplete(int token)

        if (MORE_DEBUG) Slog.i(TAG, "Blocking until operation complete for "
                + Integer.toHexString(token));
        int finalState = OP_PENDING;
        Operation op = null;
        synchronized (mCurrentOpLock) {
            while (true) {
                op = mCurrentOperations.get(token);
                if (op == null) {
                    // mysterious disappearance: treat as success with no callback
                    break;
                } else {
                    if (op.state == OP_PENDING) {
                        try {
                            mCurrentOpLock.wait();
                        } catch (InterruptedException e) {}
                        // When the wait is notified we loop around and recheck the current state
                    } else {
                        // No longer pending; we're done
                        finalState = op.state;
                        break;
                    }
                }
            }
        }

        mBackupHandler.removeMessages(MSG_TIMEOUT);
        if (MORE_DEBUG) Slog.v(TAG, "operation " + Integer.toHexString(token)
                + " complete: finalState=" + finalState);
        return finalState == OP_ACKNOWLEDGED;
    
private voidwriteFullBackupScheduleAsync()


       
        mBackupHandler.removeCallbacks(mFullBackupScheduleWriter);
        mBackupHandler.post(mFullBackupScheduleWriter);
    
voidwriteRestoreTokens()

        try {
            RandomAccessFile af = new RandomAccessFile(mTokenFile, "rwd");

            // First, the version number of this record, for futureproofing
            af.writeInt(CURRENT_ANCESTRAL_RECORD_VERSION);

            // Write the ancestral and current tokens
            af.writeLong(mAncestralToken);
            af.writeLong(mCurrentToken);

            // Now write the set of ancestral packages
            if (mAncestralPackages == null) {
                af.writeInt(-1);
            } else {
                af.writeInt(mAncestralPackages.size());
                if (DEBUG) Slog.v(TAG, "Ancestral packages:  " + mAncestralPackages.size());
                for (String pkgName : mAncestralPackages) {
                    af.writeUTF(pkgName);
                    if (MORE_DEBUG) Slog.v(TAG, "   " + pkgName);
                }
            }
            af.close();
        } catch (IOException e) {
            Slog.w(TAG, "Unable to write token file:", e);
        }
    
private voidwriteToJournalLocked(java.lang.String str)

        RandomAccessFile out = null;
        try {
            if (mJournal == null) mJournal = File.createTempFile("journal", null, mJournalDir);
            out = new RandomAccessFile(mJournal, "rws");
            out.seek(out.length());
            out.writeUTF(str);
        } catch (IOException e) {
            Slog.e(TAG, "Can't write " + str + " to backup journal", e);
            mJournal = null;
        } finally {
            try { if (out != null) out.close(); } catch (IOException e) {}
        }