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