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.tests.atomicinstall;
18 
19 import static com.android.cts.install.lib.InstallUtils.assertStatusSuccess;
20 import static com.android.cts.install.lib.InstallUtils.getInstalledVersion;
21 import static com.android.cts.install.lib.InstallUtils.openPackageInstallerSession;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import static org.junit.Assert.fail;
26 
27 import android.Manifest;
28 import android.content.pm.PackageInstaller;
29 
30 import androidx.test.InstrumentationRegistry;
31 
32 import com.android.compatibility.common.util.SystemUtil;
33 import com.android.cts.install.lib.Install;
34 import com.android.cts.install.lib.InstallUtils;
35 import com.android.cts.install.lib.LocalIntentSender;
36 import com.android.cts.install.lib.TestApp;
37 import com.android.cts.install.lib.Uninstall;
38 
39 import org.junit.After;
40 import org.junit.Before;
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 import org.junit.runners.JUnit4;
44 
45 import java.util.function.Predicate;
46 import java.util.function.Supplier;
47 
48 /**
49  * Tests for multi-package (a.k.a. atomic) installs.
50  */
51 @RunWith(JUnit4.class)
52 public class AtomicInstallTest {
53     /**
54      * Time between repeated checks in {@link #retry}.
55      */
56     private static final long RETRY_CHECK_INTERVAL_MILLIS = 500;
57     /**
58      * Maximum number of checks in {@link #retry} before a timeout occurs.
59      */
60     private static final long RETRY_MAX_INTERVALS = 20;
61 
62     public static final String TEST_APP_CORRUPT_FILENAME = "corrupt.apk";
63     private static final TestApp CORRUPT_TESTAPP = new TestApp(
64             "corrupt", "com.corrupt", 1, false, TEST_APP_CORRUPT_FILENAME);
65 
adoptShellPermissions()66     private void adoptShellPermissions() {
67         InstrumentationRegistry
68                 .getInstrumentation()
69                 .getUiAutomation()
70                 .adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES,
71                     Manifest.permission.DELETE_PACKAGES);
72     }
73 
74     @Before
setup()75     public void setup() throws Exception {
76         adoptShellPermissions();
77 
78         Uninstall.packages(TestApp.A, TestApp.B, TestApp.C);
79     }
80 
81     @After
dropShellPermissions()82     public void dropShellPermissions() {
83         InstrumentationRegistry
84                 .getInstrumentation()
85                 .getUiAutomation()
86                 .dropShellPermissionIdentity();
87     }
88 
89     /**
90      * Cleans up sessions that are not committed during tests.
91      */
92     @After
cleanUpSessions()93     public void cleanUpSessions() {
94         InstallUtils.getPackageInstaller().getMySessions().forEach(info -> {
95             try {
96                 InstallUtils.getPackageInstaller().abandonSession(info.getSessionId());
97             } catch (Exception ignore) {
98             }
99         });
100     }
101 
retry(Supplier<T> supplier, Predicate<T> predicate, String message)102     private static <T> T retry(Supplier<T> supplier, Predicate<T> predicate, String message)
103             throws InterruptedException {
104         for (int i = 0; i < RETRY_MAX_INTERVALS; i++) {
105             T result = supplier.get();
106             if (predicate.test(result)) {
107                 return result;
108             }
109             Thread.sleep(RETRY_CHECK_INTERVAL_MILLIS);
110         }
111         throw new AssertionError(message);
112     }
113 
114     /**
115      * Tests a completed session should be cleaned up.
116      */
117     @Test
testSessionCleanUp_Single()118     public void testSessionCleanUp_Single() throws Exception {
119         int sessionId = Install.single(TestApp.A1).commit();
120         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
121         // The session is cleaned up asynchronously after install completed.
122         // Retry until the session no longer exists.
123         retry(() -> InstallUtils.getPackageInstaller().getSessionInfo(sessionId),
124                 info -> info == null,
125                 "Session " + sessionId + " not cleaned up");
126     }
127 
128     /**
129      * Tests a completed session should be cleaned up.
130      */
131     @Test
testSessionCleanUp_Multi()132     public void testSessionCleanUp_Multi() throws Exception {
133         int sessionId = Install.multi(TestApp.A1, TestApp.B1).commit();
134         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
135         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
136         // The session is cleaned up asynchronously after install completed.
137         // Retry until the session no longer exists.
138         retry(() -> InstallUtils.getPackageInstaller().getSessionInfo(sessionId),
139                 info -> info == null,
140                 "Session " + sessionId + " not cleaned up");
141     }
142 
143     @Test
testInstallTwoApks()144     public void testInstallTwoApks() throws Exception {
145         Install.multi(TestApp.A1, TestApp.B1).commit();
146         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
147         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
148     }
149 
150     /**
151      * Tests a removed child shouldn't be installed.
152      */
153     @Test
testRemoveChild()154     public void testRemoveChild() throws Exception {
155         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
156         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
157         assertThat(getInstalledVersion(TestApp.C)).isEqualTo(-1);
158 
159         int parentId = Install.multi(TestApp.A1).createSession();
160         int childBId = Install.single(TestApp.B1).createSession();
161         int childCId = Install.single(TestApp.C1).createSession();
162         try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
163             parent.addChildSessionId(childBId);
164             parent.addChildSessionId(childCId);
165             parent.removeChildSessionId(childBId);
166             LocalIntentSender sender = new LocalIntentSender();
167             parent.commit(sender.getIntentSender());
168             InstallUtils.assertStatusSuccess(sender.getResult());
169             assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
170             assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
171             assertThat(getInstalledVersion(TestApp.C)).isEqualTo(1);
172         }
173     }
174 
175     @Test
testInstallTwoApksDowngradeFail()176     public void testInstallTwoApksDowngradeFail() throws Exception {
177         Install.multi(TestApp.A2, TestApp.B1).commit();
178         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
179         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
180 
181         InstallUtils.commitExpectingFailure(AssertionError.class,
182                 "INSTALL_FAILED_VERSION_DOWNGRADE", Install.multi(TestApp.A1, TestApp.B1));
183         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
184         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
185     }
186 
187     @Test
testFailInconsistentMultiPackageCommit()188     public void testFailInconsistentMultiPackageCommit() throws Exception {
189         // Test inconsistency in staged settings
190         Install parentStaged = Install.multi(Install.single(TestApp.A1)).setStaged();
191         Install childStaged = Install.multi(Install.single(TestApp.A1).setStaged());
192 
193         assertInconsistentStagedSettings(parentStaged);
194         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
195         assertInconsistentStagedSettings(childStaged);
196         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
197 
198         // Test inconsistency in rollback settings
199         Install parentEnabledRollback = Install.multi(Install.single(TestApp.A1))
200                 .setEnableRollback();
201         Install childEnabledRollback = Install.multi(
202                 Install.single(TestApp.A1).setEnableRollback());
203 
204         assertInconsistentRollbackSettings(parentEnabledRollback);
205         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
206         assertInconsistentRollbackSettings(childEnabledRollback);
207         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
208     }
209 
210     @Test
testChildFailurePropagated()211     public void testChildFailurePropagated() throws Exception {
212         // Create a child session that "inherits" from a non-existent package. This
213         // causes the session commit to fail with a PackageManagerException.
214         Install childInstall = Install.single(TestApp.A1).setSessionMode(
215                 PackageInstaller.SessionParams.MODE_INHERIT_EXISTING);
216         Install parentInstall = Install.multi(childInstall);
217 
218         InstallUtils.commitExpectingFailure(AssertionError.class, "Missing existing base package",
219                 parentInstall);
220 
221         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
222     }
223 
224     @Test
testEarlyFailureFailsAll()225     public void testEarlyFailureFailsAll() throws Exception {
226         InstallUtils.commitExpectingFailure(AssertionError.class, "Failed to parse",
227                 Install.multi(TestApp.A1, TestApp.B1, CORRUPT_TESTAPP));
228         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
229         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
230     }
231 
232     @Test
testInvalidStateScenario_MultiSessionCantBeApex()233     public void testInvalidStateScenario_MultiSessionCantBeApex() throws Exception {
234         try {
235             SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check true");
236             PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
237                     PackageInstaller.SessionParams.MODE_FULL_INSTALL);
238             params.setMultiPackage();
239             params.setInstallAsApex();
240             params.setStaged();
241             try {
242                 InstallUtils.getPackageInstaller().createSession(params);
243                 fail("Should not be able to create a multi-session set as APEX!");
244             } catch (Exception ignore) {
245             }
246         } finally {
247             SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check false");
248         }
249     }
250 
251     /**
252      * Tests a single-session can't have child.
253      */
254     @Test
testInvalidStateScenario_AddChildToSingleSessionShouldFail()255     public void testInvalidStateScenario_AddChildToSingleSessionShouldFail() throws Exception {
256         int parentId = Install.single(TestApp.A1).createSession();
257         int childId = Install.single(TestApp.B1).createSession();
258         try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
259             try {
260                 parent.addChildSessionId(childId);
261                 fail("Should not be able to add a child session to a single-session!");
262             } catch (Exception ignore) {
263             }
264         }
265     }
266 
267     /**
268      * Tests a multi-session can't be a child.
269      */
270     @Test
testInvalidStateScenario_MultiSessionAddedAsChildShouldFail()271     public void testInvalidStateScenario_MultiSessionAddedAsChildShouldFail() throws Exception {
272         int parentId = Install.multi(TestApp.A1).createSession();
273         int childId = Install.multi(TestApp.B1).createSession();
274         try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
275             try {
276                 parent.addChildSessionId(childId);
277                 fail("Should not be able to add a multi-session as a child!");
278             } catch (Exception ignore) {
279             }
280         }
281     }
282 
283     /**
284      * Tests a committed session can't add child.
285      */
286     @Test
testInvalidStateScenario_AddChildToCommittedSessionShouldFail()287     public void testInvalidStateScenario_AddChildToCommittedSessionShouldFail() throws Exception {
288         int parentId = Install.multi(TestApp.A1).createSession();
289         int childId = Install.single(TestApp.B1).createSession();
290         try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
291             LocalIntentSender sender = new LocalIntentSender();
292             parent.commit(sender.getIntentSender());
293             try {
294                 parent.addChildSessionId(childId);
295                 fail("Should not be able to add child to a committed session");
296             } catch (Exception ignore) {
297             }
298         }
299     }
300 
301     /**
302      * Tests a committed session can't remove child.
303      */
304     @Test
testInvalidStateScenario_RemoveChildFromCommittedSessionShouldFail()305     public void testInvalidStateScenario_RemoveChildFromCommittedSessionShouldFail()
306             throws Exception {
307         int parentId = Install.multi(TestApp.A1).createSession();
308         int childId = Install.single(TestApp.B1).createSession();
309         try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
310             parent.addChildSessionId(childId);
311             LocalIntentSender sender = new LocalIntentSender();
312             parent.commit(sender.getIntentSender());
313             try {
314                 parent.removeChildSessionId(childId);
315                 fail("Should not be able to remove child from a committed session");
316             } catch (Exception ignore) {
317             }
318         }
319     }
320 
321     /**
322      * Tests removing a child that is not its own should do nothing.
323      */
324     @Test
testInvalidStateScenario_RemoveWrongChildShouldDoNothing()325     public void testInvalidStateScenario_RemoveWrongChildShouldDoNothing() throws Exception {
326         int parent1Id = Install.multi(TestApp.A1).createSession();
327         int parent2Id = Install.multi(TestApp.C1).createSession();
328         int childId = Install.single(TestApp.B1).createSession();
329         try (PackageInstaller.Session parent1 = openPackageInstallerSession(parent1Id);
330              PackageInstaller.Session parent2 = openPackageInstallerSession(parent2Id);) {
331             parent1.addChildSessionId(childId);
332             // Should do nothing since the child doesn't belong to parent2
333             parent2.removeChildSessionId(childId);
334             int currentParentId =
335                     InstallUtils.getPackageInstaller().getSessionInfo(childId).getParentSessionId();
336             // Check this child still belongs to parent1
337             assertThat(currentParentId).isEqualTo(parent1Id);
338             assertThat(parent1.getChildSessionIds()).asList().contains(childId);
339             assertThat(parent2.getChildSessionIds()).asList().doesNotContain(childId);
340         }
341     }
342 
343     @Test
testInvalidStateScenarios()344     public void testInvalidStateScenarios() throws Exception {
345         int parentSessionId = Install.multi(TestApp.A1, TestApp.B1).createSession();
346         try (PackageInstaller.Session parentSession =
347                      openPackageInstallerSession(parentSessionId)) {
348             for (int childSessionId : parentSession.getChildSessionIds()) {
349                 try (PackageInstaller.Session childSession =
350                              openPackageInstallerSession(childSessionId)) {
351                     try {
352                         LocalIntentSender sender = new LocalIntentSender();
353                         childSession.commit(sender.getIntentSender());
354                         fail("Should not be able to commit a child session!");
355                     } catch (IllegalStateException e) {
356                         // ignore
357                     }
358                     try {
359                         childSession.abandon();
360                         fail("Should not be able to abandon a child session!");
361                     } catch (IllegalStateException e) {
362                         // ignore
363                     }
364                 }
365             }
366             int toAbandonSessionId = Install.single(TestApp.A1).createSession();
367             try (PackageInstaller.Session toAbandonSession =
368                          openPackageInstallerSession(toAbandonSessionId)) {
369                 toAbandonSession.abandon();
370                 try {
371                     parentSession.addChildSessionId(toAbandonSessionId);
372                     fail("Should not be able to add abandoned child session!");
373                 } catch (RuntimeException e) {
374                     // ignore
375                 }
376             }
377 
378             LocalIntentSender sender = new LocalIntentSender();
379             parentSession.commit(sender.getIntentSender());
380             assertStatusSuccess(sender.getResult());
381         }
382     }
383 
assertInconsistentStagedSettings(Install install)384     private static void assertInconsistentStagedSettings(Install install) {
385         assertInconsistentSettings("inconsistent staged settings", install);
386     }
387 
assertInconsistentRollbackSettings(Install install)388     private static void assertInconsistentRollbackSettings(Install install) {
389         assertInconsistentSettings("inconsistent rollback settings", install);
390     }
391 
assertInconsistentSettings(String failMessage, Install install)392     private static void assertInconsistentSettings(String failMessage, Install install) {
393         InstallUtils.commitExpectingFailure(IllegalStateException.class, failMessage, install);
394     }
395 }
396