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.cts.install.lib.Install;
33 import com.android.cts.install.lib.InstallUtils;
34 import com.android.cts.install.lib.LocalIntentSender;
35 import com.android.cts.install.lib.TestApp;
36 import com.android.cts.install.lib.Uninstall;
37 
38 import org.junit.After;
39 import org.junit.Before;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 import org.junit.runners.JUnit4;
43 
44 /**
45  * Tests for multi-package (a.k.a. atomic) installs.
46  */
47 @RunWith(JUnit4.class)
48 public class AtomicInstallTest {
49 
50     public static final String TEST_APP_CORRUPT_FILENAME = "corrupt.apk";
51     private static final TestApp CORRUPT_TESTAPP = new TestApp(
52             "corrupt", "com.corrupt", 1, false, TEST_APP_CORRUPT_FILENAME);
53 
adoptShellPermissions()54     private void adoptShellPermissions() {
55         InstrumentationRegistry
56                 .getInstrumentation()
57                 .getUiAutomation()
58                 .adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES,
59                     Manifest.permission.DELETE_PACKAGES);
60     }
61 
62     @Before
setup()63     public void setup() throws Exception {
64         adoptShellPermissions();
65 
66         Uninstall.packages(TestApp.A, TestApp.B);
67     }
68 
69     @After
dropShellPermissions()70     public void dropShellPermissions() {
71         InstrumentationRegistry
72                 .getInstrumentation()
73                 .getUiAutomation()
74                 .dropShellPermissionIdentity();
75     }
76 
77     @Test
testInstallTwoApks()78     public void testInstallTwoApks() throws Exception {
79         Install.multi(TestApp.A1, TestApp.B1).commit();
80         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
81         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
82     }
83 
84     @Test
testInstallTwoApksDowngradeFail()85     public void testInstallTwoApksDowngradeFail() throws Exception {
86         Install.multi(TestApp.A2, TestApp.B1).commit();
87         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
88         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
89 
90         InstallUtils.commitExpectingFailure(AssertionError.class,
91                 "INSTALL_FAILED_VERSION_DOWNGRADE", Install.multi(TestApp.A1, TestApp.B1));
92         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
93         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
94     }
95 
96     @Test
testFailInconsistentMultiPackageCommit()97     public void testFailInconsistentMultiPackageCommit() throws Exception {
98         // Test inconsistency in staged settings
99         Install parentStaged = Install.multi(Install.single(TestApp.A1)).setStaged();
100         Install childStaged = Install.multi(Install.single(TestApp.A1).setStaged());
101 
102         assertInconsistentStagedSettings(parentStaged);
103         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
104         assertInconsistentStagedSettings(childStaged);
105         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
106 
107         // Test inconsistency in rollback settings
108         Install parentEnabledRollback = Install.multi(Install.single(TestApp.A1))
109                 .setEnableRollback();
110         Install childEnabledRollback = Install.multi(
111                 Install.single(TestApp.A1).setEnableRollback());
112 
113         assertInconsistentRollbackSettings(parentEnabledRollback);
114         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
115         assertInconsistentRollbackSettings(childEnabledRollback);
116         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
117     }
118 
119     @Test
testChildFailurePropagated()120     public void testChildFailurePropagated() throws Exception {
121         // Create a child session that "inherits" from a non-existent package. This
122         // causes the session commit to fail with a PackageManagerException.
123         Install childInstall = Install.single(TestApp.A1).setSessionMode(
124                 PackageInstaller.SessionParams.MODE_INHERIT_EXISTING);
125         Install parentInstall = Install.multi(childInstall);
126 
127         InstallUtils.commitExpectingFailure(AssertionError.class, "Missing existing base package",
128                 parentInstall);
129 
130         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
131     }
132 
133     @Test
testEarlyFailureFailsAll()134     public void testEarlyFailureFailsAll() throws Exception {
135         InstallUtils.commitExpectingFailure(AssertionError.class, "Failed to parse",
136                 Install.multi(TestApp.A1, TestApp.B1, CORRUPT_TESTAPP));
137         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
138         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
139     }
140 
141     @Test
testInvalidStateScenarios()142     public void testInvalidStateScenarios() throws Exception {
143         int parentSessionId = Install.multi(TestApp.A1, TestApp.B1).createSession();
144         try (PackageInstaller.Session parentSession =
145                      openPackageInstallerSession(parentSessionId)) {
146             for (int childSessionId : parentSession.getChildSessionIds()) {
147                 try (PackageInstaller.Session childSession =
148                              openPackageInstallerSession(childSessionId)) {
149                     try {
150                         childSession.commit(LocalIntentSender.getIntentSender());
151                         fail("Should not be able to commit a child session!");
152                     } catch (IllegalStateException e) {
153                         // ignore
154                     }
155                     try {
156                         childSession.abandon();
157                         fail("Should not be able to abandon a child session!");
158                     } catch (IllegalStateException e) {
159                         // ignore
160                     }
161                 }
162             }
163             int toAbandonSessionId = Install.single(TestApp.A1).createSession();
164             try (PackageInstaller.Session toAbandonSession =
165                          openPackageInstallerSession(toAbandonSessionId)) {
166                 toAbandonSession.abandon();
167                 try {
168                     parentSession.addChildSessionId(toAbandonSessionId);
169                     fail("Should not be able to add abandoned child session!");
170                 } catch (RuntimeException e) {
171                     // ignore
172                 }
173             }
174 
175             parentSession.commit(LocalIntentSender.getIntentSender());
176             assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
177         }
178     }
179 
assertInconsistentStagedSettings(Install install)180     private static void assertInconsistentStagedSettings(Install install) {
181         assertInconsistentSettings("inconsistent staged settings", install);
182     }
183 
assertInconsistentRollbackSettings(Install install)184     private static void assertInconsistentRollbackSettings(Install install) {
185         assertInconsistentSettings("inconsistent rollback settings", install);
186     }
187 
assertInconsistentSettings(String failMessage, Install install)188     private static void assertInconsistentSettings(String failMessage, Install install) {
189         InstallUtils.commitExpectingFailure(AssertionError.class, failMessage, install);
190     }
191 }
192