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