PackageManagerBackupAgent.java revision 72d19aa51e90d45c7895629db78e548da2f6d469
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 com.android.server;
18
19import android.app.BackupAgent;
20import android.backup.BackupDataInput;
21import android.backup.BackupDataOutput;
22import android.content.pm.ApplicationInfo;
23import android.content.pm.PackageInfo;
24import android.content.pm.PackageManager;
25import android.content.pm.PackageManager.NameNotFoundException;
26import android.content.pm.Signature;
27import android.os.Build;
28import android.os.ParcelFileDescriptor;
29import android.util.Log;
30
31import java.io.ByteArrayInputStream;
32import java.io.ByteArrayOutputStream;
33import java.io.DataInputStream;
34import java.io.DataOutputStream;
35import java.io.EOFException;
36import java.io.FileInputStream;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.util.ArrayList;
40import java.util.HashMap;
41import java.util.HashSet;
42import java.util.List;
43
44/**
45 * We back up the signatures of each package so that during a system restore,
46 * we can verify that the app whose data we think we have matches the app
47 * actually resident on the device.
48 *
49 * Since the Package Manager isn't a proper "application" we just provide a
50 * direct IBackupAgent implementation and hand-construct it at need.
51 */
52public class PackageManagerBackupAgent extends BackupAgent {
53    private static final String TAG = "PMBA";
54    private static final boolean DEBUG = true;
55
56    // key under which we store global metadata (individual app metadata
57    // is stored using the package name as a key)
58    private static final String GLOBAL_METADATA_KEY = "@meta@";
59
60    private List<PackageInfo> mAllPackages;
61    private PackageManager mPackageManager;
62    // version & signature info of each app in a restore set
63    private HashMap<String, Metadata> mRestoredSignatures;
64    // The version info of each backed-up app as read from the state file
65    private HashMap<String, Metadata> mStateVersions = new HashMap<String, Metadata>();
66
67    private final HashSet<String> mExisting = new HashSet<String>();
68    private int mStoredSdkVersion;
69    private String mStoredIncrementalVersion;
70
71    public class Metadata {
72        public int versionCode;
73        public Signature[] signatures;
74
75        Metadata(int version, Signature[] sigs) {
76            versionCode = version;
77            signatures = sigs;
78        }
79    }
80
81    // We're constructed with the set of applications that are participating
82    // in backup.  This set changes as apps are installed & removed.
83    PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) {
84        mPackageManager = packageMgr;
85        mAllPackages = packages;
86        mRestoredSignatures = null;
87    }
88
89    public Metadata getRestoredMetadata(String packageName) {
90        if (mRestoredSignatures == null) {
91            Log.w(TAG, "getRestoredMetadata() before metadata read!");
92            return null;
93        }
94
95        return mRestoredSignatures.get(packageName);
96    }
97
98    // The backed up data is the signature block for each app, keyed by
99    // the package name.
100    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
101            ParcelFileDescriptor newState) {
102        if (DEBUG) Log.v(TAG, "onBackup()");
103
104        ByteArrayOutputStream bufStream = new ByteArrayOutputStream();  // we'll reuse these
105        DataOutputStream outWriter = new DataOutputStream(bufStream);
106        parseStateFile(oldState);
107
108        // If the stored version string differs, we need to re-backup all
109        // of the metadata.  We force this by removing everything from the
110        // "already backed up" map built by parseStateFile().
111        if (mStoredIncrementalVersion == null
112                || !mStoredIncrementalVersion.equals(Build.VERSION.INCREMENTAL)) {
113            Log.i(TAG, "Previous metadata " + mStoredIncrementalVersion + " mismatch vs "
114                    + Build.VERSION.INCREMENTAL + " - rewriting");
115            mExisting.clear();
116        }
117
118        try {
119            /*
120             * Global metadata:
121             *
122             * int SDKversion -- the SDK version of the OS itself on the device
123             *                   that produced this backup set.  Used to reject
124             *                   backups from later OSes onto earlier ones.
125             * String incremental -- the incremental release name of the OS stored in
126             *                       the backup set.
127             */
128            if (!mExisting.contains(GLOBAL_METADATA_KEY)) {
129                if (DEBUG) Log.v(TAG, "Storing global metadata key");
130                outWriter.writeInt(Build.VERSION.SDK_INT);
131                outWriter.writeUTF(Build.VERSION.INCREMENTAL);
132                byte[] metadata = bufStream.toByteArray();
133                data.writeEntityHeader(GLOBAL_METADATA_KEY, metadata.length);
134                data.writeEntityData(metadata, metadata.length);
135            } else {
136                if (DEBUG) Log.v(TAG, "Global metadata key already stored");
137                // don't consider it to have been skipped/deleted
138                mExisting.remove(GLOBAL_METADATA_KEY);
139            }
140
141            // For each app we have on device, see if we've backed it up yet.  If not,
142            // write its signature block to the output, keyed on the package name.
143            for (PackageInfo pkg : mAllPackages) {
144                String packName = pkg.packageName;
145                if (packName.equals(GLOBAL_METADATA_KEY)) {
146                    // We've already handled the metadata key; skip it here
147                    continue;
148                } else {
149                    PackageInfo info = null;
150                    try {
151                        info = mPackageManager.getPackageInfo(packName,
152                                PackageManager.GET_SIGNATURES);
153                    } catch (NameNotFoundException e) {
154                        // Weird; we just found it, and now are told it doesn't exist.
155                        // Treat it as having been removed from the device.
156                        mExisting.add(packName);
157                        continue;
158                    }
159
160                    boolean doBackup = false;
161                    if (!mExisting.contains(packName)) {
162                        // We haven't backed up this app before
163                        doBackup = true;
164                    } else {
165                        // We *have* backed this one up before.  Check whether the version
166                        // of the backup matches the version of the current app; if they
167                        // don't match, the app has been updated and we need to store its
168                        // metadata again.  In either case, take it out of mExisting so that
169                        // we don't consider it deleted later.
170                        if (info.versionCode != mStateVersions.get(packName).versionCode) {
171                            doBackup = true;
172                        }
173                        mExisting.remove(packName);
174                    }
175
176                    if (doBackup) {
177                        // We need to store this app's metadata
178                        /*
179                         * Metadata for each package:
180                         *
181                         * int version       -- [4] the package's versionCode
182                         * byte[] signatures -- [len] flattened Signature[] of the package
183                         */
184
185                        // marshal the version code in a canonical form
186                        bufStream.reset();
187                        outWriter.writeInt(info.versionCode);
188                        byte[] versionBuf = bufStream.toByteArray();
189
190                        byte[] sigs = flattenSignatureArray(info.signatures);
191
192                        // !!! TODO: take out this debugging
193                        if (DEBUG) {
194                            Log.v(TAG, "+ metadata for " + packName
195                                    + " version=" + info.versionCode
196                                    + " versionLen=" + versionBuf.length
197                                    + " sigsLen=" + sigs.length);
198                        }
199                        // Now we can write the backup entity for this package
200                        data.writeEntityHeader(packName, versionBuf.length + sigs.length);
201                        data.writeEntityData(versionBuf, versionBuf.length);
202                        data.writeEntityData(sigs, sigs.length);
203                    }
204                }
205            }
206
207            // At this point, the only entries in 'existing' are apps that were
208            // mentioned in the saved state file, but appear to no longer be present
209            // on the device.  Write a deletion entity for them.
210            for (String app : mExisting) {
211                // !!! TODO: take out this msg
212                if (DEBUG) Log.v(TAG, "- removing metadata for deleted pkg " + app);
213                try {
214                    data.writeEntityHeader(app, -1);
215                } catch (IOException e) {
216                    Log.e(TAG, "Unable to write package deletions!");
217                    return;
218                }
219            }
220        } catch (IOException e) {
221            // Real error writing data
222            Log.e(TAG, "Unable to write package backup data file!");
223            return;
224        }
225
226        // Finally, write the new state blob -- just the list of all apps we handled
227        writeStateFile(mAllPackages, newState);
228    }
229
230    // "Restore" here is a misnomer.  What we're really doing is reading back the
231    // set of app signatures associated with each backed-up app in this restore
232    // image.  We'll use those later to determine what we can legitimately restore.
233    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
234            throws IOException {
235        List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
236        HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
237        if (DEBUG) Log.v(TAG, "onRestore()");
238        int storedSystemVersion = -1;
239
240        while (data.readNextHeader()) {
241            String key = data.getKey();
242            int dataSize = data.getDataSize();
243
244            if (DEBUG) Log.v(TAG, "   got key=" + key + " dataSize=" + dataSize);
245
246            // generic setup to parse any entity data
247            byte[] dataBuf = new byte[dataSize];
248            data.readEntityData(dataBuf, 0, dataSize);
249            ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
250            DataInputStream in = new DataInputStream(baStream);
251
252            if (key.equals(GLOBAL_METADATA_KEY)) {
253                int storedSdkVersion = in.readInt();
254                if (DEBUG) Log.v(TAG, "   storedSystemVersion = " + storedSystemVersion);
255                if (storedSystemVersion > Build.VERSION.SDK_INT) {
256                    // returning before setting the sig map means we rejected the restore set
257                    Log.w(TAG, "Restore set was from a later version of Android; not restoring");
258                    return;
259                }
260                mStoredSdkVersion = storedSdkVersion;
261                mStoredIncrementalVersion = in.readUTF();
262                // !!! TODO: remove this debugging output
263                if (DEBUG) {
264                    Log.i(TAG, "Restore set version " + storedSystemVersion
265                            + " is compatible with OS version " + Build.VERSION.SDK_INT
266                            + " (" + mStoredIncrementalVersion + " vs "
267                            + Build.VERSION.INCREMENTAL + ")");
268                }
269            } else {
270                // it's a file metadata record
271                int versionCode = in.readInt();
272                Signature[] sigs = unflattenSignatureArray(in);
273//              !!! TODO: take out this debugging
274                if (DEBUG) {
275                    Log.i(TAG, "   restored metadata for " + key
276                            + " dataSize=" + dataSize
277                            + " versionCode=" + versionCode + " sigs=" + sigs);
278                }
279
280                ApplicationInfo app = new ApplicationInfo();
281                app.packageName = key;
282                restoredApps.add(app);
283                sigMap.put(key, new Metadata(versionCode, sigs));
284            }
285        }
286
287        // On successful completion, cache the signature map for the Backup Manager to use
288        mRestoredSignatures = sigMap;
289    }
290
291
292    // Util: convert an array of Signatures into a flattened byte buffer.  The
293    // flattened format contains enough info to reconstruct the signature array.
294    private byte[] flattenSignatureArray(Signature[] allSigs) {
295        ByteArrayOutputStream outBuf = new ByteArrayOutputStream();
296        DataOutputStream out = new DataOutputStream(outBuf);
297
298        // build the set of subsidiary buffers
299        try {
300            // first the # of signatures in the array
301            out.writeInt(allSigs.length);
302
303            // then the signatures themselves, length + flattened buffer
304            for (Signature sig : allSigs) {
305                byte[] flat = sig.toByteArray();
306                out.writeInt(flat.length);
307                out.write(flat);
308            }
309        } catch (IOException e) {
310            // very strange; we're writing to memory here.  abort.
311            return null;
312        }
313
314        return outBuf.toByteArray();
315    }
316
317    private Signature[] unflattenSignatureArray(/*byte[] buffer*/ DataInputStream in) {
318        Signature[] sigs = null;
319
320        try {
321            int num = in.readInt();
322            Log.v(TAG, " ... unflatten read " + num);
323            sigs = new Signature[num];
324            for (int i = 0; i < num; i++) {
325                int len = in.readInt();
326                byte[] flatSig = new byte[len];
327                in.read(flatSig);
328                sigs[i] = new Signature(flatSig);
329            }
330        } catch (EOFException e) {
331            // clean termination
332            if (sigs == null) {
333                Log.w(TAG, "Empty signature block found");
334            }
335        } catch (IOException e) {
336            Log.d(TAG, "Unable to unflatten sigs");
337            return null;
338        }
339
340        return sigs;
341    }
342
343    // Util: parse out an existing state file into a usable structure
344    private void parseStateFile(ParcelFileDescriptor stateFile) {
345        mExisting.clear();
346        mStateVersions.clear();
347        mStoredSdkVersion = 0;
348        mStoredIncrementalVersion = null;
349
350        // The state file is just the list of app names we have stored signatures for
351        // with the exception of the metadata block, to which is also appended the
352        // version numbers corresponding with the last time we wrote this PM block.
353        // If they mismatch the current system, we'll re-store the metadata key.
354        FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
355        DataInputStream in = new DataInputStream(instream);
356
357        int bufSize = 256;
358        byte[] buf = new byte[bufSize];
359        try {
360            String pkg = in.readUTF();
361            if (pkg.equals(GLOBAL_METADATA_KEY)) {
362                mStoredSdkVersion = in.readInt();
363                mStoredIncrementalVersion = in.readUTF();
364                mExisting.add(GLOBAL_METADATA_KEY);
365            } else {
366                Log.e(TAG, "No global metadata in state file!");
367                return;
368            }
369
370            // The global metadata was first; now read all the apps
371            while (true) {
372                pkg = in.readUTF();
373                int versionCode = in.readInt();
374                mExisting.add(pkg);
375                mStateVersions.put(pkg, new Metadata(versionCode, null));
376            }
377        } catch (EOFException eof) {
378            // safe; we're done
379        } catch (IOException e) {
380            // whoops, bad state file.  abort.
381            Log.e(TAG, "Unable to read Package Manager state file: " + e);
382        }
383    }
384
385    // Util: write out our new backup state file
386    private void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) {
387        FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
388        DataOutputStream out = new DataOutputStream(outstream);
389
390        try {
391            // by the time we get here we know we've stored the global metadata record
392            out.writeUTF(GLOBAL_METADATA_KEY);
393            out.writeInt(Build.VERSION.SDK_INT);
394            out.writeUTF(Build.VERSION.INCREMENTAL);
395
396            // now write all the app names too
397            for (PackageInfo pkg : pkgs) {
398                out.writeUTF(pkg.packageName);
399                out.writeInt(pkg.versionCode);
400            }
401        } catch (IOException e) {
402            Log.e(TAG, "Unable to write package manager state file!");
403            return;
404        }
405    }
406}
407