1 /* 2 * Copyright (C) 2021 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.bedstead.testapp; 18 19 import static com.android.compatibility.common.util.FileUtils.readInputStreamFully; 20 21 import android.content.Context; 22 import android.os.Bundle; 23 import android.os.UserHandle; 24 25 import androidx.annotation.Nullable; 26 27 import com.android.bedstead.nene.TestApis; 28 import com.android.bedstead.nene.exceptions.NeneException; 29 import com.android.bedstead.nene.packages.Package; 30 import com.android.bedstead.nene.users.UserReference; 31 import com.android.bedstead.nene.users.Users; 32 import com.android.bedstead.testapp.processor.annotations.TestAppSender; 33 import com.android.queryable.collections.QueryableActivityInfoHashSet; 34 35 import java.io.File; 36 import java.io.FileOutputStream; 37 import java.io.IOException; 38 import java.io.InputStream; 39 import java.util.Objects; 40 import java.util.Set; 41 42 /** Represents a single test app which can be installed and interacted with. */ 43 @TestAppSender 44 public final class TestApp { 45 // Must be instrumentation context to access resources 46 private static final Context sContext = TestApis.context().instrumentationContext(); 47 private static final String LOG_TAG = "TestApp"; 48 final TestAppDetails mDetails; 49 TestApp(TestAppDetails details)50 TestApp(TestAppDetails details) { 51 if (details == null) { 52 throw new NullPointerException(); 53 } 54 mDetails = details; 55 } 56 57 /** 58 * Get a {@link Package} for the {@link TestApp}. 59 * 60 * <p>This will only be resolvable after the app is installed. 61 */ pkg()62 public Package pkg() { 63 return TestApis.packages().find(packageName()); 64 } 65 66 /** 67 * Install the {@link TestApp} on the device for the instrumented user. 68 * 69 * <p>See {@link Users#instrumented()} 70 */ install()71 public TestAppInstance install() { 72 return install(TestApis.users().instrumented()); 73 } 74 75 /** 76 * Install the {@link TestApp} on the device for the given {@link UserReference}. 77 */ install(UserReference user)78 public TestAppInstance install(UserReference user) { 79 try { 80 pkg().installBytes(user, this::apkBytes); 81 // pkg().setAllowTestApiAccess(true); 82 } catch (NeneException e) { 83 throw new NeneException("Error while installing TestApp " + this, e); 84 } 85 86 return new TestAppInstance(this, user); 87 } 88 89 /** 90 * Install the {@link TestApp} on the device for the given {@link UserHandle}. 91 */ install(UserHandle user)92 public TestAppInstance install(UserHandle user) { 93 install(TestApis.users().find(user)); 94 return instance(user); 95 } 96 97 /** 98 * Uninstall the {@link TestApp} on the device from the instrumented user. 99 * 100 * <p>See {@link Users#instrumented()} 101 */ uninstall()102 public void uninstall() { 103 uninstall(TestApis.users().instrumented()); 104 } 105 106 /** 107 * Check if {@link TestApp} is installed on the given {@link UserReference} 108 */ installedOnUser(UserReference user)109 public boolean installedOnUser(UserReference user) { 110 return pkg().installedOnUser(user); 111 } 112 113 /** 114 * Check if {@link TestApp} is installed on the given {@link UserHandle} 115 */ installedOnUser(UserHandle user)116 public boolean installedOnUser(UserHandle user) { 117 return pkg().installedOnUser(user); 118 } 119 120 /** 121 * Uninstall the {@link TestApp} from the device from all users. 122 */ uninstallFromAllUsers()123 public void uninstallFromAllUsers() { 124 pkg().uninstallFromAllUsers(); 125 } 126 127 /** 128 * Uninstall the {@link TestApp} on the device from the given {@link UserReference}. 129 */ uninstall(UserReference user)130 public void uninstall(UserReference user) { 131 pkg().uninstall(user); 132 } 133 134 /** 135 * Uninstall the {@link TestApp} on the device from the given {@link UserHandle}. 136 */ uninstall(UserHandle user)137 public void uninstall(UserHandle user) { 138 uninstall(TestApis.users().find(user)); 139 } 140 141 /** 142 * Get a reference to the specific instance of this test app on a given user. 143 * 144 * <p>This does not check if the user exists, or if the test app is installed on the user. 145 */ instance(UserHandle user)146 public TestAppInstance instance(UserHandle user) { 147 return instance(TestApis.users().find(user)); 148 } 149 150 /** 151 * Get a reference to the specific instance of this test app on a given user. 152 * 153 * <p>This does not check if the user exists, or if the test app is installed on the user. 154 */ instance(UserReference user)155 public TestAppInstance instance(UserReference user) { 156 if (user == null) { 157 throw new NullPointerException(); 158 } 159 return new TestAppInstance(this, user); 160 } 161 162 /** 163 * Returns an {@link InputStream} of the apk for this app. 164 */ apkStream()165 public InputStream apkStream() { 166 return sContext.getResources().openRawResource(mDetails.mResourceIdentifier); 167 } 168 169 /** 170 * Returns a byte array of the apk for this app. 171 */ apkBytes()172 public byte[] apkBytes() { 173 try (InputStream inputStream = apkStream()) { 174 return readInputStreamFully(inputStream); 175 } catch (IOException e) { 176 throw new NeneException("Error when reading TestApp bytes", e); 177 } 178 } 179 180 /** Write the APK file to the given {@link File}. */ writeApkFile(File outputFile)181 public void writeApkFile(File outputFile) throws IOException { 182 outputFile.getParentFile().mkdirs(); 183 try (FileOutputStream output = new FileOutputStream(outputFile)) { 184 output.write(apkBytes()); 185 } 186 } 187 188 /** The package label, or the package name if no label is specified. */ label()189 public String label() { 190 return mDetails.label() != null ? mDetails.label() : packageName(); 191 } 192 193 /** The package name of the test app. */ packageName()194 public String packageName() { 195 return mDetails.mApp.getPackageName(); 196 } 197 198 /** The testOnly attribute for the test app. */ testOnly()199 public boolean testOnly() { 200 return mDetails.mApp.getTestOnly(); 201 } 202 203 /** The minSdkVersion of the test app. */ minSdkVersion()204 public int minSdkVersion() { 205 return mDetails.mApp.getUsesSdk().getMinSdkVersion(); 206 } 207 208 /** The maxSdkVersion of the test app. */ maxSdkVersion()209 public int maxSdkVersion() { 210 return mDetails.mApp.getUsesSdk().getMaxSdkVersion(); 211 } 212 213 /** The targetSdkVersion of the test app. */ targetSdkVersion()214 public int targetSdkVersion() { 215 return mDetails.mApp.getUsesSdk().getTargetSdkVersion(); 216 } 217 218 /** The permissions declared by the test app. */ permissions()219 public Set<String> permissions() { 220 return mDetails.mPermissions; 221 } 222 223 /** The activities which exist in the test app. */ activities()224 public QueryableActivityInfoHashSet activities() { 225 return new QueryableActivityInfoHashSet(mDetails.mActivities); 226 } 227 228 /** 229 * The metadata declared by the test app. 230 * 231 * <p>Note that currently all values are of type String. 232 */ metadata()233 public Bundle metadata() { 234 return mDetails.mMetadata; 235 } 236 237 @Override toString()238 public String toString() { 239 return "TestApp{" 240 + "packageName=" + packageName() 241 + "}"; 242 } 243 244 /** The shared user id of the test app, if any. */ 245 @Nullable sharedUserId()246 public String sharedUserId() { 247 return mDetails.sharedUserId(); 248 } 249 250 @Override equals(Object o)251 public boolean equals(Object o) { 252 if (this == o) return true; 253 if (!(o instanceof TestApp)) return false; 254 TestApp testApp = (TestApp) o; 255 return mDetails.mApp.getPackageName().equals(testApp.mDetails.mApp.getPackageName()); 256 } 257 258 @Override hashCode()259 public int hashCode() { 260 return Objects.hash(mDetails.mApp.getPackageName()); 261 } 262 } 263