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