• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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