blob: 26b57bf51667a0da894460585e6264ee394c1826 [file] [log] [blame]
Christopher Tate6785dd82009-06-18 15:58:25 -07001/*
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
Christopher Tate45281862010-03-05 15:46:30 -080019import android.app.backup.BackupAgent;
20import android.app.backup.BackupDataInput;
21import android.app.backup.BackupDataOutput;
Christopher Tate6785dd82009-06-18 15:58:25 -070022import 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;
Christopher Tate3a31a932009-06-22 15:10:30 -070027import android.os.Build;
Christopher Tate6785dd82009-06-18 15:58:25 -070028import android.os.ParcelFileDescriptor;
Joe Onorato8a9b2202010-02-26 18:56:32 -080029import android.util.Slog;
Christopher Tate6785dd82009-06-18 15:58:25 -070030
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;
Christopher Tateb49ceb32010-02-08 16:22:24 -080043import java.util.Set;
Christopher Tate6785dd82009-06-18 15:58:25 -070044
Christopher Tate6785dd82009-06-18 15:58:25 -070045/**
46 * We back up the signatures of each package so that during a system restore,
47 * we can verify that the app whose data we think we have matches the app
48 * actually resident on the device.
49 *
50 * Since the Package Manager isn't a proper "application" we just provide a
51 * direct IBackupAgent implementation and hand-construct it at need.
52 */
53public class PackageManagerBackupAgent extends BackupAgent {
54 private static final String TAG = "PMBA";
Christopher Tateb808a9392009-09-29 16:09:52 -070055 private static final boolean DEBUG = false;
Christopher Tate6785dd82009-06-18 15:58:25 -070056
Christopher Tate3a31a932009-06-22 15:10:30 -070057 // key under which we store global metadata (individual app metadata
58 // is stored using the package name as a key)
59 private static final String GLOBAL_METADATA_KEY = "@meta@";
60
Dan Egnorefe52642009-06-24 00:16:33 -070061 private List<PackageInfo> mAllPackages;
Christopher Tate6785dd82009-06-18 15:58:25 -070062 private PackageManager mPackageManager;
Christopher Tate72d19aa2009-06-30 12:47:33 -070063 // version & signature info of each app in a restore set
Christopher Tate6aa41f42009-06-19 14:14:22 -070064 private HashMap<String, Metadata> mRestoredSignatures;
Christopher Tate72d19aa2009-06-30 12:47:33 -070065 // The version info of each backed-up app as read from the state file
66 private HashMap<String, Metadata> mStateVersions = new HashMap<String, Metadata>();
67
68 private final HashSet<String> mExisting = new HashSet<String>();
69 private int mStoredSdkVersion;
70 private String mStoredIncrementalVersion;
Christopher Tate3d7cd132009-07-07 14:23:07 -070071 private boolean mHasMetadata;
Christopher Tate6aa41f42009-06-19 14:14:22 -070072
73 public class Metadata {
74 public int versionCode;
75 public Signature[] signatures;
76
77 Metadata(int version, Signature[] sigs) {
78 versionCode = version;
79 signatures = sigs;
80 }
81 }
Christopher Tate6785dd82009-06-18 15:58:25 -070082
83 // We're constructed with the set of applications that are participating
84 // in backup. This set changes as apps are installed & removed.
Dan Egnorefe52642009-06-24 00:16:33 -070085 PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) {
Christopher Tate6785dd82009-06-18 15:58:25 -070086 mPackageManager = packageMgr;
Dan Egnorefe52642009-06-24 00:16:33 -070087 mAllPackages = packages;
Christopher Tate6785dd82009-06-18 15:58:25 -070088 mRestoredSignatures = null;
Christopher Tate3d7cd132009-07-07 14:23:07 -070089 mHasMetadata = false;
90 }
91
92 public boolean hasMetadata() {
93 return mHasMetadata;
Christopher Tate6785dd82009-06-18 15:58:25 -070094 }
95
Christopher Tate6aa41f42009-06-19 14:14:22 -070096 public Metadata getRestoredMetadata(String packageName) {
Christopher Tate6785dd82009-06-18 15:58:25 -070097 if (mRestoredSignatures == null) {
Joe Onorato8a9b2202010-02-26 18:56:32 -080098 Slog.w(TAG, "getRestoredMetadata() before metadata read!");
Christopher Tate6785dd82009-06-18 15:58:25 -070099 return null;
100 }
101
102 return mRestoredSignatures.get(packageName);
103 }
Christopher Tateb49ceb32010-02-08 16:22:24 -0800104
105 public Set<String> getRestoredPackages() {
106 if (mRestoredSignatures == null) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800107 Slog.w(TAG, "getRestoredPackages() before metadata read!");
Christopher Tateb49ceb32010-02-08 16:22:24 -0800108 return null;
109 }
110
111 // This is technically the set of packages on the originating handset
112 // that had backup agents at all, not limited to the set of packages
113 // that had actually contributed a restore dataset, but it's a
114 // close enough approximation for our purposes and does not require any
115 // additional involvement by the transport to obtain.
116 return mRestoredSignatures.keySet();
117 }
Christopher Tate6785dd82009-06-18 15:58:25 -0700118
119 // The backed up data is the signature block for each app, keyed by
120 // the package name.
121 public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
122 ParcelFileDescriptor newState) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800123 if (DEBUG) Slog.v(TAG, "onBackup()");
Christopher Tate3a31a932009-06-22 15:10:30 -0700124
Christopher Tate6aa41f42009-06-19 14:14:22 -0700125 ByteArrayOutputStream bufStream = new ByteArrayOutputStream(); // we'll reuse these
126 DataOutputStream outWriter = new DataOutputStream(bufStream);
Christopher Tate72d19aa2009-06-30 12:47:33 -0700127 parseStateFile(oldState);
128
129 // If the stored version string differs, we need to re-backup all
130 // of the metadata. We force this by removing everything from the
131 // "already backed up" map built by parseStateFile().
132 if (mStoredIncrementalVersion == null
133 || !mStoredIncrementalVersion.equals(Build.VERSION.INCREMENTAL)) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800134 Slog.i(TAG, "Previous metadata " + mStoredIncrementalVersion + " mismatch vs "
Christopher Tate72d19aa2009-06-30 12:47:33 -0700135 + Build.VERSION.INCREMENTAL + " - rewriting");
136 mExisting.clear();
137 }
Christopher Tate6aa41f42009-06-19 14:14:22 -0700138
Christopher Tate3a31a932009-06-22 15:10:30 -0700139 try {
140 /*
141 * Global metadata:
142 *
Christopher Tate72d19aa2009-06-30 12:47:33 -0700143 * int SDKversion -- the SDK version of the OS itself on the device
144 * that produced this backup set. Used to reject
145 * backups from later OSes onto earlier ones.
146 * String incremental -- the incremental release name of the OS stored in
147 * the backup set.
Christopher Tate3a31a932009-06-22 15:10:30 -0700148 */
Christopher Tate72d19aa2009-06-30 12:47:33 -0700149 if (!mExisting.contains(GLOBAL_METADATA_KEY)) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800150 if (DEBUG) Slog.v(TAG, "Storing global metadata key");
Christopher Tate3a31a932009-06-22 15:10:30 -0700151 outWriter.writeInt(Build.VERSION.SDK_INT);
Christopher Tate72d19aa2009-06-30 12:47:33 -0700152 outWriter.writeUTF(Build.VERSION.INCREMENTAL);
Christopher Tate3a31a932009-06-22 15:10:30 -0700153 byte[] metadata = bufStream.toByteArray();
154 data.writeEntityHeader(GLOBAL_METADATA_KEY, metadata.length);
155 data.writeEntityData(metadata, metadata.length);
156 } else {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800157 if (DEBUG) Slog.v(TAG, "Global metadata key already stored");
Christopher Tate72d19aa2009-06-30 12:47:33 -0700158 // don't consider it to have been skipped/deleted
159 mExisting.remove(GLOBAL_METADATA_KEY);
Christopher Tate3a31a932009-06-22 15:10:30 -0700160 }
Christopher Tate6aa41f42009-06-19 14:14:22 -0700161
Christopher Tate3a31a932009-06-22 15:10:30 -0700162 // For each app we have on device, see if we've backed it up yet. If not,
163 // write its signature block to the output, keyed on the package name.
Dan Egnorefe52642009-06-24 00:16:33 -0700164 for (PackageInfo pkg : mAllPackages) {
165 String packName = pkg.packageName;
Christopher Tate6f317422009-06-29 18:52:55 -0700166 if (packName.equals(GLOBAL_METADATA_KEY)) {
167 // We've already handled the metadata key; skip it here
168 continue;
Christopher Tate72d19aa2009-06-30 12:47:33 -0700169 } else {
170 PackageInfo info = null;
Christopher Tate3a31a932009-06-22 15:10:30 -0700171 try {
Christopher Tate72d19aa2009-06-30 12:47:33 -0700172 info = mPackageManager.getPackageInfo(packName,
Christopher Tate3a31a932009-06-22 15:10:30 -0700173 PackageManager.GET_SIGNATURES);
Christopher Tate72d19aa2009-06-30 12:47:33 -0700174 } catch (NameNotFoundException e) {
175 // Weird; we just found it, and now are told it doesn't exist.
176 // Treat it as having been removed from the device.
177 mExisting.add(packName);
178 continue;
179 }
180
181 boolean doBackup = false;
182 if (!mExisting.contains(packName)) {
183 // We haven't backed up this app before
184 doBackup = true;
185 } else {
186 // We *have* backed this one up before. Check whether the version
187 // of the backup matches the version of the current app; if they
188 // don't match, the app has been updated and we need to store its
189 // metadata again. In either case, take it out of mExisting so that
190 // we don't consider it deleted later.
191 if (info.versionCode != mStateVersions.get(packName).versionCode) {
192 doBackup = true;
193 }
194 mExisting.remove(packName);
195 }
196
197 if (doBackup) {
198 // We need to store this app's metadata
Christopher Tate3a31a932009-06-22 15:10:30 -0700199 /*
200 * Metadata for each package:
201 *
202 * int version -- [4] the package's versionCode
203 * byte[] signatures -- [len] flattened Signature[] of the package
204 */
Christopher Tate6aa41f42009-06-19 14:14:22 -0700205
Christopher Tate72d19aa2009-06-30 12:47:33 -0700206 // marshal the version code in a canonical form
Christopher Tate3a31a932009-06-22 15:10:30 -0700207 bufStream.reset();
208 outWriter.writeInt(info.versionCode);
209 byte[] versionBuf = bufStream.toByteArray();
210
211 byte[] sigs = flattenSignatureArray(info.signatures);
212
Christopher Tate3a31a932009-06-22 15:10:30 -0700213 if (DEBUG) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800214 Slog.v(TAG, "+ metadata for " + packName
Christopher Tate3a31a932009-06-22 15:10:30 -0700215 + " version=" + info.versionCode
216 + " versionLen=" + versionBuf.length
217 + " sigsLen=" + sigs.length);
218 }
219 // Now we can write the backup entity for this package
220 data.writeEntityHeader(packName, versionBuf.length + sigs.length);
221 data.writeEntityData(versionBuf, versionBuf.length);
222 data.writeEntityData(sigs, sigs.length);
Christopher Tate3a31a932009-06-22 15:10:30 -0700223 }
224 }
225 }
226
227 // At this point, the only entries in 'existing' are apps that were
228 // mentioned in the saved state file, but appear to no longer be present
229 // on the device. Write a deletion entity for them.
Christopher Tate72d19aa2009-06-30 12:47:33 -0700230 for (String app : mExisting) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800231 if (DEBUG) Slog.v(TAG, "- removing metadata for deleted pkg " + app);
Christopher Tate3a31a932009-06-22 15:10:30 -0700232 try {
233 data.writeEntityHeader(app, -1);
Christopher Tate6785dd82009-06-18 15:58:25 -0700234 } catch (IOException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800235 Slog.e(TAG, "Unable to write package deletions!");
Christopher Tate6785dd82009-06-18 15:58:25 -0700236 return;
237 }
Christopher Tate6785dd82009-06-18 15:58:25 -0700238 }
Christopher Tate3a31a932009-06-22 15:10:30 -0700239 } catch (IOException e) {
240 // Real error writing data
Joe Onorato8a9b2202010-02-26 18:56:32 -0800241 Slog.e(TAG, "Unable to write package backup data file!");
Christopher Tate3a31a932009-06-22 15:10:30 -0700242 return;
Christopher Tate6785dd82009-06-18 15:58:25 -0700243 }
244
245 // Finally, write the new state blob -- just the list of all apps we handled
Dan Egnorefe52642009-06-24 00:16:33 -0700246 writeStateFile(mAllPackages, newState);
Christopher Tate6785dd82009-06-18 15:58:25 -0700247 }
248
249 // "Restore" here is a misnomer. What we're really doing is reading back the
250 // set of app signatures associated with each backed-up app in this restore
251 // image. We'll use those later to determine what we can legitimately restore.
Christopher Tate5cbbf562009-06-22 16:44:51 -0700252 public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
Christopher Tate6785dd82009-06-18 15:58:25 -0700253 throws IOException {
254 List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
Christopher Tate6aa41f42009-06-19 14:14:22 -0700255 HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
Joe Onorato8a9b2202010-02-26 18:56:32 -0800256 if (DEBUG) Slog.v(TAG, "onRestore()");
Christopher Tate3a31a932009-06-22 15:10:30 -0700257 int storedSystemVersion = -1;
Christopher Tate6785dd82009-06-18 15:58:25 -0700258
259 while (data.readNextHeader()) {
Christopher Tate3a31a932009-06-22 15:10:30 -0700260 String key = data.getKey();
Christopher Tate6785dd82009-06-18 15:58:25 -0700261 int dataSize = data.getDataSize();
Christopher Tate6785dd82009-06-18 15:58:25 -0700262
Joe Onorato8a9b2202010-02-26 18:56:32 -0800263 if (DEBUG) Slog.v(TAG, " got key=" + key + " dataSize=" + dataSize);
Christopher Tate6aa41f42009-06-19 14:14:22 -0700264
Christopher Tate3a31a932009-06-22 15:10:30 -0700265 // generic setup to parse any entity data
266 byte[] dataBuf = new byte[dataSize];
267 data.readEntityData(dataBuf, 0, dataSize);
268 ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
269 DataInputStream in = new DataInputStream(baStream);
270
271 if (key.equals(GLOBAL_METADATA_KEY)) {
Christopher Tate72d19aa2009-06-30 12:47:33 -0700272 int storedSdkVersion = in.readInt();
Joe Onorato8a9b2202010-02-26 18:56:32 -0800273 if (DEBUG) Slog.v(TAG, " storedSystemVersion = " + storedSystemVersion);
Christopher Tate3a31a932009-06-22 15:10:30 -0700274 if (storedSystemVersion > Build.VERSION.SDK_INT) {
275 // returning before setting the sig map means we rejected the restore set
Joe Onorato8a9b2202010-02-26 18:56:32 -0800276 Slog.w(TAG, "Restore set was from a later version of Android; not restoring");
Christopher Tate3a31a932009-06-22 15:10:30 -0700277 return;
278 }
Christopher Tate72d19aa2009-06-30 12:47:33 -0700279 mStoredSdkVersion = storedSdkVersion;
280 mStoredIncrementalVersion = in.readUTF();
Christopher Tate3d7cd132009-07-07 14:23:07 -0700281 mHasMetadata = true;
Christopher Tate3a31a932009-06-22 15:10:30 -0700282 if (DEBUG) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800283 Slog.i(TAG, "Restore set version " + storedSystemVersion
Christopher Tate72d19aa2009-06-30 12:47:33 -0700284 + " is compatible with OS version " + Build.VERSION.SDK_INT
285 + " (" + mStoredIncrementalVersion + " vs "
286 + Build.VERSION.INCREMENTAL + ")");
Christopher Tate3a31a932009-06-22 15:10:30 -0700287 }
288 } else {
289 // it's a file metadata record
290 int versionCode = in.readInt();
291 Signature[] sigs = unflattenSignatureArray(in);
Christopher Tate3a31a932009-06-22 15:10:30 -0700292 if (DEBUG) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800293 Slog.i(TAG, " restored metadata for " + key
Christopher Tate3a31a932009-06-22 15:10:30 -0700294 + " dataSize=" + dataSize
295 + " versionCode=" + versionCode + " sigs=" + sigs);
296 }
297
298 ApplicationInfo app = new ApplicationInfo();
299 app.packageName = key;
300 restoredApps.add(app);
301 sigMap.put(key, new Metadata(versionCode, sigs));
Christopher Tate6785dd82009-06-18 15:58:25 -0700302 }
Christopher Tate6785dd82009-06-18 15:58:25 -0700303 }
304
Christopher Tate3a31a932009-06-22 15:10:30 -0700305 // On successful completion, cache the signature map for the Backup Manager to use
Christopher Tate6785dd82009-06-18 15:58:25 -0700306 mRestoredSignatures = sigMap;
307 }
308
309
310 // Util: convert an array of Signatures into a flattened byte buffer. The
311 // flattened format contains enough info to reconstruct the signature array.
312 private byte[] flattenSignatureArray(Signature[] allSigs) {
313 ByteArrayOutputStream outBuf = new ByteArrayOutputStream();
314 DataOutputStream out = new DataOutputStream(outBuf);
315
316 // build the set of subsidiary buffers
317 try {
318 // first the # of signatures in the array
319 out.writeInt(allSigs.length);
320
321 // then the signatures themselves, length + flattened buffer
322 for (Signature sig : allSigs) {
323 byte[] flat = sig.toByteArray();
324 out.writeInt(flat.length);
325 out.write(flat);
326 }
327 } catch (IOException e) {
328 // very strange; we're writing to memory here. abort.
329 return null;
330 }
331
332 return outBuf.toByteArray();
333 }
334
Christopher Tate6aa41f42009-06-19 14:14:22 -0700335 private Signature[] unflattenSignatureArray(/*byte[] buffer*/ DataInputStream in) {
Christopher Tate6785dd82009-06-18 15:58:25 -0700336 Signature[] sigs = null;
337
338 try {
339 int num = in.readInt();
Joe Onorato8a9b2202010-02-26 18:56:32 -0800340 if (DEBUG) Slog.v(TAG, " ... unflatten read " + num);
Christopher Tate5a8a1152009-09-10 16:08:47 -0700341
342 // Sensical?
343 if (num > 20) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800344 Slog.e(TAG, "Suspiciously large sig count in restore data; aborting");
Christopher Tate5a8a1152009-09-10 16:08:47 -0700345 throw new IllegalStateException("Bad restore state");
346 }
347
Christopher Tate6785dd82009-06-18 15:58:25 -0700348 sigs = new Signature[num];
349 for (int i = 0; i < num; i++) {
350 int len = in.readInt();
351 byte[] flatSig = new byte[len];
352 in.read(flatSig);
353 sigs[i] = new Signature(flatSig);
354 }
355 } catch (EOFException e) {
356 // clean termination
357 if (sigs == null) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800358 Slog.w(TAG, "Empty signature block found");
Christopher Tate6785dd82009-06-18 15:58:25 -0700359 }
360 } catch (IOException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800361 Slog.e(TAG, "Unable to unflatten sigs");
Christopher Tate6785dd82009-06-18 15:58:25 -0700362 return null;
363 }
364
365 return sigs;
366 }
367
368 // Util: parse out an existing state file into a usable structure
Christopher Tate72d19aa2009-06-30 12:47:33 -0700369 private void parseStateFile(ParcelFileDescriptor stateFile) {
370 mExisting.clear();
371 mStateVersions.clear();
372 mStoredSdkVersion = 0;
373 mStoredIncrementalVersion = null;
374
Christopher Tate6785dd82009-06-18 15:58:25 -0700375 // The state file is just the list of app names we have stored signatures for
Christopher Tate72d19aa2009-06-30 12:47:33 -0700376 // with the exception of the metadata block, to which is also appended the
377 // version numbers corresponding with the last time we wrote this PM block.
378 // If they mismatch the current system, we'll re-store the metadata key.
Christopher Tate6785dd82009-06-18 15:58:25 -0700379 FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
380 DataInputStream in = new DataInputStream(instream);
381
382 int bufSize = 256;
383 byte[] buf = new byte[bufSize];
384 try {
Christopher Tate72d19aa2009-06-30 12:47:33 -0700385 String pkg = in.readUTF();
386 if (pkg.equals(GLOBAL_METADATA_KEY)) {
387 mStoredSdkVersion = in.readInt();
388 mStoredIncrementalVersion = in.readUTF();
389 mExisting.add(GLOBAL_METADATA_KEY);
390 } else {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800391 Slog.e(TAG, "No global metadata in state file!");
Christopher Tate72d19aa2009-06-30 12:47:33 -0700392 return;
Christopher Tate6785dd82009-06-18 15:58:25 -0700393 }
Christopher Tate72d19aa2009-06-30 12:47:33 -0700394
395 // The global metadata was first; now read all the apps
396 while (true) {
397 pkg = in.readUTF();
398 int versionCode = in.readInt();
399 mExisting.add(pkg);
400 mStateVersions.put(pkg, new Metadata(versionCode, null));
401 }
Christopher Tate6785dd82009-06-18 15:58:25 -0700402 } catch (EOFException eof) {
403 // safe; we're done
404 } catch (IOException e) {
405 // whoops, bad state file. abort.
Joe Onorato8a9b2202010-02-26 18:56:32 -0800406 Slog.e(TAG, "Unable to read Package Manager state file: " + e);
Christopher Tate6785dd82009-06-18 15:58:25 -0700407 }
Christopher Tate6785dd82009-06-18 15:58:25 -0700408 }
409
Christopher Tate3a31a932009-06-22 15:10:30 -0700410 // Util: write out our new backup state file
Dan Egnorefe52642009-06-24 00:16:33 -0700411 private void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) {
Christopher Tate6785dd82009-06-18 15:58:25 -0700412 FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
413 DataOutputStream out = new DataOutputStream(outstream);
414
Christopher Tate3a31a932009-06-22 15:10:30 -0700415 try {
416 // by the time we get here we know we've stored the global metadata record
Christopher Tate72d19aa2009-06-30 12:47:33 -0700417 out.writeUTF(GLOBAL_METADATA_KEY);
418 out.writeInt(Build.VERSION.SDK_INT);
419 out.writeUTF(Build.VERSION.INCREMENTAL);
Christopher Tate3a31a932009-06-22 15:10:30 -0700420
421 // now write all the app names too
Dan Egnorefe52642009-06-24 00:16:33 -0700422 for (PackageInfo pkg : pkgs) {
Christopher Tate72d19aa2009-06-30 12:47:33 -0700423 out.writeUTF(pkg.packageName);
424 out.writeInt(pkg.versionCode);
Christopher Tate6785dd82009-06-18 15:58:25 -0700425 }
Christopher Tate3a31a932009-06-22 15:10:30 -0700426 } catch (IOException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800427 Slog.e(TAG, "Unable to write package manager state file!");
Christopher Tate3a31a932009-06-22 15:10:30 -0700428 return;
Christopher Tate6785dd82009-06-18 15:58:25 -0700429 }
430 }
431}