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 com.android.server.backup;
18 
19 import android.app.backup.BackupAgent;
20 import android.app.backup.BackupDataInput;
21 import android.app.backup.BackupDataOutput;
22 import android.content.ComponentName;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.pm.Signature;
29 import android.os.Build;
30 import android.os.ParcelFileDescriptor;
31 import android.util.Slog;
32 
33 import java.io.BufferedInputStream;
34 import java.io.BufferedOutputStream;
35 import java.io.ByteArrayInputStream;
36 import java.io.ByteArrayOutputStream;
37 import java.io.DataInputStream;
38 import java.io.DataOutputStream;
39 import java.io.EOFException;
40 import java.io.FileInputStream;
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 import java.util.ArrayList;
44 import java.util.HashMap;
45 import java.util.HashSet;
46 import java.util.List;
47 import java.util.Set;
48 
49 import java.util.Objects;
50 
51 /**
52  * We back up the signatures of each package so that during a system restore,
53  * we can verify that the app whose data we think we have matches the app
54  * actually resident on the device.
55  *
56  * Since the Package Manager isn't a proper "application" we just provide a
57  * direct IBackupAgent implementation and hand-construct it at need.
58  */
59 public class PackageManagerBackupAgent extends BackupAgent {
60     private static final String TAG = "PMBA";
61     private static final boolean DEBUG = false;
62 
63     // key under which we store global metadata (individual app metadata
64     // is stored using the package name as a key)
65     private static final String GLOBAL_METADATA_KEY = "@meta@";
66 
67     // key under which we store the identity of the user's chosen default home app
68     private static final String DEFAULT_HOME_KEY = "@home@";
69 
70     // Sentinel: start of state file, followed by a version number
71     private static final String STATE_FILE_HEADER = "=state=";
72     private static final int STATE_FILE_VERSION = 2;
73 
74     // Current version of the saved ancestral-dataset file format
75     private static final int ANCESTRAL_RECORD_VERSION = 1;
76 
77     private List<PackageInfo> mAllPackages;
78     private PackageManager mPackageManager;
79     // version & signature info of each app in a restore set
80     private HashMap<String, Metadata> mRestoredSignatures;
81     // The version info of each backed-up app as read from the state file
82     private HashMap<String, Metadata> mStateVersions = new HashMap<String, Metadata>();
83 
84     private final HashSet<String> mExisting = new HashSet<String>();
85     private int mStoredSdkVersion;
86     private String mStoredIncrementalVersion;
87     private ComponentName mStoredHomeComponent;
88     private long mStoredHomeVersion;
89     private ArrayList<byte[]> mStoredHomeSigHashes;
90 
91     private boolean mHasMetadata;
92     private ComponentName mRestoredHome;
93     private long mRestoredHomeVersion;
94     private String mRestoredHomeInstaller;
95     private ArrayList<byte[]> mRestoredHomeSigHashes;
96 
97     // For compactness we store the SHA-256 hash of each app's Signatures
98     // rather than the Signature blocks themselves.
99     public class Metadata {
100         public int versionCode;
101         public ArrayList<byte[]> sigHashes;
102 
Metadata(int version, ArrayList<byte[]> hashes)103         Metadata(int version, ArrayList<byte[]> hashes) {
104             versionCode = version;
105             sigHashes = hashes;
106         }
107     }
108 
109     // We're constructed with the set of applications that are participating
110     // in backup.  This set changes as apps are installed & removed.
PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages)111     PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) {
112         init(packageMgr, packages);
113     }
114 
PackageManagerBackupAgent(PackageManager packageMgr)115     PackageManagerBackupAgent(PackageManager packageMgr) {
116         init(packageMgr, null);
117 
118         evaluateStorablePackages();
119     }
120 
init(PackageManager packageMgr, List<PackageInfo> packages)121     private void init(PackageManager packageMgr, List<PackageInfo> packages) {
122         mPackageManager = packageMgr;
123         mAllPackages = packages;
124         mRestoredSignatures = null;
125         mHasMetadata = false;
126 
127         mStoredSdkVersion = Build.VERSION.SDK_INT;
128         mStoredIncrementalVersion = Build.VERSION.INCREMENTAL;
129     }
130 
131     // We will need to refresh our understanding of what is eligible for
132     // backup periodically; this entry point serves that purpose.
evaluateStorablePackages()133     public void evaluateStorablePackages() {
134         mAllPackages = getStorableApplications(mPackageManager);
135     }
136 
getStorableApplications(PackageManager pm)137     public static List<PackageInfo> getStorableApplications(PackageManager pm) {
138         List<PackageInfo> pkgs;
139         pkgs = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
140         int N = pkgs.size();
141         for (int a = N-1; a >= 0; a--) {
142             PackageInfo pkg = pkgs.get(a);
143             if (!BackupManagerService.appIsEligibleForBackup(pkg.applicationInfo)) {
144                 pkgs.remove(a);
145             }
146         }
147         return pkgs;
148     }
149 
hasMetadata()150     public boolean hasMetadata() {
151         return mHasMetadata;
152     }
153 
getRestoredMetadata(String packageName)154     public Metadata getRestoredMetadata(String packageName) {
155         if (mRestoredSignatures == null) {
156             Slog.w(TAG, "getRestoredMetadata() before metadata read!");
157             return null;
158         }
159 
160         return mRestoredSignatures.get(packageName);
161     }
162 
getRestoredPackages()163     public Set<String> getRestoredPackages() {
164         if (mRestoredSignatures == null) {
165             Slog.w(TAG, "getRestoredPackages() before metadata read!");
166             return null;
167         }
168 
169         // This is technically the set of packages on the originating handset
170         // that had backup agents at all, not limited to the set of packages
171         // that had actually contributed a restore dataset, but it's a
172         // close enough approximation for our purposes and does not require any
173         // additional involvement by the transport to obtain.
174         return mRestoredSignatures.keySet();
175     }
176 
177     // The backed up data is the signature block for each app, keyed by
178     // the package name.
onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)179     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
180             ParcelFileDescriptor newState) {
181         if (DEBUG) Slog.v(TAG, "onBackup()");
182 
183         ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();  // we'll reuse these
184         DataOutputStream outputBufferStream = new DataOutputStream(outputBuffer);
185         parseStateFile(oldState);
186 
187         // If the stored version string differs, we need to re-backup all
188         // of the metadata.  We force this by removing everything from the
189         // "already backed up" map built by parseStateFile().
190         if (mStoredIncrementalVersion == null
191                 || !mStoredIncrementalVersion.equals(Build.VERSION.INCREMENTAL)) {
192             Slog.i(TAG, "Previous metadata " + mStoredIncrementalVersion + " mismatch vs "
193                     + Build.VERSION.INCREMENTAL + " - rewriting");
194             mExisting.clear();
195         }
196 
197         long homeVersion = 0;
198         ArrayList<byte[]> homeSigHashes = null;
199         PackageInfo homeInfo = null;
200         String homeInstaller = null;
201         ComponentName home = getPreferredHomeComponent();
202         if (home != null) {
203             try {
204                 homeInfo = mPackageManager.getPackageInfo(home.getPackageName(),
205                         PackageManager.GET_SIGNATURES);
206                 homeInstaller = mPackageManager.getInstallerPackageName(home.getPackageName());
207                 homeVersion = homeInfo.versionCode;
208                 homeSigHashes = hashSignatureArray(homeInfo.signatures);
209             } catch (NameNotFoundException e) {
210                 Slog.w(TAG, "Can't access preferred home info");
211                 // proceed as though there were no preferred home set
212                 home = null;
213             }
214         }
215 
216         try {
217             // We need to push a new preferred-home-app record if:
218             //    1. the version of the home app has changed since our last backup;
219             //    2. the home app [or absence] we now use differs from the prior state,
220             // OR 3. it looks like we use the same home app + version as before, but
221             //       the signatures don't match so we treat them as different apps.
222             final boolean needHomeBackup = (homeVersion != mStoredHomeVersion)
223                     || !Objects.equals(home, mStoredHomeComponent)
224                     || (home != null
225                         && !BackupManagerService.signaturesMatch(mStoredHomeSigHashes, homeInfo));
226             if (needHomeBackup) {
227                 if (DEBUG) {
228                     Slog.i(TAG, "Home preference changed; backing up new state " + home);
229                 }
230                 if (home != null) {
231                     outputBufferStream.writeUTF(home.flattenToString());
232                     outputBufferStream.writeLong(homeVersion);
233                     outputBufferStream.writeUTF(homeInstaller != null ? homeInstaller : "" );
234                     writeSignatureHashArray(outputBufferStream, homeSigHashes);
235                     writeEntity(data, DEFAULT_HOME_KEY, outputBuffer.toByteArray());
236                 } else {
237                     data.writeEntityHeader(DEFAULT_HOME_KEY, -1);
238                 }
239             }
240 
241             /*
242              * Global metadata:
243              *
244              * int SDKversion -- the SDK version of the OS itself on the device
245              *                   that produced this backup set.  Used to reject
246              *                   backups from later OSes onto earlier ones.
247              * String incremental -- the incremental release name of the OS stored in
248              *                       the backup set.
249              */
250             outputBuffer.reset();
251             if (!mExisting.contains(GLOBAL_METADATA_KEY)) {
252                 if (DEBUG) Slog.v(TAG, "Storing global metadata key");
253                 outputBufferStream.writeInt(Build.VERSION.SDK_INT);
254                 outputBufferStream.writeUTF(Build.VERSION.INCREMENTAL);
255                 writeEntity(data, GLOBAL_METADATA_KEY, outputBuffer.toByteArray());
256             } else {
257                 if (DEBUG) Slog.v(TAG, "Global metadata key already stored");
258                 // don't consider it to have been skipped/deleted
259                 mExisting.remove(GLOBAL_METADATA_KEY);
260             }
261 
262             // For each app we have on device, see if we've backed it up yet.  If not,
263             // write its signature block to the output, keyed on the package name.
264             for (PackageInfo pkg : mAllPackages) {
265                 String packName = pkg.packageName;
266                 if (packName.equals(GLOBAL_METADATA_KEY)) {
267                     // We've already handled the metadata key; skip it here
268                     continue;
269                 } else {
270                     PackageInfo info = null;
271                     try {
272                         info = mPackageManager.getPackageInfo(packName,
273                                 PackageManager.GET_SIGNATURES);
274                     } catch (NameNotFoundException e) {
275                         // Weird; we just found it, and now are told it doesn't exist.
276                         // Treat it as having been removed from the device.
277                         mExisting.add(packName);
278                         continue;
279                     }
280 
281                     if (mExisting.contains(packName)) {
282                         // We have backed up this app before.  Check whether the version
283                         // of the backup matches the version of the current app; if they
284                         // don't match, the app has been updated and we need to store its
285                         // metadata again.  In either case, take it out of mExisting so that
286                         // we don't consider it deleted later.
287                         mExisting.remove(packName);
288                         if (info.versionCode == mStateVersions.get(packName).versionCode) {
289                             continue;
290                         }
291                     }
292 
293                     if (info.signatures == null || info.signatures.length == 0)
294                     {
295                         Slog.w(TAG, "Not backing up package " + packName
296                                 + " since it appears to have no signatures.");
297                         continue;
298                     }
299 
300                     // We need to store this app's metadata
301                     /*
302                      * Metadata for each package:
303                      *
304                      * int version       -- [4] the package's versionCode
305                      * byte[] signatures -- [len] flattened signature hash array of the package
306                      */
307 
308                     // marshal the version code in a canonical form
309                     outputBuffer.reset();
310                     outputBufferStream.writeInt(info.versionCode);
311                     writeSignatureHashArray(outputBufferStream,
312                             hashSignatureArray(info.signatures));
313 
314                     if (DEBUG) {
315                         Slog.v(TAG, "+ writing metadata for " + packName
316                                 + " version=" + info.versionCode
317                                 + " entityLen=" + outputBuffer.size());
318                     }
319 
320                     // Now we can write the backup entity for this package
321                     writeEntity(data, packName, outputBuffer.toByteArray());
322                 }
323             }
324 
325             // At this point, the only entries in 'existing' are apps that were
326             // mentioned in the saved state file, but appear to no longer be present
327             // on the device.  We want to preserve the entry for them, however,
328             // because we want the right thing to happen if the user goes through
329             // a backup / uninstall / backup / reinstall sequence.
330             if (DEBUG) {
331                 if (mExisting.size() > 0) {
332                     StringBuilder sb = new StringBuilder(64);
333                     sb.append("Preserving metadata for deleted packages:");
334                     for (String app : mExisting) {
335                         sb.append(' ');
336                         sb.append(app);
337                     }
338                     Slog.v(TAG, sb.toString());
339                 }
340             }
341         } catch (IOException e) {
342             // Real error writing data
343             Slog.e(TAG, "Unable to write package backup data file!");
344             return;
345         }
346 
347         // Finally, write the new state blob -- just the list of all apps we handled
348         writeStateFile(mAllPackages, home, homeVersion, homeSigHashes, newState);
349     }
350 
writeEntity(BackupDataOutput data, String key, byte[] bytes)351     private static void writeEntity(BackupDataOutput data, String key, byte[] bytes)
352             throws IOException {
353         data.writeEntityHeader(key, bytes.length);
354         data.writeEntityData(bytes, bytes.length);
355     }
356 
357     // "Restore" here is a misnomer.  What we're really doing is reading back the
358     // set of app signatures associated with each backed-up app in this restore
359     // image.  We'll use those later to determine what we can legitimately restore.
onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)360     public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
361             throws IOException {
362         List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
363         HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
364         if (DEBUG) Slog.v(TAG, "onRestore()");
365         int storedSystemVersion = -1;
366 
367         while (data.readNextHeader()) {
368             String key = data.getKey();
369             int dataSize = data.getDataSize();
370 
371             if (DEBUG) Slog.v(TAG, "   got key=" + key + " dataSize=" + dataSize);
372 
373             // generic setup to parse any entity data
374             byte[] inputBytes = new byte[dataSize];
375             data.readEntityData(inputBytes, 0, dataSize);
376             ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
377             DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
378 
379             if (key.equals(GLOBAL_METADATA_KEY)) {
380                 int storedSdkVersion = inputBufferStream.readInt();
381                 if (DEBUG) Slog.v(TAG, "   storedSystemVersion = " + storedSystemVersion);
382                 if (storedSystemVersion > Build.VERSION.SDK_INT) {
383                     // returning before setting the sig map means we rejected the restore set
384                     Slog.w(TAG, "Restore set was from a later version of Android; not restoring");
385                     return;
386                 }
387                 mStoredSdkVersion = storedSdkVersion;
388                 mStoredIncrementalVersion = inputBufferStream.readUTF();
389                 mHasMetadata = true;
390                 if (DEBUG) {
391                     Slog.i(TAG, "Restore set version " + storedSystemVersion
392                             + " is compatible with OS version " + Build.VERSION.SDK_INT
393                             + " (" + mStoredIncrementalVersion + " vs "
394                             + Build.VERSION.INCREMENTAL + ")");
395                 }
396             } else if (key.equals(DEFAULT_HOME_KEY)) {
397                 String cn = inputBufferStream.readUTF();
398                 mRestoredHome = ComponentName.unflattenFromString(cn);
399                 mRestoredHomeVersion = inputBufferStream.readLong();
400                 mRestoredHomeInstaller = inputBufferStream.readUTF();
401                 mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
402                 if (DEBUG) {
403                     Slog.i(TAG, "   read preferred home app " + mRestoredHome
404                             + " version=" + mRestoredHomeVersion
405                             + " installer=" + mRestoredHomeInstaller
406                             + " sig=" + mRestoredHomeSigHashes);
407                 }
408             } else {
409                 // it's a file metadata record
410                 int versionCode = inputBufferStream.readInt();
411                 ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
412                 if (DEBUG) {
413                     Slog.i(TAG, "   read metadata for " + key
414                             + " dataSize=" + dataSize
415                             + " versionCode=" + versionCode + " sigs=" + sigs);
416                 }
417 
418                 if (sigs == null || sigs.size() == 0) {
419                     Slog.w(TAG, "Not restoring package " + key
420                             + " since it appears to have no signatures.");
421                     continue;
422                 }
423 
424                 ApplicationInfo app = new ApplicationInfo();
425                 app.packageName = key;
426                 restoredApps.add(app);
427                 sigMap.put(key, new Metadata(versionCode, sigs));
428             }
429         }
430 
431         // On successful completion, cache the signature map for the Backup Manager to use
432         mRestoredSignatures = sigMap;
433     }
434 
hashSignatureArray(Signature[] sigs)435     private static ArrayList<byte[]> hashSignatureArray(Signature[] sigs) {
436         if (sigs == null) {
437             return null;
438         }
439 
440         ArrayList<byte[]> hashes = new ArrayList<byte[]>(sigs.length);
441         for (Signature s : sigs) {
442             hashes.add(BackupManagerService.hashSignature(s));
443         }
444         return hashes;
445     }
446 
writeSignatureHashArray(DataOutputStream out, ArrayList<byte[]> hashes)447     private static void writeSignatureHashArray(DataOutputStream out, ArrayList<byte[]> hashes)
448             throws IOException {
449         // the number of entries in the array
450         out.writeInt(hashes.size());
451 
452         // the hash arrays themselves as length + contents
453         for (byte[] buffer : hashes) {
454             out.writeInt(buffer.length);
455             out.write(buffer);
456         }
457     }
458 
readSignatureHashArray(DataInputStream in)459     private static ArrayList<byte[]> readSignatureHashArray(DataInputStream in) {
460         try {
461             int num;
462             try {
463                 num = in.readInt();
464             } catch (EOFException e) {
465                 // clean termination
466                 Slog.w(TAG, "Read empty signature block");
467                 return null;
468             }
469 
470             if (DEBUG) Slog.v(TAG, " ... unflatten read " + num);
471 
472             // Sensical?
473             if (num > 20) {
474                 Slog.e(TAG, "Suspiciously large sig count in restore data; aborting");
475                 throw new IllegalStateException("Bad restore state");
476             }
477 
478             // This could be a "legacy" block of actual signatures rather than their hashes.
479             // If this is the case, convert them now.  We judge based on the payload size:
480             // if the blocks are all 256 bits (32 bytes) then we take them to be SHA-256 hashes;
481             // otherwise we take them to be Signatures.
482             boolean nonHashFound = false;
483             ArrayList<byte[]> sigs = new ArrayList<byte[]>(num);
484             for (int i = 0; i < num; i++) {
485                 int len = in.readInt();
486                 byte[] readHash = new byte[len];
487                 in.read(readHash);
488                 sigs.add(readHash);
489                 if (len != 32) {
490                     nonHashFound = true;
491                 }
492             }
493 
494             if (nonHashFound) {
495                 ArrayList<byte[]> hashes =
496                         new ArrayList<byte[]>(sigs.size());
497                 for (int i = 0; i < sigs.size(); i++) {
498                     Signature s = new Signature(sigs.get(i));
499                     hashes.add(BackupManagerService.hashSignature(s));
500                 }
501                 sigs = hashes;
502             }
503 
504             return sigs;
505         } catch (IOException e) {
506             Slog.e(TAG, "Unable to read signatures");
507             return null;
508         }
509     }
510 
511     // Util: parse out an existing state file into a usable structure
parseStateFile(ParcelFileDescriptor stateFile)512     private void parseStateFile(ParcelFileDescriptor stateFile) {
513         mExisting.clear();
514         mStateVersions.clear();
515         mStoredSdkVersion = 0;
516         mStoredIncrementalVersion = null;
517         mStoredHomeComponent = null;
518         mStoredHomeVersion = 0;
519         mStoredHomeSigHashes = null;
520 
521         // The state file is just the list of app names we have stored signatures for
522         // with the exception of the metadata block, to which is also appended the
523         // version numbers corresponding with the last time we wrote this PM block.
524         // If they mismatch the current system, we'll re-store the metadata key.
525         FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
526         BufferedInputStream inbuffer = new BufferedInputStream(instream);
527         DataInputStream in = new DataInputStream(inbuffer);
528 
529         try {
530             boolean ignoreExisting = false;
531             String pkg = in.readUTF();
532 
533             // Validate the state file version is sensical to us
534             if (pkg.equals(STATE_FILE_HEADER)) {
535                 int stateVersion = in.readInt();
536                 if (stateVersion > STATE_FILE_VERSION) {
537                     Slog.w(TAG, "Unsupported state file version " + stateVersion
538                             + ", redoing from start");
539                     return;
540                 }
541                 pkg = in.readUTF();
542             } else {
543                 // This is an older version of the state file in which the lead element
544                 // is not a STATE_FILE_VERSION string.  If that's the case, we want to
545                 // make sure to write our full backup dataset when given an opportunity.
546                 // We trigger that by simply not marking the restored package metadata
547                 // as known-to-exist-in-archive.
548                 Slog.i(TAG, "Older version of saved state - rewriting");
549                 ignoreExisting = true;
550             }
551 
552             // First comes the preferred home app data, if any, headed by the DEFAULT_HOME_KEY tag
553             if (pkg.equals(DEFAULT_HOME_KEY)) {
554                 // flattened component name, version, signature of the home app
555                 mStoredHomeComponent = ComponentName.unflattenFromString(in.readUTF());
556                 mStoredHomeVersion = in.readLong();
557                 mStoredHomeSigHashes = readSignatureHashArray(in);
558 
559                 pkg = in.readUTF(); // set up for the next block of state
560             } else {
561                 // else no preferred home app on the ancestral device - fall through to the rest
562             }
563 
564             // After (possible) home app data comes the global metadata block
565             if (pkg.equals(GLOBAL_METADATA_KEY)) {
566                 mStoredSdkVersion = in.readInt();
567                 mStoredIncrementalVersion = in.readUTF();
568                 if (!ignoreExisting) {
569                     mExisting.add(GLOBAL_METADATA_KEY);
570                 }
571             } else {
572                 Slog.e(TAG, "No global metadata in state file!");
573                 return;
574             }
575 
576             // The global metadata was last; now read all the apps
577             while (true) {
578                 pkg = in.readUTF();
579                 int versionCode = in.readInt();
580 
581                 if (!ignoreExisting) {
582                     mExisting.add(pkg);
583                 }
584                 mStateVersions.put(pkg, new Metadata(versionCode, null));
585             }
586         } catch (EOFException eof) {
587             // safe; we're done
588         } catch (IOException e) {
589             // whoops, bad state file.  abort.
590             Slog.e(TAG, "Unable to read Package Manager state file: " + e);
591         }
592     }
593 
getPreferredHomeComponent()594     private ComponentName getPreferredHomeComponent() {
595         return mPackageManager.getHomeActivities(new ArrayList<ResolveInfo>());
596     }
597 
598     // Util: write out our new backup state file
writeStateFile(List<PackageInfo> pkgs, ComponentName preferredHome, long homeVersion, ArrayList<byte[]> homeSigHashes, ParcelFileDescriptor stateFile)599     private void writeStateFile(List<PackageInfo> pkgs, ComponentName preferredHome,
600             long homeVersion, ArrayList<byte[]> homeSigHashes, ParcelFileDescriptor stateFile) {
601         FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
602         BufferedOutputStream outbuf = new BufferedOutputStream(outstream);
603         DataOutputStream out = new DataOutputStream(outbuf);
604 
605         // by the time we get here we know we've done all our backing up
606         try {
607             // state file version header
608             out.writeUTF(STATE_FILE_HEADER);
609             out.writeInt(STATE_FILE_VERSION);
610 
611             // If we remembered a preferred home app, record that
612             if (preferredHome != null) {
613                 out.writeUTF(DEFAULT_HOME_KEY);
614                 out.writeUTF(preferredHome.flattenToString());
615                 out.writeLong(homeVersion);
616                 writeSignatureHashArray(out, homeSigHashes);
617             }
618 
619             // Conclude with the metadata block
620             out.writeUTF(GLOBAL_METADATA_KEY);
621             out.writeInt(Build.VERSION.SDK_INT);
622             out.writeUTF(Build.VERSION.INCREMENTAL);
623 
624             // now write all the app names + versions
625             for (PackageInfo pkg : pkgs) {
626                 out.writeUTF(pkg.packageName);
627                 out.writeInt(pkg.versionCode);
628             }
629 
630             out.flush();
631         } catch (IOException e) {
632             Slog.e(TAG, "Unable to write package manager state file!");
633         }
634     }
635 }
636