1 /* 2 * Copyright (C) 2024 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 static com.google.common.truth.Truth.assertThat; 20 21 import static org.mockito.ArgumentMatchers.anyInt; 22 import static org.mockito.ArgumentMatchers.eq; 23 import static org.mockito.Mockito.when; 24 25 import android.app.backup.BackupDataInput; 26 import android.app.backup.BackupDataOutput; 27 import android.content.pm.PackageInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.Signature; 30 import android.content.pm.SigningDetails; 31 import android.content.pm.SigningInfo; 32 import android.os.Build; 33 import android.os.ParcelFileDescriptor; 34 import android.platform.test.annotations.Presubmit; 35 36 import androidx.test.runner.AndroidJUnit4; 37 38 import com.google.common.collect.ImmutableList; 39 import com.google.common.collect.ImmutableMap; 40 41 import org.junit.Before; 42 import org.junit.Rule; 43 import org.junit.Test; 44 import org.junit.rules.TemporaryFolder; 45 import org.junit.runner.RunWith; 46 import org.mockito.Mock; 47 import org.mockito.MockitoAnnotations; 48 49 import java.io.BufferedOutputStream; 50 import java.io.DataOutputStream; 51 import java.io.File; 52 import java.io.FileOutputStream; 53 import java.nio.ByteBuffer; 54 import java.util.Optional; 55 56 @Presubmit 57 @RunWith(AndroidJUnit4.class) 58 public class PackageManagerBackupAgentTest { 59 60 private static final String EXISTING_PACKAGE_NAME = "com.android.wallpaperbackup"; 61 private static final int EXISTING_PACKAGE_VERSION = 1; 62 63 private static final int USER_ID = 0; 64 65 @Rule public TemporaryFolder folder = new TemporaryFolder(); 66 67 @Mock private PackageManager mPackageManager; 68 69 private PackageManagerBackupAgent mPackageManagerBackupAgent; 70 private ImmutableList<PackageInfo> mPackages; 71 private File mBackupData, mOldState, mNewState; 72 73 @Before setUp()74 public void setUp() throws Exception { 75 MockitoAnnotations.initMocks(this); 76 77 PackageInfo existingPackageInfo = 78 createPackage(EXISTING_PACKAGE_NAME, EXISTING_PACKAGE_VERSION); 79 Signature sig = new Signature(new byte[256]); 80 existingPackageInfo.signingInfo = 81 new SigningInfo(new SigningDetails(new Signature[] {sig}, 1, null, null)); 82 when(mPackageManager.getPackageInfoAsUser(eq(EXISTING_PACKAGE_NAME), anyInt(), anyInt())) 83 .thenReturn(existingPackageInfo); 84 85 mPackages = ImmutableList.of(existingPackageInfo); 86 mPackageManagerBackupAgent = 87 new PackageManagerBackupAgent(mPackageManager, mPackages, USER_ID); 88 89 mBackupData = folder.newFile("backup_data"); 90 mOldState = folder.newFile("old_state"); 91 mNewState = folder.newFile("new_state"); 92 } 93 94 @Test onBackup_noState_backsUpEverything()95 public void onBackup_noState_backsUpEverything() throws Exception { 96 // no setup needed 97 98 runBackupAgentOnBackup(); 99 100 // key/values should be written to backup data 101 ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData); 102 assertThat(keyValues.keySet()) 103 .containsExactly( 104 PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY, 105 PackageManagerBackupAgent.GLOBAL_METADATA_KEY, 106 EXISTING_PACKAGE_NAME) 107 .inOrder(); 108 // new state must not be empty 109 assertThat(mNewState.length()).isGreaterThan(0); 110 } 111 112 @Test onBackup_recentState_backsUpNothing()113 public void onBackup_recentState_backsUpNothing() throws Exception { 114 try (ParcelFileDescriptor oldStateDescriptor = openForWriting(mOldState)) { 115 PackageManagerBackupAgent.writeStateFile(mPackages, oldStateDescriptor); 116 } 117 118 runBackupAgentOnBackup(); 119 120 ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData); 121 assertThat(keyValues).isEmpty(); 122 assertThat(mNewState.length()).isGreaterThan(0); 123 assertThat(mNewState.length()).isEqualTo(mOldState.length()); 124 } 125 126 @Test onBackup_oldState_backsUpChanges()127 public void onBackup_oldState_backsUpChanges() throws Exception { 128 String uninstalledPackageName = "does.not.exist"; 129 try (ParcelFileDescriptor oldStateDescriptor = openForWriting(mOldState)) { 130 PackageManagerBackupAgent.writeStateFile( 131 ImmutableList.of(createPackage(uninstalledPackageName, 1)), oldStateDescriptor); 132 } 133 134 runBackupAgentOnBackup(); 135 136 // Note that uninstalledPackageName should not exist, i.e. it did not get deleted. 137 ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData); 138 assertThat(keyValues.keySet()).containsExactly(EXISTING_PACKAGE_NAME); 139 assertThat(mNewState.length()).isGreaterThan(0); 140 } 141 142 @Test onBackup_legacyState_backsUpEverything()143 public void onBackup_legacyState_backsUpEverything() throws Exception { 144 String uninstalledPackageName = "does.not.exist"; 145 writeLegacyStateFile( 146 mOldState, 147 ImmutableList.of(createPackage(uninstalledPackageName, 1), mPackages.getFirst()), 148 /* writeStateFileVersion= */ false, 149 /* writeAncestralRecordVersion= */ false); 150 151 runBackupAgentOnBackup(); 152 153 ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData); 154 assertThat(keyValues.keySet()) 155 .containsExactly( 156 PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY, 157 PackageManagerBackupAgent.GLOBAL_METADATA_KEY, 158 EXISTING_PACKAGE_NAME); 159 assertThat(mNewState.length()).isGreaterThan(0); 160 } 161 162 @Test onBackup_noAncestralRecordInfo_deletesUninstalledPackagesFromBackup()163 public void onBackup_noAncestralRecordInfo_deletesUninstalledPackagesFromBackup() 164 throws Exception { 165 PackageInfo pkgNotInstalled = createPackage("does.not.exist", 2); 166 PackageInfo pkgInstalled = 167 mPackageManager.getPackageInfoAsUser( 168 EXISTING_PACKAGE_NAME, PackageManager.GET_SIGNING_CERTIFICATES, USER_ID); 169 writeLegacyStateFile( 170 mOldState, 171 ImmutableList.of(pkgInstalled, pkgNotInstalled), 172 /* writeStateFileVersion= */ true, 173 /* writeAncestralRecordVersion= */ false); 174 175 runBackupAgentOnBackup(); 176 177 ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData); 178 assertThat(keyValues.keySet()) 179 .containsExactly( 180 PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY, 181 pkgInstalled.packageName, 182 pkgNotInstalled.packageName); 183 assertThat(keyValues).containsEntry(pkgNotInstalled.packageName, Optional.empty()); 184 } 185 186 @Test onRestore_recentBackup_restoresBackup()187 public void onRestore_recentBackup_restoresBackup() throws Exception { 188 runBackupAgentOnBackup(); 189 190 runBackupAgentOnRestore(); 191 192 assertThat(mPackageManagerBackupAgent.getRestoredPackages()) 193 .containsExactly(EXISTING_PACKAGE_NAME); 194 // onRestore does not write to newState 195 assertThat(mNewState.length()).isEqualTo(0); 196 } 197 198 @Test onRestore_legacyBackup_restoresBackup()199 public void onRestore_legacyBackup_restoresBackup() throws Exception { 200 // A legacy backup is one without an ancestral record version. Ancestral record versions 201 // are always written however, so we'll need to delete it from the backup data before 202 // restoring. 203 runBackupAgentOnBackup(); 204 deleteKeyFromBackupData(mBackupData, PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY); 205 206 runBackupAgentOnRestore(); 207 208 assertThat(mPackageManagerBackupAgent.getRestoredPackages()) 209 .containsExactly(EXISTING_PACKAGE_NAME); 210 // onRestore does not write to newState 211 assertThat(mNewState.length()).isEqualTo(0); 212 } 213 214 @Test onRestore_legacyBackupWithMissingSignature_restoresBackup()215 public void onRestore_legacyBackupWithMissingSignature_restoresBackup() throws Exception { 216 PackageInfo pkgWithoutSigs = createPackage("pkg.no.sigs", 1); 217 pkgWithoutSigs.signingInfo = 218 new SigningInfo(new SigningDetails(new Signature[0], 1, null, null)); 219 when(mPackageManager.getPackageInfoAsUser( 220 eq(pkgWithoutSigs.packageName), anyInt(), anyInt())) 221 .thenReturn(pkgWithoutSigs); 222 ImmutableList<PackageInfo> packages = 223 ImmutableList.<PackageInfo>builder().addAll(mPackages).add(pkgWithoutSigs).build(); 224 mPackageManagerBackupAgent = 225 new PackageManagerBackupAgent(mPackageManager, packages, USER_ID); 226 // A legacy backup is one without an ancestral record version. Ancestral record versions 227 // are always written however, so we'll need to delete it from the backup data before 228 // restoring. 229 runBackupAgentOnBackup(); 230 deleteKeyFromBackupData(mBackupData, PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY); 231 232 runBackupAgentOnRestore(); // should not fail or timeout 233 234 assertThat(mPackageManagerBackupAgent.getRestoredPackages()) 235 .containsExactly(EXISTING_PACKAGE_NAME); 236 } 237 runBackupAgentOnBackup()238 private void runBackupAgentOnBackup() throws Exception { 239 try (ParcelFileDescriptor oldStateDescriptor = openForReading(mOldState); 240 ParcelFileDescriptor backupDataDescriptor = openForWriting(mBackupData); 241 ParcelFileDescriptor newStateDescriptor = openForWriting(mNewState)) { 242 mPackageManagerBackupAgent.onBackup( 243 oldStateDescriptor, 244 new BackupDataOutput(backupDataDescriptor.getFileDescriptor()), 245 newStateDescriptor); 246 } 247 } 248 runBackupAgentOnRestore()249 private void runBackupAgentOnRestore() throws Exception { 250 try (ParcelFileDescriptor backupDataDescriptor = openForReading(mBackupData); 251 ParcelFileDescriptor newStateDescriptor = openForWriting(mNewState)) { 252 mPackageManagerBackupAgent.onRestore( 253 new BackupDataInput(backupDataDescriptor.getFileDescriptor()), 254 /* appVersionCode= */ 0, 255 newStateDescriptor); 256 } 257 } 258 deleteKeyFromBackupData(File backupData, String key)259 private void deleteKeyFromBackupData(File backupData, String key) throws Exception { 260 File temporaryBackupData = folder.newFile("backup_data.tmp"); 261 try (ParcelFileDescriptor inputDescriptor = openForReading(backupData); 262 ParcelFileDescriptor outputDescriptor = openForWriting(temporaryBackupData); ) { 263 BackupDataInput input = new BackupDataInput(inputDescriptor.getFileDescriptor()); 264 BackupDataOutput output = new BackupDataOutput(outputDescriptor.getFileDescriptor()); 265 while (input.readNextHeader()) { 266 if (input.getKey().equals(key)) { 267 if (input.getDataSize() > 0) { 268 input.skipEntityData(); 269 } 270 continue; 271 } 272 output.writeEntityHeader(input.getKey(), input.getDataSize()); 273 if (input.getDataSize() < 0) { 274 input.skipEntityData(); 275 } else { 276 byte[] buf = new byte[input.getDataSize()]; 277 input.readEntityData(buf, 0, buf.length); 278 output.writeEntityData(buf, buf.length); 279 } 280 } 281 } 282 assertThat(temporaryBackupData.renameTo(backupData)).isTrue(); 283 } 284 createPackage(String name, int versionCode)285 private static PackageInfo createPackage(String name, int versionCode) { 286 PackageInfo packageInfo = new PackageInfo(); 287 packageInfo.packageName = name; 288 packageInfo.versionCodeMajor = versionCode; 289 return packageInfo; 290 } 291 292 /** This creates a legacy state file in which {@code STATE_FILE_HEADER} was not yet present. */ writeLegacyStateFile( File stateFile, ImmutableList<PackageInfo> packages, boolean writeStateFileVersion, boolean writeAncestralRecordVersion)293 private static void writeLegacyStateFile( 294 File stateFile, 295 ImmutableList<PackageInfo> packages, 296 boolean writeStateFileVersion, 297 boolean writeAncestralRecordVersion) 298 throws Exception { 299 try (ParcelFileDescriptor stateFileDescriptor = openForWriting(stateFile); 300 DataOutputStream out = 301 new DataOutputStream( 302 new BufferedOutputStream( 303 new FileOutputStream( 304 stateFileDescriptor.getFileDescriptor())))) { 305 306 if (writeStateFileVersion) { 307 // state file version header 308 out.writeUTF(PackageManagerBackupAgent.STATE_FILE_HEADER); 309 out.writeInt(PackageManagerBackupAgent.STATE_FILE_VERSION); 310 } 311 312 if (writeAncestralRecordVersion) { 313 // Record the ancestral record 314 out.writeUTF(PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY); 315 out.writeInt(PackageManagerBackupAgent.ANCESTRAL_RECORD_VERSION); 316 } 317 318 out.writeUTF(PackageManagerBackupAgent.GLOBAL_METADATA_KEY); 319 out.writeInt(Build.VERSION.SDK_INT); 320 out.writeUTF(Build.VERSION.INCREMENTAL); 321 322 // now write all the app names + versions 323 for (PackageInfo pkg : packages) { 324 out.writeUTF(pkg.packageName); 325 out.writeInt(pkg.versionCode); 326 } 327 out.flush(); 328 } 329 } 330 331 /** 332 * Reads the given backup data file and returns a map of key-value pairs. The value is a {@link 333 * ByteBuffer} wrapped in an {@link Optional}, where the empty {@link Optional} represents a key 334 * deletion. 335 */ getKeyValues(File backupData)336 private static ImmutableMap<String, Optional<ByteBuffer>> getKeyValues(File backupData) 337 throws Exception { 338 ImmutableMap.Builder<String, Optional<ByteBuffer>> builder = ImmutableMap.builder(); 339 try (ParcelFileDescriptor backupDataDescriptor = openForReading(backupData)) { 340 BackupDataInput backupDataInput = 341 new BackupDataInput(backupDataDescriptor.getFileDescriptor()); 342 while (backupDataInput.readNextHeader()) { 343 ByteBuffer value = null; 344 if (backupDataInput.getDataSize() >= 0) { 345 byte[] val = new byte[backupDataInput.getDataSize()]; 346 backupDataInput.readEntityData(val, 0, val.length); 347 value = ByteBuffer.wrap(val); 348 } 349 builder.put(backupDataInput.getKey(), Optional.ofNullable(value)); 350 } 351 } 352 return builder.build(); 353 } 354 openForWriting(File file)355 private static ParcelFileDescriptor openForWriting(File file) throws Exception { 356 return ParcelFileDescriptor.open( 357 file, 358 ParcelFileDescriptor.MODE_CREATE 359 | ParcelFileDescriptor.MODE_TRUNCATE 360 | ParcelFileDescriptor.MODE_WRITE_ONLY); 361 } 362 openForReading(File file)363 private static ParcelFileDescriptor openForReading(File file) throws Exception { 364 return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); 365 } 366 } 367