1 /*
2  * Copyright (C) 2019 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.cts.rollback.host.app;
18 
19 import static com.android.cts.shim.lib.ShimPackage.PRIVILEGED_SHIM_PACKAGE_NAME;
20 import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
21 import static com.android.cts.shim.lib.ShimPackage.SHIM_PACKAGE_NAME;
22 import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import android.Manifest;
27 import android.content.Context;
28 import android.content.pm.PackageInstaller;
29 import android.content.rollback.RollbackInfo;
30 import android.content.rollback.RollbackManager;
31 import android.os.storage.StorageManager;
32 
33 import androidx.test.platform.app.InstrumentationRegistry;
34 
35 import com.android.cts.install.lib.Install;
36 import com.android.cts.install.lib.InstallUtils;
37 import com.android.cts.install.lib.TestApp;
38 import com.android.cts.rollback.lib.Rollback;
39 import com.android.cts.rollback.lib.RollbackUtils;
40 
41 import org.junit.After;
42 import org.junit.Before;
43 import org.junit.Test;
44 import org.junit.runner.RunWith;
45 import org.junit.runners.JUnit4;
46 
47 import java.io.BufferedReader;
48 import java.io.BufferedWriter;
49 import java.io.File;
50 import java.io.FileReader;
51 import java.io.FileWriter;
52 import java.io.IOException;
53 import java.nio.file.Files;
54 
55 /**
56  * On-device helper test methods used for host-driven rollback tests.
57  */
58 @RunWith(JUnit4.class)
59 public class HostTestHelper {
60     private static final String TAG = "RollbackTest";
61 
62     private static final TestApp Apex2SignedBobRotRollback = new TestApp(
63             "Apex2SignedBobRotRollback", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true,
64             "com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex");
65     private static final String APK_VERSION_FILENAME = "ctsrollback_apkversion";
66     private static final String APK_VERSION_SEPARATOR = ",";
67 
68     /**
69      * Adopts common permissions needed to test rollbacks.
70      */
71     @Before
setup()72     public void setup() throws InterruptedException, IOException {
73         InstallUtils.adoptShellPermissionIdentity(
74                     Manifest.permission.INSTALL_PACKAGES,
75                     Manifest.permission.DELETE_PACKAGES,
76                     Manifest.permission.TEST_MANAGE_ROLLBACKS);
77     }
78 
79     /**
80      * Drops adopted shell permissions.
81      */
82     @After
teardown()83     public void teardown() throws InterruptedException, IOException {
84         InstallUtils.dropShellPermissionIdentity();
85     }
86 
87     @Test
cleanUp()88     public void cleanUp() throws Exception {
89         // Remove all pending rollbacks
90         RollbackManager rm = RollbackUtils.getRollbackManager();
91         rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream())
92                 .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
93         // remove the version file.
94         Files.deleteIfExists(getApkInApexVersionFile().toPath());
95     }
96 
97     /**
98      * Test rollbacks of staged installs involving only apks.
99      * Commits TestApp.A2 as a staged install with rollback enabled.
100      */
101     @Test
testApkOnlyStagedRollback_Phase1_Install()102     public void testApkOnlyStagedRollback_Phase1_Install() throws Exception {
103         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
104 
105         Install.single(TestApp.A1).commit();
106         Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
107     }
108 
109     /**
110      * Test rollbacks of staged installs involving only apks.
111      * Confirms a staged rollback is available for TestApp.A2 and commits the
112      * rollback.
113      */
114     @Test
testApkOnlyStagedRollback_Phase2_RollBack()115     public void testApkOnlyStagedRollback_Phase2_RollBack() throws Exception {
116         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
117         InstallUtils.processUserData(TestApp.A);
118 
119         RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
120         assertThat(available).isStaged();
121         assertThat(available).packagesContainsExactly(
122                 Rollback.from(TestApp.A2).to(TestApp.A1));
123         assertThat(RollbackUtils.getCommittedRollback(TestApp.A)).isNull();
124 
125         RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
126         RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
127         assertThat(committed).hasRollbackId(available.getRollbackId());
128         assertThat(committed).isStaged();
129         assertThat(committed).packagesContainsExactly(
130                 Rollback.from(TestApp.A2).to(TestApp.A1));
131         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
132         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
133 
134         // Note: The app is not rolled back until after the rollback is staged
135         // and the device has been rebooted.
136         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
137     }
138 
139     /**
140      * Test rollbacks of staged installs involving only apks.
141      * Confirms TestApp.A2 was rolled back.
142      */
143     @Test
testApkOnlyStagedRollback_Phase3_Confirm()144     public void testApkOnlyStagedRollback_Phase3_Confirm() throws Exception {
145         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
146         InstallUtils.processUserData(TestApp.A);
147 
148         RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
149         assertThat(committed).isStaged();
150         assertThat(committed).packagesContainsExactly(
151                 Rollback.from(TestApp.A2).to(TestApp.A1));
152         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
153         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
154     }
155 
156     /**
157      * Test rollbacks of multiple staged installs involving only apks.
158      * Commits TestApp.A2 and TestApp.B2 as a staged install with rollback enabled.
159      */
160     @Test
testApkOnlyMultipleStagedRollback_Phase1_Install()161     public void testApkOnlyMultipleStagedRollback_Phase1_Install() throws Exception {
162         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
163         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
164 
165         Install.single(TestApp.A1).commit();
166         Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
167 
168         Install.single(TestApp.B1).commit();
169         Install.single(TestApp.B2).setStaged().setEnableRollback().commit();
170     }
171 
172     /**
173      * Test rollbacks of multiple staged installs involving only apks.
174      * Confirms staged rollbacks are available for TestApp.A2 and TestApp.b2, and commits the
175      * rollback.
176      */
177     @Test
testApkOnlyMultipleStagedRollback_Phase2_RollBack()178     public void testApkOnlyMultipleStagedRollback_Phase2_RollBack() throws Exception {
179         // Process TestApp.A
180         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
181         InstallUtils.processUserData(TestApp.A);
182         RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
183         assertThat(available).isStaged();
184         assertThat(available).packagesContainsExactly(
185                 Rollback.from(TestApp.A2).to(TestApp.A1));
186         assertThat(RollbackUtils.getCommittedRollback(TestApp.A)).isNull();
187 
188         RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
189         RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
190         assertThat(committed).hasRollbackId(available.getRollbackId());
191         assertThat(committed).isStaged();
192         assertThat(committed).packagesContainsExactly(
193                 Rollback.from(TestApp.A2).to(TestApp.A1));
194         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
195         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
196         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
197 
198         // Process TestApp.B
199         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
200         InstallUtils.processUserData(TestApp.B);
201         available = RollbackUtils.getAvailableRollback(TestApp.B);
202         assertThat(available).isStaged();
203         assertThat(available).packagesContainsExactly(
204                 Rollback.from(TestApp.B2).to(TestApp.B1));
205         assertThat(RollbackUtils.getCommittedRollback(TestApp.B)).isNull();
206 
207         RollbackUtils.rollback(available.getRollbackId(), TestApp.B2);
208         committed = RollbackUtils.getCommittedRollback(TestApp.B);
209         assertThat(committed).hasRollbackId(available.getRollbackId());
210         assertThat(committed).isStaged();
211         assertThat(committed).packagesContainsExactly(
212                 Rollback.from(TestApp.B2).to(TestApp.B1));
213         assertThat(committed).causePackagesContainsExactly(TestApp.B2);
214         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
215         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
216     }
217 
218     /**
219      * Test rollbacks of staged installs involving only apks.
220      * Confirms TestApp.A2 and TestApp.B2 was rolled back.
221      */
222     @Test
testApkOnlyMultipleStagedRollback_Phase3_Confirm()223     public void testApkOnlyMultipleStagedRollback_Phase3_Confirm() throws Exception {
224         // Process TestApp.A
225         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
226         InstallUtils.processUserData(TestApp.A);
227         RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
228         assertThat(committed).isStaged();
229         assertThat(committed).packagesContainsExactly(
230                 Rollback.from(TestApp.A2).to(TestApp.A1));
231         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
232         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
233 
234         // Process TestApp.B
235         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
236         InstallUtils.processUserData(TestApp.B);
237         committed = RollbackUtils.getCommittedRollback(TestApp.B);
238         assertThat(committed).isStaged();
239         assertThat(committed).packagesContainsExactly(
240                 Rollback.from(TestApp.B2).to(TestApp.B1));
241         assertThat(committed).causePackagesContainsExactly(TestApp.B2);
242         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
243     }
244 
245     /**
246      * Test partial rollbacks of multiple staged installs involving only apks.
247      * Commits TestApp.A2 and TestApp.B2 as a staged install with rollback enabled.
248      */
249     @Test
testApkOnlyMultipleStagedPartialRollback_Phase1_Install()250     public void testApkOnlyMultipleStagedPartialRollback_Phase1_Install() throws Exception {
251         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
252         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
253 
254         Install.single(TestApp.A1).commit();
255         Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
256 
257         Install.single(TestApp.B1).commit();
258         Install.single(TestApp.B2).setStaged().commit();
259     }
260 
261     /**
262      * Test partial rollbacks of multiple staged installs involving only apks.
263      * Confirms staged rollbacks are available for TestApp.A2, and commits the
264      * rollback.
265      */
266     @Test
testApkOnlyMultipleStagedPartialRollback_Phase2_RollBack()267     public void testApkOnlyMultipleStagedPartialRollback_Phase2_RollBack() throws Exception {
268         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
269         InstallUtils.processUserData(TestApp.A);
270         RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
271         assertThat(available).isStaged();
272         assertThat(available).packagesContainsExactly(
273                 Rollback.from(TestApp.A2).to(TestApp.A1));
274         assertThat(RollbackUtils.getCommittedRollback(TestApp.A)).isNull();
275 
276         RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
277         RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
278         assertThat(committed).hasRollbackId(available.getRollbackId());
279         assertThat(committed).isStaged();
280         assertThat(committed).packagesContainsExactly(
281                 Rollback.from(TestApp.A2).to(TestApp.A1));
282         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
283         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
284         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
285     }
286 
287     /**
288      * Test partial rollbacks of staged installs involving only apks.
289      * Confirms TestApp.A2 was rolled back.
290      */
291     @Test
testApkOnlyMultipleStagedPartialRollback_Phase3_Confirm()292     public void testApkOnlyMultipleStagedPartialRollback_Phase3_Confirm() throws Exception {
293         // Process TestApp.A
294         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
295         InstallUtils.processUserData(TestApp.A);
296         RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
297         assertThat(committed).isStaged();
298         assertThat(committed).packagesContainsExactly(
299                 Rollback.from(TestApp.A2).to(TestApp.A1));
300         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
301         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
302 
303         // Process TestApp.B
304         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
305     }
306 
307     /**
308      * Test rollbacks of staged installs involving only apex.
309      * Install first version phase.
310      *
311      * <p> We start by installing version 2. The test ultimately rolls back from 3 to 2.
312      */
313     @Test
testApexOnlyStagedRollback_Phase1_InstallFirst()314     public void testApexOnlyStagedRollback_Phase1_InstallFirst() throws Exception {
315         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
316         Install.single(TestApp.Apex2).setStaged().commit();
317     }
318 
319     /**
320      * Test rollbacks of staged installs involving only apex.
321      * Enable rollback phase.
322      */
323     @Test
testApexOnlyStagedRollback_Phase2_InstallSecond()324     public void testApexOnlyStagedRollback_Phase2_InstallSecond() throws Exception {
325         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
326 
327         // keep the versions of the apks in shim apex for verifying in phase3
328         recordApkInApexVersion();
329 
330         Install.single(TestApp.Apex3).setStaged().setEnableRollback().commit();
331     }
332 
333     /**
334      * Test rollbacks of staged installs involving only apex.
335      * Commit rollback phase.
336      */
337     @Test
testApexOnlyStagedRollback_Phase3_RollBack()338     public void testApexOnlyStagedRollback_Phase3_RollBack() throws Exception {
339         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
340 
341         long[] versions = retrieveApkInApexVersion();
342         final long apkInShimApexVersion = versions[0];
343         final long privApkInShimApexVersion = versions[1];
344 
345         RollbackInfo available = RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME);
346         assertThat(available).isStaged();
347         assertThat(available).packagesContainsExactly(
348                 Rollback.from(TestApp.Apex3).to(TestApp.Apex2),
349                 Rollback.from(SHIM_PACKAGE_NAME, 0)
350                         .to(SHIM_PACKAGE_NAME, apkInShimApexVersion),
351                 Rollback.from(PRIVILEGED_SHIM_PACKAGE_NAME, 0)
352                         .to(PRIVILEGED_SHIM_PACKAGE_NAME, privApkInShimApexVersion));
353 
354         RollbackUtils.rollback(available.getRollbackId(), TestApp.Apex3);
355         RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
356         assertThat(committed).isNotNull();
357         assertThat(committed).isStaged();
358         assertThat(committed).packagesContainsExactly(
359                 Rollback.from(TestApp.Apex3).to(TestApp.Apex2),
360                 Rollback.from(SHIM_PACKAGE_NAME, 0)
361                         .to(SHIM_PACKAGE_NAME, apkInShimApexVersion),
362                 Rollback.from(PRIVILEGED_SHIM_PACKAGE_NAME, 0)
363                         .to(PRIVILEGED_SHIM_PACKAGE_NAME, privApkInShimApexVersion));
364         assertThat(committed).causePackagesContainsExactly(TestApp.Apex3);
365         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
366 
367         // Note: The app is not rolled back until after the rollback is staged
368         // and the device has been rebooted.
369         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
370     }
371 
372     /**
373      * Test rollbacks of staged installs involving only apex.
374      * Confirm rollback phase.
375      */
376     @Test
testApexOnlyStagedRollback_Phase4_Confirm()377     public void testApexOnlyStagedRollback_Phase4_Confirm() throws Exception {
378         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
379 
380         // Rollback data for shim apex will remain in storage since the apex cannot be completely
381         // removed and thus the rollback data won't be expired. Unfortunately, we can't also delete
382         // the rollback data manually from storage.
383     }
384 
385     /**
386      * Test rollback to system version involving apex only
387      */
388     @Test
testApexOnlySystemVersionStagedRollback_Phase1_Install()389     public void testApexOnlySystemVersionStagedRollback_Phase1_Install() throws Exception {
390         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
391 
392         // keep the versions of the apks in shim apex for verifying in phase2
393         recordApkInApexVersion();
394 
395         Install.single(TestApp.Apex2).setStaged().setEnableRollback().commit();
396     }
397 
398     @Test
testApexOnlySystemVersionStagedRollback_Phase2_RollBack()399     public void testApexOnlySystemVersionStagedRollback_Phase2_RollBack() throws Exception {
400         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
401 
402         long[] versions = retrieveApkInApexVersion();
403         final long apkInShimApexVersion = versions[0];
404         final long privApkInShimApexVersion = versions[1];
405 
406         RollbackInfo available = RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME);
407         assertThat(available).isStaged();
408         assertThat(available).packagesContainsExactly(
409                 Rollback.from(TestApp.Apex2).to(TestApp.Apex1),
410                 Rollback.from(SHIM_PACKAGE_NAME, 0)
411                         .to(SHIM_PACKAGE_NAME, apkInShimApexVersion),
412                 Rollback.from(PRIVILEGED_SHIM_PACKAGE_NAME, 0)
413                         .to(PRIVILEGED_SHIM_PACKAGE_NAME, privApkInShimApexVersion));
414 
415         RollbackUtils.rollback(available.getRollbackId(), TestApp.Apex2);
416         RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
417         assertThat(committed).isNotNull();
418         assertThat(committed).isStaged();
419         assertThat(committed).packagesContainsExactly(
420                 Rollback.from(TestApp.Apex2).to(TestApp.Apex1),
421                 Rollback.from(SHIM_PACKAGE_NAME, 0)
422                         .to(SHIM_PACKAGE_NAME, apkInShimApexVersion),
423                 Rollback.from(PRIVILEGED_SHIM_PACKAGE_NAME, 0)
424                         .to(PRIVILEGED_SHIM_PACKAGE_NAME, privApkInShimApexVersion));
425         assertThat(committed).causePackagesContainsExactly(TestApp.Apex2);
426         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
427 
428         // Note: The app is not rolled back until after the rollback is staged
429         // and the device has been rebooted.
430         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
431     }
432 
433     @Test
testApexOnlySystemVersionStagedRollback_Phase3_Confirm()434     public void testApexOnlySystemVersionStagedRollback_Phase3_Confirm() throws Exception {
435         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
436     }
437 
438     /**
439      * Test rollbacks of staged installs involving apex and apk.
440      * Install first version phase.
441      */
442     @Test
testApexAndApkStagedRollback_Phase1_InstallFirst()443     public void testApexAndApkStagedRollback_Phase1_InstallFirst() throws Exception {
444         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
445         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
446 
447         Install.multi(TestApp.Apex2, TestApp.A1).setStaged().commit();
448     }
449 
450     /**
451      * Test rollbacks of staged installs involving apex and apk.
452      * Enable rollback phase.
453      */
454     @Test
testApexAndApkStagedRollback_Phase2_InstallSecond()455     public void testApexAndApkStagedRollback_Phase2_InstallSecond() throws Exception {
456         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
457         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
458 
459         // keep the versions of the apks in shim apex for verifying in phase3 and phase4
460         recordApkInApexVersion();
461 
462         Install.multi(TestApp.Apex3, TestApp.A2).setStaged().setEnableRollback().commit();
463     }
464 
465     /**
466      * Test rollbacks of staged installs involving apex and apk.
467      * Commit rollback phase.
468      */
469     @Test
testApexAndApkStagedRollback_Phase3_RollBack()470     public void testApexAndApkStagedRollback_Phase3_RollBack() throws Exception {
471         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
472         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
473         InstallUtils.processUserData(TestApp.A);
474 
475         long[] versions = retrieveApkInApexVersion();
476         final long apkInShimApexVersion = versions[0];
477         final long privApkInShimApexVersion = versions[1];
478 
479         RollbackInfo available = RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME);
480         assertThat(available).isStaged();
481         assertThat(available).packagesContainsExactly(
482                 Rollback.from(TestApp.Apex3).to(TestApp.Apex2),
483                 Rollback.from(TestApp.A2).to(TestApp.A1),
484                 Rollback.from(SHIM_PACKAGE_NAME, 0)
485                         .to(SHIM_PACKAGE_NAME, apkInShimApexVersion),
486                 Rollback.from(PRIVILEGED_SHIM_PACKAGE_NAME, 0)
487                         .to(PRIVILEGED_SHIM_PACKAGE_NAME, privApkInShimApexVersion));
488 
489         RollbackUtils.rollback(available.getRollbackId(), TestApp.Apex3, TestApp.A2);
490         RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
491         assertThat(committed).isNotNull();
492         assertThat(committed).isStaged();
493         assertThat(committed).packagesContainsExactly(
494                 Rollback.from(TestApp.Apex3).to(TestApp.Apex2),
495                 Rollback.from(TestApp.A2).to(TestApp.A1),
496                 Rollback.from(SHIM_PACKAGE_NAME, 0)
497                         .to(SHIM_PACKAGE_NAME, apkInShimApexVersion),
498                 Rollback.from(PRIVILEGED_SHIM_PACKAGE_NAME, 0)
499                         .to(PRIVILEGED_SHIM_PACKAGE_NAME, privApkInShimApexVersion));
500         assertThat(committed).causePackagesContainsExactly(TestApp.Apex3, TestApp.A2);
501         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
502 
503         // Note: The app is not rolled back until after the rollback is staged
504         // and the device has been rebooted.
505         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
506         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
507     }
508 
509     /**
510      * Test rollbacks of staged installs involving apex and apk.
511      * Confirm rollback phase.
512      */
513     @Test
testApexAndApkStagedRollback_Phase4_Confirm()514     public void testApexAndApkStagedRollback_Phase4_Confirm() throws Exception {
515         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
516         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
517         InstallUtils.processUserData(TestApp.A);
518 
519         long[] versions = retrieveApkInApexVersion();
520         final long apkInShimApexVersion = versions[0];
521         final long privApkInShimApexVersion = versions[1];
522 
523         RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
524         assertThat(committed).isStaged();
525         assertThat(committed).packagesContainsExactly(
526                 Rollback.from(TestApp.Apex3).to(TestApp.Apex2),
527                 Rollback.from(TestApp.A2).to(TestApp.A1),
528                 Rollback.from(SHIM_PACKAGE_NAME, 0)
529                         .to(SHIM_PACKAGE_NAME, apkInShimApexVersion),
530                 Rollback.from(PRIVILEGED_SHIM_PACKAGE_NAME, 0)
531                         .to(PRIVILEGED_SHIM_PACKAGE_NAME, privApkInShimApexVersion));
532         assertThat(committed).causePackagesContainsExactly(TestApp.Apex3, TestApp.A2);
533         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
534 
535         // Rollback data for shim apex will remain in storage since the apex cannot be completely
536         // removed and thus the rollback data won't be expired. Unfortunately, we can't also delete
537         // the rollback data manually from storage due to SEPolicy rules.
538     }
539 
540     /**
541      * Tests that apex update expires existing rollbacks for that apex.
542      * Enable rollback phase.
543      */
544     @Test
testApexRollbackExpiration_Phase1_InstallFirst()545     public void testApexRollbackExpiration_Phase1_InstallFirst() throws Exception {
546         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
547 
548         Install.single(TestApp.Apex2).setStaged().setEnableRollback().commit();
549     }
550 
551     /**
552      * Tests that apex update expires existing rollbacks for that apex.
553      * Update apex phase.
554      */
555     @Test
testApexRollbackExpiration_Phase2_InstallSecond()556     public void testApexRollbackExpiration_Phase2_InstallSecond() throws Exception {
557         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
558         assertThat(RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME)).isNotNull();
559         Install.single(TestApp.Apex3).setStaged().commit();
560     }
561 
562     /**
563      * Tests that apex update expires existing rollbacks for that apex.
564      * Confirm expiration phase.
565      */
566     @Test
testApexRollbackExpiration_Phase3_Confirm()567     public void testApexRollbackExpiration_Phase3_Confirm() throws Exception {
568         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
569         assertThat(RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME)).isNull();
570     }
571 
572     /**
573      * Test rollback with key downgrade for apex only
574      */
575     @Test
testApexKeyRotationStagedRollback_Phase1_Install()576     public void testApexKeyRotationStagedRollback_Phase1_Install() throws Exception {
577         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
578 
579         // keep the versions of the apks in shim apex for verifying in phase2
580         recordApkInApexVersion();
581 
582         Install.single(Apex2SignedBobRotRollback).setStaged().setEnableRollback().commit();
583     }
584 
585     @Test
testApexKeyRotationStagedRollback_Phase2_RollBack()586     public void testApexKeyRotationStagedRollback_Phase2_RollBack() throws Exception {
587         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
588         RollbackInfo available = RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME);
589         long[] versions = retrieveApkInApexVersion();
590         final long apkInShimApexVersion = versions[0];
591         final long privApkInShimApexVersion = versions[1];
592 
593         assertThat(available).isStaged();
594         assertThat(available).packagesContainsExactly(
595                 Rollback.from(Apex2SignedBobRotRollback).to(TestApp.Apex1),
596                 Rollback.from(SHIM_PACKAGE_NAME, 0)
597                         .to(SHIM_PACKAGE_NAME, apkInShimApexVersion),
598                 Rollback.from(PRIVILEGED_SHIM_PACKAGE_NAME, 0)
599                         .to(PRIVILEGED_SHIM_PACKAGE_NAME, privApkInShimApexVersion));
600 
601         RollbackUtils.rollback(available.getRollbackId(), Apex2SignedBobRotRollback);
602         RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
603         assertThat(committed).isNotNull();
604         assertThat(committed).isStaged();
605         assertThat(committed).packagesContainsExactly(
606                 Rollback.from(Apex2SignedBobRotRollback).to(TestApp.Apex1),
607                 Rollback.from(SHIM_PACKAGE_NAME, 0)
608                         .to(SHIM_PACKAGE_NAME, apkInShimApexVersion),
609                 Rollback.from(PRIVILEGED_SHIM_PACKAGE_NAME, 0)
610                         .to(PRIVILEGED_SHIM_PACKAGE_NAME, privApkInShimApexVersion));
611         assertThat(committed).causePackagesContainsExactly(Apex2SignedBobRotRollback);
612         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
613 
614         // Note: The app is not rolled back until after the rollback is staged
615         // and the device has been rebooted.
616         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
617     }
618 
619     @Test
testApexKeyRotationStagedRollback_Phase3_Confirm()620     public void testApexKeyRotationStagedRollback_Phase3_Confirm() throws Exception {
621         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
622     }
623 
624     @Test
testApkRollbackByAnotherInstaller_Phase1_FirstInstaller()625     public void testApkRollbackByAnotherInstaller_Phase1_FirstInstaller() throws Exception {
626         Install.single(TestApp.A1).commit();
627         Install.single(TestApp.A2).setEnableRollback().commit();
628     }
629 
630     @Test
testFingerprintChange_Phase1_Install()631     public void testFingerprintChange_Phase1_Install() throws Exception {
632         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
633         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
634 
635         // Both available and enabling rollbacks should be invalidated after fingerprint changes.
636         Install.multi(TestApp.A1, TestApp.B1).commit();
637         Install.single(TestApp.A2).setEnableRollback().commit();
638         Install.single(TestApp.B2).setEnableRollback().setStaged().commit();
639     }
640 
641     @Test
testFingerprintChange_Phase2_Confirm()642     public void testFingerprintChange_Phase2_Confirm() throws Exception {
643         assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
644     }
645 
646     @Test
testRollbackFailsOtherSessions_Phase1_Install()647     public void testRollbackFailsOtherSessions_Phase1_Install() throws Exception {
648         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
649         Install.single(TestApp.A1).commit();
650         Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
651     }
652 
653     @Test
testRollbackFailsOtherSessions_Phase2_RollBack()654     public void testRollbackFailsOtherSessions_Phase2_RollBack() throws Exception {
655         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
656         InstallUtils.processUserData(TestApp.A);
657         // Stage session for package A to check if it can block rollback of A
658         final int sessionIdA = Install.single(TestApp.A3).setStaged().setEnableRollback().commit();
659 
660         // Stage another package not related to the rollback
661         Install.single(TestApp.B1).commit();
662         final int sessionIdB = Install.single(TestApp.B2).setStaged().setEnableRollback().commit();
663 
664         final RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
665         RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
666         final RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
667         assertThat(committed).hasRollbackId(available.getRollbackId());
668         assertThat(committed).isStaged();
669         assertThat(committed).packagesContainsExactly(
670                 Rollback.from(TestApp.A2).to(TestApp.A1));
671         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
672         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
673 
674         // Assert that blocking staged session is failed
675         final PackageInstaller.SessionInfo sessionA = InstallUtils.getStagedSessionInfo(sessionIdA);
676         assertThat(sessionA).isNotNull();
677         assertThat(sessionA.isStagedSessionFailed()).isTrue();
678 
679         // Assert that the unrelated staged session is also failed
680         final PackageInstaller.SessionInfo sessionB = InstallUtils.getStagedSessionInfo(sessionIdB);
681         assertThat(sessionB).isNotNull();
682         assertThat(sessionB.isStagedSessionFailed()).isTrue();
683 
684         // Note: The app is not rolled back until after the rollback is staged
685         // and the device has been rebooted.
686         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
687     }
688 
689     @Test
testRollbackFailsOtherSessions_Phase3_Confirm()690     public void testRollbackFailsOtherSessions_Phase3_Confirm() throws Exception {
691         // Process TestApp.A
692         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
693         InstallUtils.processUserData(TestApp.A);
694         final RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
695         assertThat(committed).isStaged();
696         assertThat(committed).packagesContainsExactly(
697                 Rollback.from(TestApp.A2).to(TestApp.A1));
698         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
699         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
700 
701         // Assert that unrelated package was also failed
702         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
703     }
704 
705     @Test
testSimultaneousRollbacksBothSucceed_Phase1_Install()706     public void testSimultaneousRollbacksBothSucceed_Phase1_Install() throws Exception {
707         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
708         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
709         Install.single(TestApp.A1).commit();
710         Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
711         Install.single(TestApp.B1).commit();
712         Install.single(TestApp.B2).setStaged().setEnableRollback().commit();
713     }
714 
715     @Test
testSimultaneousRollbacksBothSucceed_Phase2_RollBack()716     public void testSimultaneousRollbacksBothSucceed_Phase2_RollBack() throws Exception {
717         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
718         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
719         InstallUtils.processUserData(TestApp.A);
720         InstallUtils.processUserData(TestApp.B);
721 
722         final RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
723         RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
724         final RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
725         assertThat(committed).hasRollbackId(available.getRollbackId());
726         assertThat(committed).isStaged();
727         assertThat(committed).packagesContainsExactly(
728                 Rollback.from(TestApp.A2).to(TestApp.A1));
729         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
730         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
731 
732         final RollbackInfo availableB = RollbackUtils.getAvailableRollback(TestApp.B);
733         RollbackUtils.rollback(availableB.getRollbackId(), TestApp.B2);
734         final RollbackInfo committedB = RollbackUtils.getCommittedRollback(TestApp.B);
735         assertThat(committedB).hasRollbackId(availableB.getRollbackId());
736         assertThat(committedB).isStaged();
737         assertThat(committedB).packagesContainsExactly(
738                 Rollback.from(TestApp.B2).to(TestApp.B1));
739         assertThat(committedB).causePackagesContainsExactly(TestApp.B2);
740         assertThat(committedB.getCommittedSessionId()).isNotEqualTo(-1);
741     }
742 
743     @Test
testSimultaneousRollbacksBothSucceed_Phase3_Confirm()744     public void testSimultaneousRollbacksBothSucceed_Phase3_Confirm() throws Exception {
745         // Process TestApp.A
746         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
747         InstallUtils.processUserData(TestApp.A);
748         final RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
749         assertThat(committed).isStaged();
750         assertThat(committed).packagesContainsExactly(
751                 Rollback.from(TestApp.A2).to(TestApp.A1));
752         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
753         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
754 
755         // Process TestApp.B
756         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
757         InstallUtils.processUserData(TestApp.B);
758         final RollbackInfo committedB = RollbackUtils.getCommittedRollback(TestApp.B);
759         assertThat(committedB).isStaged();
760         assertThat(committedB).packagesContainsExactly(
761                 Rollback.from(TestApp.B2).to(TestApp.B1));
762         assertThat(committedB).causePackagesContainsExactly(TestApp.B2);
763         assertThat(committedB.getCommittedSessionId()).isNotEqualTo(-1);
764     }
765 
766     /**
767      * Record the versions of Apk in shim apex and PrivApk in shim apex
768      * in the order into {@link #APK_VERSION_FILENAME}.
769      *
770      * @see ShimPackage#SHIM_PACKAGE_NAME
771      * @see ShimPackage#PRIVILEGED_SHIM_PACKAGE_NAME
772      */
recordApkInApexVersion()773     private void recordApkInApexVersion() throws Exception {
774         final File versionFile = getApkInApexVersionFile();
775 
776         if (!versionFile.exists()) {
777             versionFile.createNewFile();
778         }
779 
780         final long apkInApexVersion = InstallUtils.getInstalledVersion(SHIM_PACKAGE_NAME);
781         final long privApkInApexVersion = InstallUtils.getInstalledVersion(
782                 PRIVILEGED_SHIM_PACKAGE_NAME);
783 
784         try (BufferedWriter writer = new BufferedWriter(new FileWriter(versionFile))) {
785             writer.write(apkInApexVersion + APK_VERSION_SEPARATOR + privApkInApexVersion);
786         }
787     }
788 
789     /**
790      * Returns the array of the versions of Apk in shim apex and PrivApk in shim apex
791      * in the order from {@link #APK_VERSION_FILENAME}.
792      *
793      * @see ShimPackage#SHIM_PACKAGE_NAME
794      * @see ShimPackage#PRIVILEGED_SHIM_PACKAGE_NAME
795      */
retrieveApkInApexVersion()796     private long[] retrieveApkInApexVersion() throws Exception {
797         final File versionFile = getApkInApexVersionFile();
798 
799         if (!versionFile.exists()) {
800             throw new IllegalStateException("The RollbackTest version file not found");
801         }
802 
803         try (BufferedReader reader = new BufferedReader(new FileReader(versionFile))) {
804             String[] versions = reader.readLine().split(APK_VERSION_SEPARATOR);
805 
806             if (versions.length != 2) {
807                 throw new IllegalStateException("The RollbackTest version file is wrong.");
808             }
809             return new long[]{Long.parseLong(versions[0]), Long.parseLong(versions[1])};
810         }
811     }
812 
getApkInApexVersionFile()813     private File getApkInApexVersionFile() {
814         final Context context = InstrumentationRegistry.getInstrumentation().getContext();
815         return new File(context.getFilesDir(), APK_VERSION_FILENAME);
816     }
817 }
818