16785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate/*
26785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * Copyright (C) 2009 The Android Open Source Project
36785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate *
46785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * Licensed under the Apache License, Version 2.0 (the "License");
56785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * you may not use this file except in compliance with the License.
66785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * You may obtain a copy of the License at
76785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate *
86785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate *      http://www.apache.org/licenses/LICENSE-2.0
96785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate *
106785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * Unless required by applicable law or agreed to in writing, software
116785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * distributed under the License is distributed on an "AS IS" BASIS,
126785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * See the License for the specific language governing permissions and
146785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * limitations under the License.
156785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate */
166785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
176785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tatepackage com.android.server;
186785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
194528186e0d65fc68ef0dd1941aa2ac8aefcd55a3Christopher Tateimport android.app.backup.BackupAgent;
204528186e0d65fc68ef0dd1941aa2ac8aefcd55a3Christopher Tateimport android.app.backup.BackupDataInput;
214528186e0d65fc68ef0dd1941aa2ac8aefcd55a3Christopher Tateimport android.app.backup.BackupDataOutput;
226785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport android.content.pm.ApplicationInfo;
236785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport android.content.pm.PackageInfo;
246785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport android.content.pm.PackageManager;
256785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport android.content.pm.PackageManager.NameNotFoundException;
266785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport android.content.pm.Signature;
273a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tateimport android.os.Build;
286785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport android.os.ParcelFileDescriptor;
298a9b22056b13477f59df934928c00c58b5871c95Joe Onoratoimport android.util.Slog;
306785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
316785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport java.io.ByteArrayInputStream;
326785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport java.io.ByteArrayOutputStream;
336785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport java.io.DataInputStream;
346785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport java.io.DataOutputStream;
356785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport java.io.EOFException;
366785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport java.io.FileInputStream;
376785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport java.io.FileOutputStream;
386785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport java.io.IOException;
396785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport java.util.ArrayList;
406785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport java.util.HashMap;
416785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport java.util.HashSet;
426785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tateimport java.util.List;
43b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tateimport java.util.Set;
446785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
456785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate/**
466785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * We back up the signatures of each package so that during a system restore,
476785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * we can verify that the app whose data we think we have matches the app
486785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * actually resident on the device.
496785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate *
506785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * Since the Package Manager isn't a proper "application" we just provide a
516785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate * direct IBackupAgent implementation and hand-construct it at need.
526785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate */
536785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tatepublic class PackageManagerBackupAgent extends BackupAgent {
546785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    private static final String TAG = "PMBA";
55b808a939328b935592d9259e06c66b433a13c1a9Christopher Tate    private static final boolean DEBUG = false;
566785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
573a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate    // key under which we store global metadata (individual app metadata
583a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate    // is stored using the package name as a key)
593a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate    private static final String GLOBAL_METADATA_KEY = "@meta@";
603a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate
61efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor    private List<PackageInfo> mAllPackages;
626785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    private PackageManager mPackageManager;
6372d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate    // version & signature info of each app in a restore set
646aa41f4c575479672661f7eb4c704ef59d26a629Christopher Tate    private HashMap<String, Metadata> mRestoredSignatures;
6572d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate    // The version info of each backed-up app as read from the state file
6672d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate    private HashMap<String, Metadata> mStateVersions = new HashMap<String, Metadata>();
6772d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate
6872d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate    private final HashSet<String> mExisting = new HashSet<String>();
6972d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate    private int mStoredSdkVersion;
7072d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate    private String mStoredIncrementalVersion;
713d7cd13e772bde1c4a72fa4e740baa03cb042e6cChristopher Tate    private boolean mHasMetadata;
726aa41f4c575479672661f7eb4c704ef59d26a629Christopher Tate
736aa41f4c575479672661f7eb4c704ef59d26a629Christopher Tate    public class Metadata {
746aa41f4c575479672661f7eb4c704ef59d26a629Christopher Tate        public int versionCode;
756aa41f4c575479672661f7eb4c704ef59d26a629Christopher Tate        public Signature[] signatures;
766aa41f4c575479672661f7eb4c704ef59d26a629Christopher Tate
776aa41f4c575479672661f7eb4c704ef59d26a629Christopher Tate        Metadata(int version, Signature[] sigs) {
786aa41f4c575479672661f7eb4c704ef59d26a629Christopher Tate            versionCode = version;
796aa41f4c575479672661f7eb4c704ef59d26a629Christopher Tate            signatures = sigs;
806aa41f4c575479672661f7eb4c704ef59d26a629Christopher Tate        }
816aa41f4c575479672661f7eb4c704ef59d26a629Christopher Tate    }
826785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
836785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    // We're constructed with the set of applications that are participating
846785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    // in backup.  This set changes as apps are installed & removed.
85efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor    PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) {
866785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        mPackageManager = packageMgr;
87efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        mAllPackages = packages;
886785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        mRestoredSignatures = null;
893d7cd13e772bde1c4a72fa4e740baa03cb042e6cChristopher Tate        mHasMetadata = false;
903d7cd13e772bde1c4a72fa4e740baa03cb042e6cChristopher Tate    }
913d7cd13e772bde1c4a72fa4e740baa03cb042e6cChristopher Tate
923d7cd13e772bde1c4a72fa4e740baa03cb042e6cChristopher Tate    public boolean hasMetadata() {
933d7cd13e772bde1c4a72fa4e740baa03cb042e6cChristopher Tate        return mHasMetadata;
946785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    }
956785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
966aa41f4c575479672661f7eb4c704ef59d26a629Christopher Tate    public Metadata getRestoredMetadata(String packageName) {
976785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        if (mRestoredSignatures == null) {
988a9b22056b13477f59df934928c00c58b5871c95Joe Onorato            Slog.w(TAG, "getRestoredMetadata() before metadata read!");
996785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            return null;
1006785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        }
1016785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
1026785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        return mRestoredSignatures.get(packageName);
1036785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    }
104b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tate
105b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tate    public Set<String> getRestoredPackages() {
106b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tate        if (mRestoredSignatures == null) {
1078a9b22056b13477f59df934928c00c58b5871c95Joe Onorato            Slog.w(TAG, "getRestoredPackages() before metadata read!");
108b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tate            return null;
109b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tate        }
110b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tate
111b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tate        // This is technically the set of packages on the originating handset
112b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tate        // that had backup agents at all, not limited to the set of packages
113b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tate        // that had actually contributed a restore dataset, but it's a
114b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tate        // close enough approximation for our purposes and does not require any
115b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tate        // additional involvement by the transport to obtain.
116b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tate        return mRestoredSignatures.keySet();
117b49ceb3b8b17656984fd969d548dc912e7d2c7c1Christopher Tate    }
1186785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
1196785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    // The backed up data is the signature block for each app, keyed by
1206785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    // the package name.
1216785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
1226785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            ParcelFileDescriptor newState) {
1238a9b22056b13477f59df934928c00c58b5871c95Joe Onorato        if (DEBUG) Slog.v(TAG, "onBackup()");
1243a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate
125e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown        ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();  // we'll reuse these
126e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown        DataOutputStream outputBufferStream = new DataOutputStream(outputBuffer);
12772d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate        parseStateFile(oldState);
12872d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate
12972d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate        // If the stored version string differs, we need to re-backup all
13072d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate        // of the metadata.  We force this by removing everything from the
13172d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate        // "already backed up" map built by parseStateFile().
13272d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate        if (mStoredIncrementalVersion == null
13372d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                || !mStoredIncrementalVersion.equals(Build.VERSION.INCREMENTAL)) {
1348a9b22056b13477f59df934928c00c58b5871c95Joe Onorato            Slog.i(TAG, "Previous metadata " + mStoredIncrementalVersion + " mismatch vs "
13572d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                    + Build.VERSION.INCREMENTAL + " - rewriting");
13672d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate            mExisting.clear();
13772d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate        }
1383a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate
1393a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate        try {
1403a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            /*
1413a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate             * Global metadata:
1423a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate             *
14372d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate             * int SDKversion -- the SDK version of the OS itself on the device
14472d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate             *                   that produced this backup set.  Used to reject
14572d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate             *                   backups from later OSes onto earlier ones.
14672d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate             * String incremental -- the incremental release name of the OS stored in
14772d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate             *                       the backup set.
1483a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate             */
14972d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate            if (!mExisting.contains(GLOBAL_METADATA_KEY)) {
1508a9b22056b13477f59df934928c00c58b5871c95Joe Onorato                if (DEBUG) Slog.v(TAG, "Storing global metadata key");
151e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                outputBufferStream.writeInt(Build.VERSION.SDK_INT);
152e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                outputBufferStream.writeUTF(Build.VERSION.INCREMENTAL);
153e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                writeEntity(data, GLOBAL_METADATA_KEY, outputBuffer.toByteArray());
1546785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            } else {
1558a9b22056b13477f59df934928c00c58b5871c95Joe Onorato                if (DEBUG) Slog.v(TAG, "Global metadata key already stored");
15672d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                // don't consider it to have been skipped/deleted
15772d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                mExisting.remove(GLOBAL_METADATA_KEY);
1583a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            }
1593a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate
1603a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            // For each app we have on device, see if we've backed it up yet.  If not,
1613a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            // write its signature block to the output, keyed on the package name.
162efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor            for (PackageInfo pkg : mAllPackages) {
163efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor                String packName = pkg.packageName;
1646f317426e49e73ef3e50d8839877504039cd2fcaChristopher Tate                if (packName.equals(GLOBAL_METADATA_KEY)) {
1656f317426e49e73ef3e50d8839877504039cd2fcaChristopher Tate                    // We've already handled the metadata key; skip it here
1666f317426e49e73ef3e50d8839877504039cd2fcaChristopher Tate                    continue;
16772d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                } else {
16872d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                    PackageInfo info = null;
1693a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                    try {
17072d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                        info = mPackageManager.getPackageInfo(packName,
1713a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                                PackageManager.GET_SIGNATURES);
17272d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                    } catch (NameNotFoundException e) {
17372d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                        // Weird; we just found it, and now are told it doesn't exist.
17472d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                        // Treat it as having been removed from the device.
17572d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                        mExisting.add(packName);
17672d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                        continue;
17772d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                    }
17872d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate
179e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    if (mExisting.contains(packName)) {
180e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                        // We have backed up this app before.  Check whether the version
18172d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                        // of the backup matches the version of the current app; if they
18272d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                        // don't match, the app has been updated and we need to store its
18372d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                        // metadata again.  In either case, take it out of mExisting so that
18472d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                        // we don't consider it deleted later.
18572d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                        mExisting.remove(packName);
186e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                        if (info.versionCode == mStateVersions.get(packName).versionCode) {
187e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                            continue;
188e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                        }
189e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    }
190e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown
191e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    if (info.signatures == null || info.signatures.length == 0)
192e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    {
193e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                        Slog.w(TAG, "Not backing up package " + packName
194e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                                + " since it appears to have no signatures.");
195e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                        continue;
19672d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                    }
19772d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate
198e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    // We need to store this app's metadata
199e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    /*
200e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                     * Metadata for each package:
201e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                     *
202e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                     * int version       -- [4] the package's versionCode
203e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                     * byte[] signatures -- [len] flattened Signature[] of the package
204e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                     */
205e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown
206e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    // marshal the version code in a canonical form
207e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    outputBuffer.reset();
208e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    outputBufferStream.writeInt(info.versionCode);
209e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    writeSignatureArray(outputBufferStream, info.signatures);
210e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown
211e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    if (DEBUG) {
212e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                        Slog.v(TAG, "+ writing metadata for " + packName
213e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                                + " version=" + info.versionCode
214e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                                + " entityLen=" + outputBuffer.size());
2153a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                    }
216e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown
217e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    // Now we can write the backup entity for this package
218e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    writeEntity(data, packName, outputBuffer.toByteArray());
2196785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate                }
2206785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            }
2216785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
2223a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            // At this point, the only entries in 'existing' are apps that were
2233a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            // mentioned in the saved state file, but appear to no longer be present
2243a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            // on the device.  Write a deletion entity for them.
22572d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate            for (String app : mExisting) {
2268a9b22056b13477f59df934928c00c58b5871c95Joe Onorato                if (DEBUG) Slog.v(TAG, "- removing metadata for deleted pkg " + app);
2273a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                try {
2283a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                    data.writeEntityHeader(app, -1);
2293a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                } catch (IOException e) {
2308a9b22056b13477f59df934928c00c58b5871c95Joe Onorato                    Slog.e(TAG, "Unable to write package deletions!");
2313a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                    return;
2323a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                }
2336785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            }
2343a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate        } catch (IOException e) {
2353a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            // Real error writing data
2368a9b22056b13477f59df934928c00c58b5871c95Joe Onorato            Slog.e(TAG, "Unable to write package backup data file!");
2373a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            return;
2386785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        }
2396785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
2406785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        // Finally, write the new state blob -- just the list of all apps we handled
241efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor        writeStateFile(mAllPackages, newState);
2426785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    }
243e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown
244e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown    private static void writeEntity(BackupDataOutput data, String key, byte[] bytes)
245e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            throws IOException {
246e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown        data.writeEntityHeader(key, bytes.length);
247e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown        data.writeEntityData(bytes, bytes.length);
248e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown    }
2496785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
2506785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    // "Restore" here is a misnomer.  What we're really doing is reading back the
2516785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    // set of app signatures associated with each backed-up app in this restore
2526785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    // image.  We'll use those later to determine what we can legitimately restore.
2535cbbf5652a78902ac3382dc4a3583bc5b0351027Christopher Tate    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
2546785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            throws IOException {
2556785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
2566aa41f4c575479672661f7eb4c704ef59d26a629Christopher Tate        HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
2578a9b22056b13477f59df934928c00c58b5871c95Joe Onorato        if (DEBUG) Slog.v(TAG, "onRestore()");
2583a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate        int storedSystemVersion = -1;
2596785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
2606785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        while (data.readNextHeader()) {
2613a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            String key = data.getKey();
2626785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            int dataSize = data.getDataSize();
2636785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
2648a9b22056b13477f59df934928c00c58b5871c95Joe Onorato            if (DEBUG) Slog.v(TAG, "   got key=" + key + " dataSize=" + dataSize);
2653a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate
2663a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            // generic setup to parse any entity data
267e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            byte[] inputBytes = new byte[dataSize];
268e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            data.readEntityData(inputBytes, 0, dataSize);
269e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
270e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
2713a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate
2723a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            if (key.equals(GLOBAL_METADATA_KEY)) {
273e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                int storedSdkVersion = inputBufferStream.readInt();
2748a9b22056b13477f59df934928c00c58b5871c95Joe Onorato                if (DEBUG) Slog.v(TAG, "   storedSystemVersion = " + storedSystemVersion);
2753a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                if (storedSystemVersion > Build.VERSION.SDK_INT) {
2763a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                    // returning before setting the sig map means we rejected the restore set
2778a9b22056b13477f59df934928c00c58b5871c95Joe Onorato                    Slog.w(TAG, "Restore set was from a later version of Android; not restoring");
2783a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                    return;
2793a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                }
28072d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                mStoredSdkVersion = storedSdkVersion;
281e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                mStoredIncrementalVersion = inputBufferStream.readUTF();
2823d7cd13e772bde1c4a72fa4e740baa03cb042e6cChristopher Tate                mHasMetadata = true;
2833a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                if (DEBUG) {
2848a9b22056b13477f59df934928c00c58b5871c95Joe Onorato                    Slog.i(TAG, "Restore set version " + storedSystemVersion
28572d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                            + " is compatible with OS version " + Build.VERSION.SDK_INT
28672d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                            + " (" + mStoredIncrementalVersion + " vs "
28772d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                            + Build.VERSION.INCREMENTAL + ")");
2883a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                }
2893a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            } else {
2903a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                // it's a file metadata record
291e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                int versionCode = inputBufferStream.readInt();
292e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                Signature[] sigs = readSignatureArray(inputBufferStream);
2933a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                if (DEBUG) {
294e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    Slog.i(TAG, "   read metadata for " + key
2953a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                            + " dataSize=" + dataSize
2963a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                            + " versionCode=" + versionCode + " sigs=" + sigs);
2973a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                }
298e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown
299e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                if (sigs == null || sigs.length == 0) {
300e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    Slog.w(TAG, "Not restoring package " + key
301e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                            + " since it appears to have no signatures.");
302e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                    continue;
303e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                }
3043a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate
3053a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                ApplicationInfo app = new ApplicationInfo();
3063a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                app.packageName = key;
3073a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                restoredApps.add(app);
3083a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate                sigMap.put(key, new Metadata(versionCode, sigs));
3093a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            }
3106785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        }
3116785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
3123a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate        // On successful completion, cache the signature map for the Backup Manager to use
3136785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        mRestoredSignatures = sigMap;
3146785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    }
3156785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
316e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown    private static void writeSignatureArray(DataOutputStream out, Signature[] sigs)
317e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            throws IOException {
318e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown        // write the number of signatures in the array
319e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown        out.writeInt(sigs.length);
320e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown
321e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown        // write the signatures themselves, length + flattened buffer
322e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown        for (Signature sig : sigs) {
323e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            byte[] flat = sig.toByteArray();
324e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            out.writeInt(flat.length);
325e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            out.write(flat);
3266785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        }
3276785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    }
3286785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
329e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown    private static Signature[] readSignatureArray(DataInputStream in) {
3306785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        try {
331e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            int num;
332e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            try {
333e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                num = in.readInt();
334e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            } catch (EOFException e) {
335e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                // clean termination
336e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                Slog.w(TAG, "Read empty signature block");
337e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown                return null;
338e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            }
339e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown
3408a9b22056b13477f59df934928c00c58b5871c95Joe Onorato            if (DEBUG) Slog.v(TAG, " ... unflatten read " + num);
341e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown
3425a8a1151e267b29978f219f9569fdfc5e74cc210Christopher Tate            // Sensical?
3435a8a1151e267b29978f219f9569fdfc5e74cc210Christopher Tate            if (num > 20) {
3448a9b22056b13477f59df934928c00c58b5871c95Joe Onorato                Slog.e(TAG, "Suspiciously large sig count in restore data; aborting");
3455a8a1151e267b29978f219f9569fdfc5e74cc210Christopher Tate                throw new IllegalStateException("Bad restore state");
3465a8a1151e267b29978f219f9569fdfc5e74cc210Christopher Tate            }
347e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown
348e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            Signature[] sigs = new Signature[num];
3496785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            for (int i = 0; i < num; i++) {
3506785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate                int len = in.readInt();
3516785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate                byte[] flatSig = new byte[len];
3526785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate                in.read(flatSig);
3536785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate                sigs[i] = new Signature(flatSig);
3546785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            }
355e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            return sigs;
3566785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        } catch (IOException e) {
357e684d9582cedf9bd5cc6c6fe47c600a79a13d816Jeff Brown            Slog.e(TAG, "Unable to read signatures");
3586785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            return null;
3596785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        }
3606785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    }
3616785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
3626785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    // Util: parse out an existing state file into a usable structure
36372d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate    private void parseStateFile(ParcelFileDescriptor stateFile) {
36472d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate        mExisting.clear();
36572d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate        mStateVersions.clear();
36672d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate        mStoredSdkVersion = 0;
36772d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate        mStoredIncrementalVersion = null;
36872d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate
3696785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        // The state file is just the list of app names we have stored signatures for
37072d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate        // with the exception of the metadata block, to which is also appended the
37172d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate        // version numbers corresponding with the last time we wrote this PM block.
37272d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate        // If they mismatch the current system, we'll re-store the metadata key.
3736785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
3746785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        DataInputStream in = new DataInputStream(instream);
3756785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
3766785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        int bufSize = 256;
3776785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        byte[] buf = new byte[bufSize];
3786785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        try {
37972d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate            String pkg = in.readUTF();
38072d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate            if (pkg.equals(GLOBAL_METADATA_KEY)) {
38172d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                mStoredSdkVersion = in.readInt();
38272d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                mStoredIncrementalVersion = in.readUTF();
38372d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                mExisting.add(GLOBAL_METADATA_KEY);
38472d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate            } else {
3858a9b22056b13477f59df934928c00c58b5871c95Joe Onorato                Slog.e(TAG, "No global metadata in state file!");
38672d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                return;
38772d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate            }
38872d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate
38972d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate            // The global metadata was first; now read all the apps
39072d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate            while (true) {
39172d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                pkg = in.readUTF();
39272d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                int versionCode = in.readInt();
39372d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                mExisting.add(pkg);
39472d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                mStateVersions.put(pkg, new Metadata(versionCode, null));
3956785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            }
3966785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        } catch (EOFException eof) {
3976785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            // safe; we're done
3986785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        } catch (IOException e) {
3996785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            // whoops, bad state file.  abort.
4008a9b22056b13477f59df934928c00c58b5871c95Joe Onorato            Slog.e(TAG, "Unable to read Package Manager state file: " + e);
4016785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        }
4026785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    }
4036785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
4043a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate    // Util: write out our new backup state file
405efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor    private void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) {
4066785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
4076785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        DataOutputStream out = new DataOutputStream(outstream);
4086785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate
4093a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate        try {
4103a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            // by the time we get here we know we've stored the global metadata record
41172d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate            out.writeUTF(GLOBAL_METADATA_KEY);
41272d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate            out.writeInt(Build.VERSION.SDK_INT);
41372d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate            out.writeUTF(Build.VERSION.INCREMENTAL);
4143a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate
4153a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            // now write all the app names too
416efe52647f6b41993be43a5f47d1178bb0468cec8Dan Egnor            for (PackageInfo pkg : pkgs) {
41772d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                out.writeUTF(pkg.packageName);
41872d19aa51e90d45c7895629db78e548da2f6d469Christopher Tate                out.writeInt(pkg.versionCode);
4196785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate            }
4203a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate        } catch (IOException e) {
4218a9b22056b13477f59df934928c00c58b5871c95Joe Onorato            Slog.e(TAG, "Unable to write package manager state file!");
4223a31a93b8a195ae2d0180e6dfbf292da2e581f50Christopher Tate            return;
4236785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate        }
4246785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate    }
4256785dd842075889e5230d93ed9c0ab9c204ab432Christopher Tate}
426