/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app.backup; import android.os.ParcelFileDescriptor; import android.util.Log; import java.io.FileDescriptor; import java.io.IOException; import java.util.Map; import java.util.TreeMap; /** @hide */ public class BackupHelperDispatcher { private static final String TAG = "BackupHelperDispatcher"; private static class Header { int chunkSize; // not including the header String keyPrefix; } TreeMap mHelpers = new TreeMap(); public BackupHelperDispatcher() { } public void addHelper(String keyPrefix, BackupHelper helper) { mHelpers.put(keyPrefix, helper); } public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException { // First, do the helpers that we've already done, since they're already in the state // file. int err; Header header = new Header(); TreeMap helpers = (TreeMap)mHelpers.clone(); FileDescriptor oldStateFD = null; if (oldState != null) { oldStateFD = oldState.getFileDescriptor(); while ((err = readHeader_native(header, oldStateFD)) >= 0) { if (err == 0) { BackupHelper helper = helpers.get(header.keyPrefix); Log.d(TAG, "handling existing helper '" + header.keyPrefix + "' " + helper); if (helper != null) { doOneBackup(oldState, data, newState, header, helper); helpers.remove(header.keyPrefix); } else { skipChunk_native(oldStateFD, header.chunkSize); } } } } // Then go through and do the rest that we haven't done. for (Map.Entry entry: helpers.entrySet()) { header.keyPrefix = entry.getKey(); Log.d(TAG, "handling new helper '" + header.keyPrefix + "'"); BackupHelper helper = entry.getValue(); doOneBackup(oldState, data, newState, header, helper); } } private void doOneBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState, Header header, BackupHelper helper) throws IOException { int err; FileDescriptor newStateFD = newState.getFileDescriptor(); // allocate space for the header in the file int pos = allocateHeader_native(header, newStateFD); if (pos < 0) { throw new IOException("allocateHeader_native failed (error " + pos + ")"); } data.setKeyPrefix(header.keyPrefix); // do the backup helper.performBackup(oldState, data, newState); // fill in the header (seeking back to pos). The file pointer will be returned to // where it was at the end of performBackup. Header.chunkSize will not be filled in. err = writeHeader_native(header, newStateFD, pos); if (err != 0) { throw new IOException("writeHeader_native failed (error " + err + ")"); } } public void performRestore(BackupDataInput input, int appVersionCode, ParcelFileDescriptor newState) throws IOException { boolean alreadyComplained = false; BackupDataInputStream stream = new BackupDataInputStream(input); while (input.readNextHeader()) { String rawKey = input.getKey(); int pos = rawKey.indexOf(':'); if (pos > 0) { String prefix = rawKey.substring(0, pos); BackupHelper helper = mHelpers.get(prefix); if (helper != null) { stream.dataSize = input.getDataSize(); stream.key = rawKey.substring(pos+1); helper.restoreEntity(stream); } else { if (!alreadyComplained) { Log.w(TAG, "Couldn't find helper for: '" + rawKey + "'"); alreadyComplained = true; } } } else { if (!alreadyComplained) { Log.w(TAG, "Entity with no prefix: '" + rawKey + "'"); alreadyComplained = true; } } input.skipEntityData(); // In case they didn't consume the data. } // Write out the state files -- mHelpers is a TreeMap, so the order is well defined. for (BackupHelper helper: mHelpers.values()) { helper.writeNewStateDescription(newState); } } private static native int readHeader_native(Header h, FileDescriptor fd); private static native int skipChunk_native(FileDescriptor fd, int bytesToSkip); private static native int allocateHeader_native(Header h, FileDescriptor fd); private static native int writeHeader_native(Header h, FileDescriptor fd, int pos); }