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