Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit efe52647 authored by Dan Egnor's avatar Dan Egnor
Browse files

Modify the IBackupTransport API to support bulk restore operations.

Change the BackupManagerService and LocalTransport to support the new API.
parent 0bc7b849
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -43,14 +43,14 @@ public class RestoreSet implements Parcelable {
     * transport.  This is guaranteed to be valid for the duration of a restore
     * session, but is meaningless once the session has ended.
     */
    public int token;
    public long token;


    public RestoreSet() {
        // Leave everything zero / null
    }

    public RestoreSet(String _name, String _dev, int _token) {
    public RestoreSet(String _name, String _dev, long _token) {
        name = _name;
        device = _dev;
        token = _token;
@@ -65,7 +65,7 @@ public class RestoreSet implements Parcelable {
    public void writeToParcel(Parcel out, int flags) {
        out.writeString(name);
        out.writeString(device);
        out.writeInt(token);
        out.writeLong(token);
    }

    public static final Parcelable.Creator<RestoreSet> CREATOR
@@ -82,6 +82,6 @@ public class RestoreSet implements Parcelable {
    private RestoreSet(Parcel in) {
        name = in.readString();
        device = in.readString();
        token = in.readInt();
        token = in.readLong();
    }
}
+42 −35
Original line number Diff line number Diff line
@@ -54,63 +54,70 @@ interface IBackupTransport {
    long requestBackupTime();

    /**
     * Establish a connection to the back-end data repository, if necessary.  If the transport
     * needs to initialize state that is not tied to individual applications' backup operations,
     * this is where it should be done.
     *
     * @return Zero on success; a nonzero error code on failure.
     */
    int startSession();

    /**
     * Send one application's data to the backup destination.
     * Send one application's data to the backup destination.  The transport may send
     * the data immediately, or may buffer it.  After this is called, {@link #finishBackup}
     * must be called to ensure the data is sent and recorded successfully.
     *
     * @param packageInfo The identity of the application whose data is being backed up.
     *   This specifically includes the signature list for the package.
     * @param data The data stream that resulted from invoking the application's
     *   BackupService.doBackup() method.  This may be a pipe rather than a file on
     *   persistent media, so it may not be seekable.
     * @return Zero on success; a nonzero error code on failure.
     * @return false if errors occurred (the backup should be aborted and rescheduled),
     *   true if everything is OK so far (but {@link #finishBackup} must be called).
     */
    int performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor data);
    boolean performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor inFd);

    /**
     * Finish sending application data to the backup destination.  This must be
     * called after {@link #performBackup} to ensure that all data is sent.  Only
     * when this method returns true can the backup be assumed to have succeeded.
     *
     * @return false if errors occurred (the backup should be aborted and rescheduled),
     *   true if everything is OK so far (but {@link #finishBackup} must be called).
     */
    boolean finishBackup();

    /**
     * Get the set of backups currently available over this transport.
     *
     * @return Descriptions of the set of restore images available for this device.
     * @return Descriptions of the set of restore images available for this device,
     *   or null if an error occurred (the attempt should be rescheduled).
     **/
    RestoreSet[] getAvailableRestoreSets();

    /**
     * Get the set of applications from a given restore image.
     * Start restoring application data from backup.  After calling this function,
     * alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData}
     * to walk through the actual application data.
     *
     * @param token A backup token as returned by {@link #getAvailableRestoreSets}.
     * @return An array of PackageInfo objects describing all of the applications
     *   available for restore from this restore image.  This should include the list
     *   of signatures for each package so that the Backup Manager can filter using that
     *   information.
     * @param packages List of applications to restore (if data is available).
     *   Application data will be restored in the order given.
     * @return false if errors occurred (the restore should be aborted and rescheduled),
     *   true if everything is OK so far (go ahead and call {@link #nextRestorePackage}).
     */
    PackageInfo[] getAppSet(int token);
    boolean startRestore(long token, in PackageInfo[] packages);

    /**
     * Retrieve one application's data from the backing store.
     *
     * @param token The backup record from which a restore is being requested.
     * @param packageInfo The identity of the application whose data is being restored.
     *   This must include the signature list for the package; it is up to the transport
     *   to verify that the requested app's signatures match the saved backup record
     *   because the transport cannot necessarily trust the client device.
     * @param data An open, writable file into which the backup image should be stored.
     * @return Zero on success; a nonzero error code on failure.
     * Get the package name of the next application with data in the backup store.
     * @return The name of one of the packages supplied to {@link #startRestore},
     *   or "" (the empty string) if no more backup data is available,
     *   or null if an error occurred (the restore should be aborted and rescheduled).
     */
    int getRestoreData(int token, in PackageInfo packageInfo, in ParcelFileDescriptor data);
    String nextRestorePackage();

    /**
     * Terminate the backup session, closing files, freeing memory, and cleaning up whatever
     * other state the transport required.
     *
     * @return Zero on success; a nonzero error code on failure.  Even on failure, the session
     *         is torn down and must be restarted if another backup is attempted.
     * Get the data for the application returned by {@link #nextRestorePackage}.
     * @param data An open, writable file into which the backup data should be stored.
     * @return false if errors occurred (the restore should be aborted and rescheduled),
     *   true if everything is OK so far (go ahead and call {@link #nextRestorePackage}).
     */
    boolean getRestoreData(in ParcelFileDescriptor outFd);

    /**
     * End a restore session (aborting any in-process data transfer as necessary),
     * freeing any resources and connections used during the restore process.
     */
    int endSession();
    void finishRestore();
}
+58 −77
Original line number Diff line number Diff line
@@ -33,11 +33,8 @@ public class LocalTransport extends IBackupTransport.Stub {
    private Context mContext;
    private PackageManager mPackageManager;
    private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
    private FileFilter mDirFileFilter = new FileFilter() {
        public boolean accept(File f) {
            return f.isDirectory();
        }
    };
    private PackageInfo[] mRestorePackages = null;
    private int mRestorePackage = -1;  // Index into mRestorePackages


    public LocalTransport(Context context) {
@@ -51,21 +48,9 @@ public class LocalTransport extends IBackupTransport.Stub {
        return 0;
    }

    public int startSession() throws RemoteException {
        if (DEBUG) Log.v(TAG, "session started");
        mDataDir.mkdirs();
        return 0;
    }

    public int endSession() throws RemoteException {
        if (DEBUG) Log.v(TAG, "session ended");
        return 0;
    }

    public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)
    public boolean performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)
            throws RemoteException {
        if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
        int err = 0;

        File packageDir = new File(mDataDir, packageInfo.packageName);
        packageDir.mkdirs();
@@ -101,9 +86,8 @@ public class LocalTransport extends IBackupTransport.Stub {
                    try {
                        entity.write(buf, 0, dataSize);
                    } catch (IOException e) {
                        Log.e(TAG, "Unable to update key file "
                                + entityFile.getAbsolutePath());
                        err = -1;
                        Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
                        return false;
                    } finally {
                        entity.close();
                    }
@@ -111,14 +95,17 @@ public class LocalTransport extends IBackupTransport.Stub {
                    entityFile.delete();
                }
            }
            return true;
        } catch (IOException e) {
            // oops, something went wrong.  abort the operation and return error.
            Log.v(TAG, "Exception reading backup input:");
            e.printStackTrace();
            err = -1;
            Log.v(TAG, "Exception reading backup input:", e);
            return false;
        }
    }

        return err;
    public boolean finishBackup() throws RemoteException {
        if (DEBUG) Log.v(TAG, "finishBackup()");
        return true;
    }

    // Restore handling
@@ -129,72 +116,66 @@ public class LocalTransport extends IBackupTransport.Stub {
        return array;
    }

    public PackageInfo[] getAppSet(int token) throws android.os.RemoteException {
        if (DEBUG) Log.v(TAG, "getting app set " + token);
        // the available packages are the extant subdirs of mDatadir
        File[] packageDirs = mDataDir.listFiles(mDirFileFilter);
        ArrayList<PackageInfo> packages = new ArrayList<PackageInfo>();
        for (File dir : packageDirs) {
            try {
                PackageInfo pkg = mPackageManager.getPackageInfo(dir.getName(),
                        PackageManager.GET_SIGNATURES);
                if (pkg != null) {
                    packages.add(pkg);
                }
            } catch (NameNotFoundException e) {
                // restore set contains data for a package not installed on the
                // phone -- just ignore it.
            }
    public boolean startRestore(long token, PackageInfo[] packages) {
        if (DEBUG) Log.v(TAG, "start restore " + token);
        mRestorePackages = packages;
        mRestorePackage = -1;
        return true;
    }

        if (DEBUG) {
            Log.v(TAG, "Built app set of " + packages.size() + " entries:");
            for (PackageInfo p : packages) {
                Log.v(TAG, "    + " + p.packageName);
    public String nextRestorePackage() {
        if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
        while (++mRestorePackage < mRestorePackages.length) {
            String name = mRestorePackages[mRestorePackage].packageName;
            if (new File(mDataDir, name).isDirectory()) {
                if (DEBUG) Log.v(TAG, "  nextRestorePackage() = " + name);
                return name;
            }
        }

        PackageInfo[] result = new PackageInfo[packages.size()];
        return packages.toArray(result);
        if (DEBUG) Log.v(TAG, "  no more packages to restore");
        return "";
    }

    public int getRestoreData(int token, PackageInfo packageInfo, ParcelFileDescriptor outFd)
            throws android.os.RemoteException {
        if (DEBUG) Log.v(TAG, "getting restore data " + token + " : " + packageInfo.packageName);
        // we only support one hardcoded restore set
        if (token != 0) return -1;

        // the data for a given package is at a known location
        File packageDir = new File(mDataDir, packageInfo.packageName);
    public boolean getRestoreData(ParcelFileDescriptor outFd) {
        if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
        if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
        File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);

        // The restore set is the concatenation of the individual record blobs,
        // each of which is a file in the package's directory
        File[] blobs = packageDir.listFiles();
        if (DEBUG) Log.v(TAG, "   found " + blobs.length + " key files");
        int err = 0;
        if (blobs != null && blobs.length > 0) {
        if (blobs == null) {
            Log.e(TAG, "Error listing directory: " + packageDir);
            return false;  // nextRestorePackage() ensures the dir exists, so this is an error
        }

        // We expect at least some data if the directory exists in the first place
        if (DEBUG) Log.v(TAG, "  getRestoreData() found " + blobs.length + " key files");
        BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
        try {
            for (File f : blobs) {
                    copyToRestoreData(f, out);
                }
            } catch (Exception e) {
                Log.e(TAG, "Unable to read backup records");
                err = -1;
            }
        }
        return err;
    }

    private void copyToRestoreData(File f, BackupDataOutput out) throws IOException {
                FileInputStream in = new FileInputStream(f);
                try {
                    int size = (int) f.length();
                    byte[] buf = new byte[size];
                    in.read(buf);
                    String key = new String(Base64.decode(f.getName()));
        if (DEBUG) Log.v(TAG, "   ... copy to stream: key=" + key
                + " size=" + size);
                    if (DEBUG) Log.v(TAG, "    ... key=" + key + " size=" + size);
                    out.writeEntityHeader(key, size);
                    out.writeEntityData(buf, size);
                } finally {
                    in.close();
                }
            }
            return true;
        } catch (IOException e) {
            Log.e(TAG, "Unable to read backup records", e);
            return false;
        }
    }

    public void finishRestore() {
        if (DEBUG) Log.v(TAG, "finishRestore()");
    }
}
+172 −205

File changed.

Preview size limit exceeded, changes collapsed.

+9 −9
Original line number Diff line number Diff line
@@ -57,7 +57,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
    // is stored using the package name as a key)
    private static final String GLOBAL_METADATA_KEY = "@meta@";

    private List<ApplicationInfo> mAllApps;
    private List<PackageInfo> mAllPackages;
    private PackageManager mPackageManager;
    private HashMap<String, Metadata> mRestoredSignatures;

@@ -73,9 +73,9 @@ public class PackageManagerBackupAgent extends BackupAgent {

    // We're constructed with the set of applications that are participating
    // in backup.  This set changes as apps are installed & removed.
    PackageManagerBackupAgent(PackageManager packageMgr, List<ApplicationInfo> apps) {
    PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) {
        mPackageManager = packageMgr;
        mAllApps = apps;
        mAllPackages = packages;
        mRestoredSignatures = null;
    }

@@ -118,8 +118,8 @@ public class PackageManagerBackupAgent extends BackupAgent {

            // For each app we have on device, see if we've backed it up yet.  If not,
            // write its signature block to the output, keyed on the package name.
            for (ApplicationInfo app : mAllApps) {
                String packName = app.packageName;
            for (PackageInfo pkg : mAllPackages) {
                String packName = pkg.packageName;
                if (!existing.contains(packName)) {
                    // We haven't stored this app's signatures yet, so we do that now
                    try {
@@ -186,7 +186,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
        }

        // Finally, write the new state blob -- just the list of all apps we handled
        writeStateFile(mAllApps, newState);
        writeStateFile(mAllPackages, newState);
    }

    // "Restore" here is a misnomer.  What we're really doing is reading back the
@@ -327,7 +327,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
    }

    // Util: write out our new backup state file
    private void writeStateFile(List<ApplicationInfo> apps, ParcelFileDescriptor stateFile) {
    private void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) {
        FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
        DataOutputStream out = new DataOutputStream(outstream);

@@ -338,8 +338,8 @@ public class PackageManagerBackupAgent extends BackupAgent {
            out.write(metaNameBuf);

            // now write all the app names too
            for (ApplicationInfo app : apps) {
                byte[] pkgNameBuf = app.packageName.getBytes();
            for (PackageInfo pkg : pkgs) {
                byte[] pkgNameBuf = pkg.packageName.getBytes();
                out.writeInt(pkgNameBuf.length);
                out.write(pkgNameBuf);
            }