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