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.install.lib;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import android.content.Intent;
22 import android.content.pm.PackageInstaller;
23 
24 import com.android.compatibility.common.util.SystemUtil;
25 
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.OutputStream;
29 
30 /**
31  * Builder class for installing test apps and creating install sessions.
32  */
33 public class Install {
34     // The collection of apps to be installed with parameters inherited from parent Install object.
35     private final TestApp[] mTestApps;
36     // The collection of apps to be installed with parameters independent of parent Install object.
37     private final Install[] mChildInstalls;
38     // Indicates whether Install represents a multiPackage install.
39     private final boolean mIsMultiPackage;
40     // PackageInstaller.Session parameters.
41     private boolean mIsStaged = false;
42     private boolean mIsDowngrade = false;
43     private boolean mEnableRollback = false;
44     private int mRollbackDataPolicy = 0;
45     private int mSessionMode = PackageInstaller.SessionParams.MODE_FULL_INSTALL;
46     private int mInstallFlags = 0;
47     private boolean mBypassAllowedApexUpdateCheck = true;
48     private boolean mBypassStagedInstallerCheck = true;
49 
Install(boolean isMultiPackage, TestApp... testApps)50     private Install(boolean isMultiPackage, TestApp... testApps) {
51         mIsMultiPackage = isMultiPackage;
52         mTestApps = testApps;
53         mChildInstalls = new Install[0];
54     }
55 
Install(boolean isMultiPackage, Install... installs)56     private Install(boolean isMultiPackage, Install... installs) {
57         mIsMultiPackage = isMultiPackage;
58         mTestApps = new TestApp[0];
59         mChildInstalls = installs;
60     }
61 
62     /**
63      * Creates an Install builder to install a single package.
64      */
single(TestApp testApp)65     public static Install single(TestApp testApp) {
66         return new Install(false, testApp);
67     }
68 
69     /**
70      * Creates an Install builder to install using multiPackage.
71      */
multi(TestApp... testApps)72     public static Install multi(TestApp... testApps) {
73         return new Install(true, testApps);
74     }
75 
76     /**
77      * Creates an Install builder from separate Install builders. The newly created builder
78      * will be responsible for building the parent session, while each one of the other builders
79      * will be responsible for building one of the child sessions.
80      *
81      * <p>Modifications to the parent install are not propagated to the child installs,
82      * and vice versa. This gives more control over a multi install session,
83      * e.g. can setStaged on a subset of the child sessions or setStaged on a child session but
84      * not on the parent session.
85      *
86      * <p>It's encouraged to use {@link #multi} that receives {@link TestApp}s
87      * instead of {@link Install}s. This variation of {@link #multi} should be used only if it's
88      * necessary to modify parameters in a subset of the installed sessions.
89      */
multi(Install... installs)90     public static Install multi(Install... installs) {
91         for (Install childInstall : installs) {
92             assertThat(childInstall.isMultiPackage()).isFalse();
93         }
94         Install install = new Install(true, installs);
95         return install;
96     }
97 
98     /**
99      * Makes the install a staged install.
100      */
setStaged()101     public Install setStaged() {
102         mIsStaged = true;
103         return this;
104     }
105 
106     /**
107      * Marks the install as a downgrade.
108      */
setRequestDowngrade()109     public Install setRequestDowngrade() {
110         mIsDowngrade = true;
111         return this;
112     }
113 
114     /**
115      * Enables rollback for the install.
116      */
setEnableRollback()117     public Install setEnableRollback() {
118         mEnableRollback = true;
119         return this;
120     }
121 
122     /**
123      * Enables rollback for the install with specified rollback data policy.
124      */
setEnableRollback(int dataPolicy)125     public Install setEnableRollback(int dataPolicy) {
126         mEnableRollback = true;
127         mRollbackDataPolicy = dataPolicy;
128         return this;
129     }
130 
131     /**
132      * Sets the session mode {@link PackageInstaller.SessionParams#MODE_INHERIT_EXISTING}.
133      * If it's not set, then the default session mode is
134      * {@link PackageInstaller.SessionParams#MODE_FULL_INSTALL}
135      */
setSessionMode(int sessionMode)136     public Install setSessionMode(int sessionMode) {
137         mSessionMode = sessionMode;
138         return this;
139     }
140 
141     /**
142      * Sets the session params.
143      */
addInstallFlags(int installFlags)144     public Install addInstallFlags(int installFlags) {
145         mInstallFlags |= installFlags;
146         return this;
147     }
148 
149     /**
150      * Sets whether to call {@code pm bypass-allowed-apex-update-check true} when creating install
151      * session.
152      */
setBypassAllowedApexUpdateCheck(boolean bypassAllowedApexUpdateCheck)153     public Install setBypassAllowedApexUpdateCheck(boolean bypassAllowedApexUpdateCheck) {
154         mBypassAllowedApexUpdateCheck = bypassAllowedApexUpdateCheck;
155         return this;
156     }
157 
158     /**
159      * Sets whether to call {@code pm bypass-staged-installer-check true} when creating install
160      * session.
161      */
setBypassStangedInstallerCheck(boolean bypassStagedInstallerCheck)162     public Install setBypassStangedInstallerCheck(boolean bypassStagedInstallerCheck) {
163         mBypassStagedInstallerCheck = bypassStagedInstallerCheck;
164         return this;
165     }
166 
167     /**
168      * Commits the install.
169      *
170      * @return the session id of the install session, if the session is successful.
171      * @throws AssertionError if the install doesn't succeed.
172      */
commit()173     public int commit() throws IOException, InterruptedException {
174         int sessionId = createSession();
175         try (PackageInstaller.Session session =
176                      InstallUtils.openPackageInstallerSession(sessionId)) {
177             LocalIntentSender sender = new LocalIntentSender();
178             session.commit(sender.getIntentSender());
179             Intent result = sender.getResult();
180             InstallUtils.assertStatusSuccess(result);
181             if (mIsStaged) {
182                 InstallUtils.waitForSessionReady(sessionId);
183             }
184             return sessionId;
185         }
186     }
187 
188     /**
189      * Kicks off an install flow by creating an install session
190      * and, in the case of a multiPackage install, child install sessions.
191      *
192      * @return the session id of the install session, if the session is successful.
193      */
createSession()194     public int createSession() throws IOException {
195         int sessionId;
196         if (isMultiPackage()) {
197             sessionId = createEmptyInstallSession(/*multiPackage*/ true, /*isApex*/false);
198             try (PackageInstaller.Session session =
199                          InstallUtils.openPackageInstallerSession(sessionId)) {
200                 for (Install subInstall : mChildInstalls) {
201                     session.addChildSessionId(subInstall.createSession());
202                 }
203                 for (TestApp testApp : mTestApps) {
204                     session.addChildSessionId(createSingleInstallSession(testApp));
205                 }
206             }
207         } else {
208             assert mTestApps.length == 1;
209             sessionId = createSingleInstallSession(mTestApps[0]);
210         }
211         return sessionId;
212     }
213 
214     /**
215      * Creates an empty install session with appropriate install params set.
216      *
217      * @return the session id of the newly created session
218      */
createEmptyInstallSession(boolean multiPackage, boolean isApex)219     private int createEmptyInstallSession(boolean multiPackage, boolean isApex)
220             throws IOException {
221         if ((mIsStaged || isApex) && mBypassStagedInstallerCheck) {
222             SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check true");
223         }
224         if (isApex && mBypassAllowedApexUpdateCheck) {
225             SystemUtil.runShellCommandForNoOutput("pm bypass-allowed-apex-update-check true");
226         }
227         try {
228             PackageInstaller.SessionParams params =
229                     new PackageInstaller.SessionParams(mSessionMode);
230             if (multiPackage) {
231                 params.setMultiPackage();
232             }
233             if (isApex) {
234                 params.setInstallAsApex();
235             }
236             if (mIsStaged) {
237                 params.setStaged();
238             }
239             params.setRequestDowngrade(mIsDowngrade);
240             params.setEnableRollback(mEnableRollback, mRollbackDataPolicy);
241             if (mInstallFlags != 0) {
242                 InstallUtils.mutateInstallFlags(params, mInstallFlags);
243             }
244             return InstallUtils.getPackageInstaller().createSession(params);
245         } finally {
246             if ((mIsStaged || isApex) && mBypassStagedInstallerCheck) {
247                 SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check false");
248             }
249             if (isApex && mBypassAllowedApexUpdateCheck) {
250                 SystemUtil.runShellCommandForNoOutput("pm bypass-allowed-apex-update-check false");
251             }
252         }
253     }
254 
255     /**
256      * Creates an install session for the given test app.
257      *
258      * @return the session id of the newly created session.
259      */
createSingleInstallSession(TestApp app)260     private int createSingleInstallSession(TestApp app) throws IOException {
261         int sessionId = createEmptyInstallSession(/*multiPackage*/false, app.isApex());
262         try (PackageInstaller.Session session =
263                      InstallUtils.getPackageInstaller().openSession(sessionId)) {
264             for (String resourceName : app.getResourceNames()) {
265                 try (OutputStream os = session.openWrite(resourceName, 0, -1);
266                      InputStream is = app.getResourceStream(resourceName);) {
267                     if (is == null) {
268                         throw new IOException("Resource " + resourceName + " not found");
269                     }
270                     byte[] buffer = new byte[4096];
271                     int n;
272                     while ((n = is.read(buffer)) >= 0) {
273                         os.write(buffer, 0, n);
274                     }
275                 }
276             }
277             return sessionId;
278         }
279     }
280 
isMultiPackage()281     private boolean isMultiPackage() {
282         return mIsMultiPackage;
283     }
284 
285 }
286