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