Home | History | Annotate | Download | only in backup
      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 
    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.
    111     PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) {
    112         init(packageMgr, packages);
    113     }
    114 
    115     PackageManagerBackupAgent(PackageManager packageMgr) {
    116         init(packageMgr, null);
    117 
    118         evaluateStorablePackages();
    119     }
    120 
    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.
    133     public void evaluateStorablePackages() {
    134         mAllPackages = getStorableApplications(mPackageManager);
    135     }
    136 
    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 
    150     public boolean hasMetadata() {
    151         return mHasMetadata;
    152     }
    153 
    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 
    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.
    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.  Write a deletion entity for them.
    328             for (String app : mExisting) {
    329                 if (DEBUG) Slog.v(TAG, "- removing metadata for deleted pkg " + app);
    330                 try {
    331                     data.writeEntityHeader(app, -1);
    332                 } catch (IOException e) {
    333                     Slog.e(TAG, "Unable to write package deletions!");
    334                     return;
    335                 }
    336             }
    337         } catch (IOException e) {
    338             // Real error writing data
    339             Slog.e(TAG, "Unable to write package backup data file!");
    340             return;
    341         }
    342 
    343         // Finally, write the new state blob -- just the list of all apps we handled
    344         writeStateFile(mAllPackages, home, homeVersion, homeSigHashes, newState);
    345     }
    346 
    347     private static void writeEntity(BackupDataOutput data, String key, byte[] bytes)
    348             throws IOException {
    349         data.writeEntityHeader(key, bytes.length);
    350         data.writeEntityData(bytes, bytes.length);
    351     }
    352 
    353     // "Restore" here is a misnomer.  What we're really doing is reading back the
    354     // set of app signatures associated with each backed-up app in this restore
    355     // image.  We'll use those later to determine what we can legitimately restore.
    356     public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
    357             throws IOException {
    358         List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
    359         HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
    360         if (DEBUG) Slog.v(TAG, "onRestore()");
    361         int storedSystemVersion = -1;
    362 
    363         while (data.readNextHeader()) {
    364             String key = data.getKey();
    365             int dataSize = data.getDataSize();
    366 
    367             if (DEBUG) Slog.v(TAG, "   got key=" + key + " dataSize=" + dataSize);
    368 
    369             // generic setup to parse any entity data
    370             byte[] inputBytes = new byte[dataSize];
    371             data.readEntityData(inputBytes, 0, dataSize);
    372             ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
    373             DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
    374 
    375             if (key.equals(GLOBAL_METADATA_KEY)) {
    376                 int storedSdkVersion = inputBufferStream.readInt();
    377                 if (DEBUG) Slog.v(TAG, "   storedSystemVersion = " + storedSystemVersion);
    378                 if (storedSystemVersion > Build.VERSION.SDK_INT) {
    379                     // returning before setting the sig map means we rejected the restore set
    380                     Slog.w(TAG, "Restore set was from a later version of Android; not restoring");
    381                     return;
    382                 }
    383                 mStoredSdkVersion = storedSdkVersion;
    384                 mStoredIncrementalVersion = inputBufferStream.readUTF();
    385                 mHasMetadata = true;
    386                 if (DEBUG) {
    387                     Slog.i(TAG, "Restore set version " + storedSystemVersion
    388                             + " is compatible with OS version " + Build.VERSION.SDK_INT
    389                             + " (" + mStoredIncrementalVersion + " vs "
    390                             + Build.VERSION.INCREMENTAL + ")");
    391                 }
    392             } else if (key.equals(DEFAULT_HOME_KEY)) {
    393                 String cn = inputBufferStream.readUTF();
    394                 mRestoredHome = ComponentName.unflattenFromString(cn);
    395                 mRestoredHomeVersion = inputBufferStream.readLong();
    396                 mRestoredHomeInstaller = inputBufferStream.readUTF();
    397                 mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
    398                 if (DEBUG) {
    399                     Slog.i(TAG, "   read preferred home app " + mRestoredHome
    400                             + " version=" + mRestoredHomeVersion
    401                             + " installer=" + mRestoredHomeInstaller
    402                             + " sig=" + mRestoredHomeSigHashes);
    403                 }
    404             } else {
    405                 // it's a file metadata record
    406                 int versionCode = inputBufferStream.readInt();
    407                 ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
    408                 if (DEBUG) {
    409                     Slog.i(TAG, "   read metadata for " + key
    410                             + " dataSize=" + dataSize
    411                             + " versionCode=" + versionCode + " sigs=" + sigs);
    412                 }
    413 
    414                 if (sigs == null || sigs.size() == 0) {
    415                     Slog.w(TAG, "Not restoring package " + key
    416                             + " since it appears to have no signatures.");
    417                     continue;
    418                 }
    419 
    420                 ApplicationInfo app = new ApplicationInfo();
    421                 app.packageName = key;
    422                 restoredApps.add(app);
    423                 sigMap.put(key, new Metadata(versionCode, sigs));
    424             }
    425         }
    426 
    427         // On successful completion, cache the signature map for the Backup Manager to use
    428         mRestoredSignatures = sigMap;
    429     }
    430 
    431     private static ArrayList<byte[]> hashSignatureArray(Signature[] sigs) {
    432         if (sigs == null) {
    433             return null;
    434         }
    435 
    436         ArrayList<byte[]> hashes = new ArrayList<byte[]>(sigs.length);
    437         for (Signature s : sigs) {
    438             hashes.add(BackupManagerService.hashSignature(s));
    439         }
    440         return hashes;
    441     }
    442 
    443     private static void writeSignatureHashArray(DataOutputStream out, ArrayList<byte[]> hashes)
    444             throws IOException {
    445         // the number of entries in the array
    446         out.writeInt(hashes.size());
    447 
    448         // the hash arrays themselves as length + contents
    449         for (byte[] buffer : hashes) {
    450             out.writeInt(buffer.length);
    451             out.write(buffer);
    452         }
    453     }
    454 
    455     private static ArrayList<byte[]> readSignatureHashArray(DataInputStream in) {
    456         try {
    457             int num;
    458             try {
    459                 num = in.readInt();
    460             } catch (EOFException e) {
    461                 // clean termination
    462                 Slog.w(TAG, "Read empty signature block");
    463                 return null;
    464             }
    465 
    466             if (DEBUG) Slog.v(TAG, " ... unflatten read " + num);
    467 
    468             // Sensical?
    469             if (num > 20) {
    470                 Slog.e(TAG, "Suspiciously large sig count in restore data; aborting");
    471                 throw new IllegalStateException("Bad restore state");
    472             }
    473 
    474             // This could be a "legacy" block of actual signatures rather than their hashes.
    475             // If this is the case, convert them now.  We judge based on the payload size:
    476             // if the blocks are all 256 bits (32 bytes) then we take them to be SHA-256 hashes;
    477             // otherwise we take them to be Signatures.
    478             boolean nonHashFound = false;
    479             ArrayList<byte[]> sigs = new ArrayList<byte[]>(num);
    480             for (int i = 0; i < num; i++) {
    481                 int len = in.readInt();
    482                 byte[] readHash = new byte[len];
    483                 in.read(readHash);
    484                 sigs.add(readHash);
    485                 if (len != 32) {
    486                     nonHashFound = true;
    487                 }
    488             }
    489 
    490             if (nonHashFound) {
    491                 ArrayList<byte[]> hashes =
    492                         new ArrayList<byte[]>(sigs.size());
    493                 for (int i = 0; i < sigs.size(); i++) {
    494                     Signature s = new Signature(sigs.get(i));
    495                     hashes.add(BackupManagerService.hashSignature(s));
    496                 }
    497                 sigs = hashes;
    498             }
    499 
    500             return sigs;
    501         } catch (IOException e) {
    502             Slog.e(TAG, "Unable to read signatures");
    503             return null;
    504         }
    505     }
    506 
    507     // Util: parse out an existing state file into a usable structure
    508     private void parseStateFile(ParcelFileDescriptor stateFile) {
    509         mExisting.clear();
    510         mStateVersions.clear();
    511         mStoredSdkVersion = 0;
    512         mStoredIncrementalVersion = null;
    513         mStoredHomeComponent = null;
    514         mStoredHomeVersion = 0;
    515         mStoredHomeSigHashes = null;
    516 
    517         // The state file is just the list of app names we have stored signatures for
    518         // with the exception of the metadata block, to which is also appended the
    519         // version numbers corresponding with the last time we wrote this PM block.
    520         // If they mismatch the current system, we'll re-store the metadata key.
    521         FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
    522         BufferedInputStream inbuffer = new BufferedInputStream(instream);
    523         DataInputStream in = new DataInputStream(inbuffer);
    524 
    525         try {
    526             boolean ignoreExisting = false;
    527             String pkg = in.readUTF();
    528 
    529             // Validate the state file version is sensical to us
    530             if (pkg.equals(STATE_FILE_HEADER)) {
    531                 int stateVersion = in.readInt();
    532                 if (stateVersion > STATE_FILE_VERSION) {
    533                     Slog.w(TAG, "Unsupported state file version " + stateVersion
    534                             + ", redoing from start");
    535                     return;
    536                 }
    537                 pkg = in.readUTF();
    538             } else {
    539                 // This is an older version of the state file in which the lead element
    540                 // is not a STATE_FILE_VERSION string.  If that's the case, we want to
    541                 // make sure to write our full backup dataset when given an opportunity.
    542                 // We trigger that by simply not marking the restored package metadata
    543                 // as known-to-exist-in-archive.
    544                 Slog.i(TAG, "Older version of saved state - rewriting");
    545                 ignoreExisting = true;
    546             }
    547 
    548             // First comes the preferred home app data, if any, headed by the DEFAULT_HOME_KEY tag
    549             if (pkg.equals(DEFAULT_HOME_KEY)) {
    550                 // flattened component name, version, signature of the home app
    551                 mStoredHomeComponent = ComponentName.unflattenFromString(in.readUTF());
    552                 mStoredHomeVersion = in.readLong();
    553                 mStoredHomeSigHashes = readSignatureHashArray(in);
    554 
    555                 pkg = in.readUTF(); // set up for the next block of state
    556             } else {
    557                 // else no preferred home app on the ancestral device - fall through to the rest
    558             }
    559 
    560             // After (possible) home app data comes the global metadata block
    561             if (pkg.equals(GLOBAL_METADATA_KEY)) {
    562                 mStoredSdkVersion = in.readInt();
    563                 mStoredIncrementalVersion = in.readUTF();
    564                 if (!ignoreExisting) {
    565                     mExisting.add(GLOBAL_METADATA_KEY);
    566                 }
    567             } else {
    568                 Slog.e(TAG, "No global metadata in state file!");
    569                 return;
    570             }
    571 
    572             // The global metadata was last; now read all the apps
    573             while (true) {
    574                 pkg = in.readUTF();
    575                 int versionCode = in.readInt();
    576 
    577                 if (!ignoreExisting) {
    578                     mExisting.add(pkg);
    579                 }
    580                 mStateVersions.put(pkg, new Metadata(versionCode, null));
    581             }
    582         } catch (EOFException eof) {
    583             // safe; we're done
    584         } catch (IOException e) {
    585             // whoops, bad state file.  abort.
    586             Slog.e(TAG, "Unable to read Package Manager state file: " + e);
    587         }
    588     }
    589 
    590     private ComponentName getPreferredHomeComponent() {
    591         return mPackageManager.getHomeActivities(new ArrayList<ResolveInfo>());
    592     }
    593 
    594     // Util: write out our new backup state file
    595     private void writeStateFile(List<PackageInfo> pkgs, ComponentName preferredHome,
    596             long homeVersion, ArrayList<byte[]> homeSigHashes, ParcelFileDescriptor stateFile) {
    597         FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
    598         BufferedOutputStream outbuf = new BufferedOutputStream(outstream);
    599         DataOutputStream out = new DataOutputStream(outbuf);
    600 
    601         // by the time we get here we know we've done all our backing up
    602         try {
    603             // state file version header
    604             out.writeUTF(STATE_FILE_HEADER);
    605             out.writeInt(STATE_FILE_VERSION);
    606 
    607             // If we remembered a preferred home app, record that
    608             if (preferredHome != null) {
    609                 out.writeUTF(DEFAULT_HOME_KEY);
    610                 out.writeUTF(preferredHome.flattenToString());
    611                 out.writeLong(homeVersion);
    612                 writeSignatureHashArray(out, homeSigHashes);
    613             }
    614 
    615             // Conclude with the metadata block
    616             out.writeUTF(GLOBAL_METADATA_KEY);
    617             out.writeInt(Build.VERSION.SDK_INT);
    618             out.writeUTF(Build.VERSION.INCREMENTAL);
    619 
    620             // now write all the app names + versions
    621             for (PackageInfo pkg : pkgs) {
    622                 out.writeUTF(pkg.packageName);
    623                 out.writeInt(pkg.versionCode);
    624             }
    625 
    626             out.flush();
    627         } catch (IOException e) {
    628             Slog.e(TAG, "Unable to write package manager state file!");
    629         }
    630     }
    631 }
    632