| package com.android.server.backup; |
| |
| import static android.os.ParcelFileDescriptor.MODE_CREATE; |
| import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; |
| import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; |
| import static android.os.ParcelFileDescriptor.MODE_TRUNCATE; |
| |
| import android.app.IBackupAgent; |
| import android.app.backup.BackupDataInput; |
| import android.app.backup.BackupDataOutput; |
| import android.app.backup.FullBackup; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| import android.util.Slog; |
| |
| import com.android.server.backup.BackupManagerService.FileMetadata; |
| import libcore.io.IoUtils; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Used by BackupManagerService to perform adb restore for key-value packages. At the moment this |
| * class resembles what is done in the standard key-value code paths in BackupManagerService, and |
| * should be unified later. |
| * |
| * TODO: We should create unified backup/restore engines that can be used for both transport and |
| * adb backup/restore, and for fullbackup and key-value backup. |
| */ |
| class KeyValueAdbRestoreEngine implements Runnable { |
| private static final String TAG = "KeyValueAdbRestoreEngine"; |
| private static final boolean DEBUG = false; |
| |
| private final BackupManagerService mBackupManagerService; |
| private final File mDataDir; |
| |
| FileMetadata mInfo; |
| BackupManagerService.PerformAdbRestoreTask mRestoreTask; |
| ParcelFileDescriptor mInFD; |
| IBackupAgent mAgent; |
| int mToken; |
| |
| KeyValueAdbRestoreEngine(BackupManagerService backupManagerService, File dataDir, |
| FileMetadata info, ParcelFileDescriptor inFD, IBackupAgent agent, int token) { |
| mBackupManagerService = backupManagerService; |
| mDataDir = dataDir; |
| mInfo = info; |
| mInFD = inFD; |
| mAgent = agent; |
| mToken = token; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| File restoreData = prepareRestoreData(mInfo, mInFD); |
| |
| // TODO: version ? |
| invokeAgentForAdbRestore(mAgent, mInfo, restoreData, 0); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| private File prepareRestoreData(FileMetadata info, ParcelFileDescriptor inFD) throws IOException { |
| String pkg = info.packageName; |
| File restoreDataName = new File(mDataDir, pkg + ".restore"); |
| File sortedDataName = new File(mDataDir, pkg + ".sorted"); |
| |
| FullBackup.restoreFile(inFD, info.size, info.type, info.mode, info.mtime, restoreDataName); |
| |
| // Sort the keys, as the BackupAgent expect them to come in lexicographical order |
| sortKeyValueData(restoreDataName, sortedDataName); |
| return sortedDataName; |
| } |
| |
| private void invokeAgentForAdbRestore(IBackupAgent agent, FileMetadata info, File restoreData, |
| int versionCode) throws IOException { |
| String pkg = info.packageName; |
| File newStateName = new File(mDataDir, pkg + ".new"); |
| try { |
| ParcelFileDescriptor backupData = |
| ParcelFileDescriptor.open(restoreData, MODE_READ_ONLY); |
| ParcelFileDescriptor newState = ParcelFileDescriptor.open(newStateName, |
| MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE); |
| |
| if (DEBUG) { |
| Slog.i(TAG, "Starting restore of package " + pkg + " for version code " |
| + versionCode); |
| } |
| agent.doRestore(backupData, versionCode, newState, mToken, |
| mBackupManagerService.mBackupManagerBinder); |
| } catch (IOException e) { |
| Slog.e(TAG, "Exception opening file. " + e); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Exception calling doRestore on agent: " + e); |
| } |
| } |
| |
| private void sortKeyValueData (File restoreData, File sortedData) throws IOException { |
| FileInputStream inputStream = null; |
| FileOutputStream outputStream = null; |
| try { |
| inputStream = new FileInputStream(restoreData); |
| outputStream = new FileOutputStream(sortedData); |
| BackupDataInput reader = new BackupDataInput(inputStream.getFD()); |
| BackupDataOutput writer = new BackupDataOutput(outputStream.getFD()); |
| copyKeysInLexicalOrder(reader, writer); |
| } finally { |
| if (inputStream != null) { |
| IoUtils.closeQuietly(inputStream); |
| } |
| if (outputStream != null) { |
| IoUtils.closeQuietly(outputStream); |
| } |
| } |
| } |
| |
| private void copyKeysInLexicalOrder(BackupDataInput in, BackupDataOutput out) |
| throws IOException { |
| Map<String, byte[]> data = new HashMap<>(); |
| while (in.readNextHeader()) { |
| String key = in.getKey(); |
| int size = in.getDataSize(); |
| if (size < 0) { |
| in.skipEntityData(); |
| continue; |
| } |
| byte[] value = new byte[size]; |
| in.readEntityData(value, 0, size); |
| data.put(key, value); |
| } |
| List<String> keys = new ArrayList<>(data.keySet()); |
| Collections.sort(keys); |
| for (String key : keys) { |
| byte[] value = data.get(key); |
| out.writeEntityHeader(key, value.length); |
| out.writeEntityData(value, value.length); |
| } |
| } |
| } |