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