1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.app.backup;
18
19import android.os.ParcelFileDescriptor;
20import android.util.Log;
21
22import java.io.FileDescriptor;
23import java.io.IOException;
24import java.util.Map;
25import java.util.TreeMap;
26
27/** @hide */
28public class BackupHelperDispatcher {
29    private static final String TAG = "BackupHelperDispatcher";
30
31    private static class Header {
32        int chunkSize; // not including the header
33        String keyPrefix;
34    }
35
36    TreeMap<String,BackupHelper> mHelpers = new TreeMap<String,BackupHelper>();
37
38    public BackupHelperDispatcher() {
39    }
40
41    public void addHelper(String keyPrefix, BackupHelper helper) {
42        mHelpers.put(keyPrefix, helper);
43    }
44
45    public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
46             ParcelFileDescriptor newState) throws IOException {
47        // First, do the helpers that we've already done, since they're already in the state
48        // file.
49        int err;
50        Header header = new Header();
51        TreeMap<String,BackupHelper> helpers = (TreeMap<String,BackupHelper>)mHelpers.clone();
52        FileDescriptor oldStateFD = null;
53        FileDescriptor newStateFD = newState.getFileDescriptor();
54
55        if (oldState != null) {
56            oldStateFD = oldState.getFileDescriptor();
57            while ((err = readHeader_native(header, oldStateFD)) >= 0) {
58                if (err == 0) {
59                    BackupHelper helper = helpers.get(header.keyPrefix);
60                    Log.d(TAG, "handling existing helper '" + header.keyPrefix + "' " + helper);
61                    if (helper != null) {
62                        doOneBackup(oldState, data, newState, header, helper);
63                        helpers.remove(header.keyPrefix);
64                    } else {
65                        skipChunk_native(oldStateFD, header.chunkSize);
66                    }
67                }
68            }
69        }
70
71        // Then go through and do the rest that we haven't done.
72        for (Map.Entry<String,BackupHelper> entry: helpers.entrySet()) {
73            header.keyPrefix = entry.getKey();
74            Log.d(TAG, "handling new helper '" + header.keyPrefix + "'");
75            BackupHelper helper = entry.getValue();
76            doOneBackup(oldState, data, newState, header, helper);
77        }
78    }
79
80    private void doOneBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
81            ParcelFileDescriptor newState, Header header, BackupHelper helper)
82            throws IOException {
83        int err;
84        FileDescriptor newStateFD = newState.getFileDescriptor();
85
86        // allocate space for the header in the file
87        int pos = allocateHeader_native(header, newStateFD);
88        if (pos < 0) {
89            throw new IOException("allocateHeader_native failed (error " + pos + ")");
90        }
91
92        data.setKeyPrefix(header.keyPrefix);
93
94        // do the backup
95        helper.performBackup(oldState, data, newState);
96
97        // fill in the header (seeking back to pos).  The file pointer will be returned to
98        // where it was at the end of performBackup.  Header.chunkSize will not be filled in.
99        err = writeHeader_native(header, newStateFD, pos);
100        if (err != 0) {
101            throw new IOException("writeHeader_native failed (error " + err + ")");
102        }
103    }
104
105    public void performRestore(BackupDataInput input, int appVersionCode,
106            ParcelFileDescriptor newState)
107            throws IOException {
108        boolean alreadyComplained = false;
109
110        BackupDataInputStream stream = new BackupDataInputStream(input);
111        while (input.readNextHeader()) {
112
113            String rawKey = input.getKey();
114            int pos = rawKey.indexOf(':');
115            if (pos > 0) {
116                String prefix = rawKey.substring(0, pos);
117                BackupHelper helper = mHelpers.get(prefix);
118                if (helper != null) {
119                    stream.dataSize = input.getDataSize();
120                    stream.key = rawKey.substring(pos+1);
121                    helper.restoreEntity(stream);
122                } else {
123                    if (!alreadyComplained) {
124                        Log.w(TAG, "Couldn't find helper for: '" + rawKey + "'");
125                        alreadyComplained = true;
126                    }
127                }
128            } else {
129                if (!alreadyComplained) {
130                    Log.w(TAG, "Entity with no prefix: '" + rawKey + "'");
131                    alreadyComplained = true;
132                }
133            }
134            input.skipEntityData(); // In case they didn't consume the data.
135        }
136
137        // Write out the state files -- mHelpers is a TreeMap, so the order is well defined.
138        for (BackupHelper helper: mHelpers.values()) {
139            helper.writeNewStateDescription(newState);
140        }
141    }
142
143    private static native int readHeader_native(Header h, FileDescriptor fd);
144    private static native int skipChunk_native(FileDescriptor fd, int bytesToSkip);
145
146    private static native int allocateHeader_native(Header h, FileDescriptor fd);
147    private static native int writeHeader_native(Header h, FileDescriptor fd, int pos);
148}
149
150