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