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 
17 package android.app.backup;
18 
19 import android.os.ParcelFileDescriptor;
20 import android.util.Log;
21 
22 import java.io.FileDescriptor;
23 import java.io.IOException;
24 import java.util.Map;
25 import java.util.TreeMap;
26 
27 /** @hide */
28 public 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 
BackupHelperDispatcher()38     public BackupHelperDispatcher() {
39     }
40 
addHelper(String keyPrefix, BackupHelper helper)41     public void addHelper(String keyPrefix, BackupHelper helper) {
42         mHelpers.put(keyPrefix, helper);
43     }
44 
performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)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 
54         if (oldState != null) {
55             oldStateFD = oldState.getFileDescriptor();
56             while ((err = readHeader_native(header, oldStateFD)) >= 0) {
57                 if (err == 0) {
58                     BackupHelper helper = helpers.get(header.keyPrefix);
59                     Log.d(TAG, "handling existing helper '" + header.keyPrefix + "' " + helper);
60                     if (helper != null) {
61                         doOneBackup(oldState, data, newState, header, helper);
62                         helpers.remove(header.keyPrefix);
63                     } else {
64                         skipChunk_native(oldStateFD, header.chunkSize);
65                     }
66                 }
67             }
68         }
69 
70         // Then go through and do the rest that we haven't done.
71         for (Map.Entry<String,BackupHelper> entry: helpers.entrySet()) {
72             header.keyPrefix = entry.getKey();
73             Log.d(TAG, "handling new helper '" + header.keyPrefix + "'");
74             BackupHelper helper = entry.getValue();
75             doOneBackup(oldState, data, newState, header, helper);
76         }
77     }
78 
doOneBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState, Header header, BackupHelper helper)79     private void doOneBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
80             ParcelFileDescriptor newState, Header header, BackupHelper helper)
81             throws IOException {
82         int err;
83         FileDescriptor newStateFD = newState.getFileDescriptor();
84 
85         // allocate space for the header in the file
86         int pos = allocateHeader_native(header, newStateFD);
87         if (pos < 0) {
88             throw new IOException("allocateHeader_native failed (error " + pos + ")");
89         }
90 
91         data.setKeyPrefix(header.keyPrefix);
92 
93         // do the backup
94         helper.performBackup(oldState, data, newState);
95 
96         // fill in the header (seeking back to pos).  The file pointer will be returned to
97         // where it was at the end of performBackup.  Header.chunkSize will not be filled in.
98         err = writeHeader_native(header, newStateFD, pos);
99         if (err != 0) {
100             throw new IOException("writeHeader_native failed (error " + err + ")");
101         }
102     }
103 
performRestore(BackupDataInput input, int appVersionCode, ParcelFileDescriptor newState)104     public void performRestore(BackupDataInput input, int appVersionCode,
105             ParcelFileDescriptor newState)
106             throws IOException {
107         boolean alreadyComplained = false;
108 
109         BackupDataInputStream stream = new BackupDataInputStream(input);
110         while (input.readNextHeader()) {
111 
112             String rawKey = input.getKey();
113             int pos = rawKey.indexOf(':');
114             if (pos > 0) {
115                 String prefix = rawKey.substring(0, pos);
116                 BackupHelper helper = mHelpers.get(prefix);
117                 if (helper != null) {
118                     stream.dataSize = input.getDataSize();
119                     stream.key = rawKey.substring(pos+1);
120                     helper.restoreEntity(stream);
121                 } else {
122                     if (!alreadyComplained) {
123                         Log.w(TAG, "Couldn't find helper for: '" + rawKey + "'");
124                         alreadyComplained = true;
125                     }
126                 }
127             } else {
128                 if (!alreadyComplained) {
129                     Log.w(TAG, "Entity with no prefix: '" + rawKey + "'");
130                     alreadyComplained = true;
131                 }
132             }
133             input.skipEntityData(); // In case they didn't consume the data.
134         }
135 
136         // Write out the state files -- mHelpers is a TreeMap, so the order is well defined.
137         for (BackupHelper helper: mHelpers.values()) {
138             helper.writeNewStateDescription(newState);
139         }
140     }
141 
readHeader_native(Header h, FileDescriptor fd)142     private static native int readHeader_native(Header h, FileDescriptor fd);
skipChunk_native(FileDescriptor fd, int bytesToSkip)143     private static native int skipChunk_native(FileDescriptor fd, int bytesToSkip);
144 
allocateHeader_native(Header h, FileDescriptor fd)145     private static native int allocateHeader_native(Header h, FileDescriptor fd);
writeHeader_native(Header h, FileDescriptor fd, int pos)146     private static native int writeHeader_native(Header h, FileDescriptor fd, int pos);
147 }
148 
149