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 com.android.queryable.Queryable; 20 import com.android.queryable.annotations.Query; 21 import com.android.queryable.info.ActivityInfo; 22 import com.android.queryable.info.ReceiverInfo; 23 import com.android.queryable.info.ServiceInfo; 24 import com.android.queryable.queries.BooleanQuery; 25 import com.android.queryable.queries.BooleanQueryHelper; 26 import com.android.queryable.queries.BundleQuery; 27 import com.android.queryable.queries.BundleQueryHelper; 28 import com.android.queryable.queries.IntegerQuery; 29 import com.android.queryable.queries.IntegerQueryHelper; 30 import com.android.queryable.queries.SetQuery; 31 import com.android.queryable.queries.SetQueryHelper; 32 import com.android.queryable.queries.StringQuery; 33 import com.android.queryable.queries.StringQueryHelper; 34 35 import com.google.auto.value.AutoAnnotation; 36 37 /** Builder for progressively building {@link TestApp} queries. */ 38 public final class TestAppQueryBuilder implements Queryable { 39 private final TestAppProvider mProvider; 40 41 StringQueryHelper<TestAppQueryBuilder> mLabel = new StringQueryHelper<>(this); 42 StringQueryHelper<TestAppQueryBuilder> mPackageName = new StringQueryHelper<>(this); 43 BundleQueryHelper<TestAppQueryBuilder> mMetadata = new BundleQueryHelper<>(this); 44 IntegerQueryHelper<TestAppQueryBuilder> mMinSdkVersion = new IntegerQueryHelper<>(this); 45 IntegerQueryHelper<TestAppQueryBuilder> mMaxSdkVersion = new IntegerQueryHelper<>(this); 46 IntegerQueryHelper<TestAppQueryBuilder> mTargetSdkVersion = new IntegerQueryHelper<>(this); 47 SetQueryHelper<TestAppQueryBuilder, String> mPermissions = 48 new SetQueryHelper<>(this); 49 BooleanQueryHelper<TestAppQueryBuilder> mTestOnly = new BooleanQueryHelper<>(this); 50 BooleanQueryHelper<TestAppQueryBuilder> mCrossProfile = new BooleanQueryHelper<>(this); 51 SetQueryHelper<TestAppQueryBuilder, ActivityInfo> mActivities = 52 new SetQueryHelper<>(this); 53 SetQueryHelper<TestAppQueryBuilder, ActivityInfo> mActivityAliases = 54 new SetQueryHelper<>(this); 55 SetQueryHelper<TestAppQueryBuilder, ServiceInfo> mServices = 56 new SetQueryHelper<>(this); 57 BooleanQueryHelper<TestAppQueryBuilder> mIsDeviceAdmin = new BooleanQueryHelper<>(this); 58 StringQueryHelper<TestAppQueryBuilder> mSharedUserId = new StringQueryHelper<>(this); 59 SetQueryHelper<TestAppQueryBuilder, ReceiverInfo> mReceivers = new SetQueryHelper<>(this); 60 BooleanQueryHelper<TestAppQueryBuilder> mIsHeadlessDOSingleUser = new BooleanQueryHelper<>(this); 61 private boolean mAllowInternalBedsteadTestApps = false; 62 63 /** 64 * Returns a {@link TestAppQueryBuilder} not linked to a specific {@link TestAppProvider}. 65 * 66 * <p>Note that attempts to resolve this query will fail. 67 */ queryBuilder()68 public static TestAppQueryBuilder queryBuilder() { 69 return new TestAppQueryBuilder(); 70 } 71 TestAppQueryBuilder()72 private TestAppQueryBuilder() { 73 mProvider = null; 74 } 75 TestAppQueryBuilder(TestAppProvider provider)76 TestAppQueryBuilder(TestAppProvider provider) { 77 if (provider == null) { 78 throw new NullPointerException(); 79 } 80 mProvider = provider; 81 } 82 83 /** 84 * Apply the query parameters inside the {@link Query} to this {@link TestAppQueryBuilder}. 85 */ applyAnnotation(Query query)86 public TestAppQueryBuilder applyAnnotation(Query query) { 87 if (query == null) { 88 return this; 89 } 90 91 TestAppQueryBuilder queryBuilder = this; 92 queryBuilder = queryBuilder.whereTargetSdkVersion().matchesAnnotation(query.targetSdkVersion()); 93 queryBuilder = queryBuilder.whereMinSdkVersion().matchesAnnotation(query.minSdkVersion()); 94 queryBuilder = queryBuilder.whereMaxSdkVersion().matchesAnnotation(query.maxSdkVersion()); 95 queryBuilder = queryBuilder.wherePackageName().matchesAnnotation(query.packageName()); 96 queryBuilder = queryBuilder.whereIsDeviceAdmin().matchesAnnotation(query.isDeviceAdmin()); 97 queryBuilder = queryBuilder.whereIsHeadlessDOSingleUser().matchesAnnotation( 98 query.isHeadlessDOSingleUser()); 99 return queryBuilder; 100 } 101 102 /** 103 * Query for a {@link TestApp} which declares the given label. 104 */ whereLabel()105 public StringQuery<TestAppQueryBuilder> whereLabel() { 106 return mLabel; 107 } 108 109 /** 110 * Query for a {@link TestApp} with a given package name. 111 * 112 * <p>Only use this filter when you are relying specifically on the package name itself. If you 113 * are relying on features you know the {@link TestApp} with that package name has, query for 114 * those features directly. 115 */ wherePackageName()116 public StringQuery<TestAppQueryBuilder> wherePackageName() { 117 return mPackageName; 118 } 119 120 /** 121 * Query for a {@link TestApp} by metadata. 122 */ whereMetadata()123 public BundleQuery<TestAppQueryBuilder> whereMetadata() { 124 return mMetadata; 125 } 126 127 /** 128 * Query for a {@link TestApp} by minSdkVersion. 129 */ whereMinSdkVersion()130 public IntegerQuery<TestAppQueryBuilder> whereMinSdkVersion() { 131 return mMinSdkVersion; 132 } 133 134 /** 135 * Query for a {@link TestApp} by maxSdkVersion. 136 */ whereMaxSdkVersion()137 public IntegerQuery<TestAppQueryBuilder> whereMaxSdkVersion() { 138 return mMaxSdkVersion; 139 } 140 141 /** 142 * Query for a {@link TestApp} by targetSdkVersion. 143 */ whereTargetSdkVersion()144 public IntegerQuery<TestAppQueryBuilder> whereTargetSdkVersion() { 145 return mTargetSdkVersion; 146 } 147 148 /** 149 * Query for a {@link TestApp} by declared permissions. 150 */ wherePermissions()151 public SetQuery<TestAppQueryBuilder, String> wherePermissions() { 152 return mPermissions; 153 } 154 155 /** 156 * Query for a {@link TestApp} by the testOnly attribute. 157 */ whereTestOnly()158 public BooleanQuery<TestAppQueryBuilder> whereTestOnly() { 159 return mTestOnly; 160 } 161 162 /** 163 * Query for a {@link TestApp} by the crossProfile attribute. 164 */ whereCrossProfile()165 public BooleanQuery<TestAppQueryBuilder> whereCrossProfile() { 166 return mCrossProfile; 167 } 168 169 /** 170 * Query for an app which is a device admin. 171 */ whereIsDeviceAdmin()172 public BooleanQuery<TestAppQueryBuilder> whereIsDeviceAdmin() { 173 return mIsDeviceAdmin; 174 } 175 176 /** 177 * Query for an app which is a headless device owner single user. 178 */ whereIsHeadlessDOSingleUser()179 public BooleanQuery<TestAppQueryBuilder> whereIsHeadlessDOSingleUser() { 180 return mIsHeadlessDOSingleUser; 181 } 182 183 /** 184 * Query for a {@link TestApp} by its sharedUserId; 185 */ whereSharedUserId()186 public StringQuery<TestAppQueryBuilder> whereSharedUserId() { 187 return mSharedUserId; 188 } 189 190 /** 191 * Query for a {@link TestApp} by its activities. 192 */ whereActivities()193 public SetQuery<TestAppQueryBuilder, ActivityInfo> whereActivities() { 194 return mActivities; 195 } 196 197 /** 198 * Query for a {@link TestApp} by its activity aliases. 199 */ whereActivityAliases()200 public SetQuery<TestAppQueryBuilder, ActivityInfo> whereActivityAliases() { 201 return mActivityAliases; 202 } 203 204 /** 205 * Query for a {@link TestApp} by its services. 206 */ whereServices()207 public SetQuery<TestAppQueryBuilder, ServiceInfo> whereServices() { 208 return mServices; 209 } 210 211 /** 212 * Query for a {@link TestApp} by its receivers. 213 */ whereReceivers()214 public SetQuery<TestAppQueryBuilder, ReceiverInfo> whereReceivers() { 215 return mReceivers; 216 } 217 218 /** 219 * Allow the query to return internal bedstead testapps. 220 */ allowInternalBedsteadTestApps()221 public TestAppQueryBuilder allowInternalBedsteadTestApps() { 222 mAllowInternalBedsteadTestApps = true; 223 return this; 224 } 225 226 /** 227 * Get the {@link TestApp} matching the query. 228 * 229 * @throws NotFoundException if there is no matching @{link TestApp}. 230 */ get()231 public TestApp get() { 232 // TODO(scottjonathan): Provide instructions on adding the TestApp if the query fails 233 return new TestApp(resolveQuery()); 234 } 235 236 /** 237 * Checks if the query matches the specified test app 238 */ matches(TestApp testApp)239 public boolean matches(TestApp testApp) { 240 TestAppDetails details = testApp.mDetails; 241 return matches(details); 242 } 243 resolveQuery()244 private TestAppDetails resolveQuery() { 245 if (mProvider == null) { 246 throw new IllegalStateException("Cannot resolve testApps in an empty query. You must" 247 + " create the query using a testAppProvider.query() rather than " 248 + "TestAppQueryBuilder.query() in order to get results"); 249 } 250 251 for (TestAppDetails details : mProvider.testApps()) { 252 if (!matches(details)) { 253 continue; 254 } 255 256 mProvider.markTestAppUsed(details); 257 return details; 258 } 259 260 throw new NotFoundException(this); 261 } 262 263 @Override isEmptyQuery()264 public boolean isEmptyQuery() { 265 return Queryable.isEmptyQuery(mPackageName) 266 && Queryable.isEmptyQuery(mLabel) 267 && Queryable.isEmptyQuery(mMetadata) 268 && Queryable.isEmptyQuery(mMinSdkVersion) 269 && Queryable.isEmptyQuery(mMaxSdkVersion) 270 && Queryable.isEmptyQuery(mTargetSdkVersion) 271 && Queryable.isEmptyQuery(mActivities) 272 && Queryable.isEmptyQuery(mActivityAliases) 273 && Queryable.isEmptyQuery(mServices) 274 && Queryable.isEmptyQuery(mPermissions) 275 && Queryable.isEmptyQuery(mTestOnly) 276 && Queryable.isEmptyQuery(mCrossProfile) 277 && Queryable.isEmptyQuery(mIsDeviceAdmin) 278 && Queryable.isEmptyQuery(mSharedUserId) 279 && Queryable.isEmptyQuery(mIsHeadlessDOSingleUser); 280 } 281 matches(TestAppDetails details)282 private boolean matches(TestAppDetails details) { 283 if (!StringQueryHelper.matches(mPackageName, details.mApp.getPackageName())) { 284 return false; 285 } 286 287 if (!StringQueryHelper.matches(mLabel, details.label())) { 288 return false; 289 } 290 291 if (!BundleQueryHelper.matches(mMetadata, details.mMetadata)) { 292 return false; 293 } 294 295 if (!IntegerQueryHelper.matches( 296 mMinSdkVersion, details.mApp.getUsesSdk().getMinSdkVersion())) { 297 return false; 298 } 299 300 if (!IntegerQueryHelper.matches( 301 mMaxSdkVersion, details.mApp.getUsesSdk().getMaxSdkVersion())) { 302 return false; 303 } 304 305 if (!IntegerQueryHelper.matches( 306 mTargetSdkVersion, details.mApp.getUsesSdk().getTargetSdkVersion())) { 307 return false; 308 } 309 310 if (!SetQueryHelper.matches(mActivities, details.mActivities)) { 311 return false; 312 } 313 314 if (!SetQueryHelper.matches(mActivityAliases, details.mActivityAliases)) { 315 return false; 316 } 317 318 if (!SetQueryHelper.matches(mServices, details.mServices)) { 319 return false; 320 } 321 322 if (!SetQueryHelper.matches(mPermissions, details.mPermissions)) { 323 return false; 324 } 325 326 if (!BooleanQueryHelper.matches(mTestOnly, details.mApp.getTestOnly())) { 327 return false; 328 } 329 330 if (!BooleanQueryHelper.matches(mCrossProfile, details.mApp.getCrossProfile())) { 331 return false; 332 } 333 334 if (!SetQueryHelper.matches(mReceivers, details.mReceivers)) { 335 return false; 336 } 337 338 // TODO(b/198419895): Actually query for the correct receiver + metadata 339 boolean isDeviceAdmin = details.mApp.getPackageName().contains( 340 "DeviceAdminTestApp"); 341 if (!BooleanQueryHelper.matches(mIsDeviceAdmin, isDeviceAdmin)) { 342 return false; 343 } 344 345 // TODO(b/320666412): Enable querying test apps using xml content 346 boolean isHeadlessDOSingleUser = details.mMetadata.getString("headless_do_single_user", 347 "false").equals("true"); 348 if (!BooleanQueryHelper.matches(mIsHeadlessDOSingleUser, isHeadlessDOSingleUser)) { 349 return false; 350 } 351 352 if (mSharedUserId.isEmpty()) { 353 if (details.sharedUserId() != null) { 354 return false; 355 } 356 } else { 357 if (!StringQueryHelper.matches(mSharedUserId, details.sharedUserId())) { 358 return false; 359 } 360 } 361 362 if (!mAllowInternalBedsteadTestApps 363 && details.mMetadata.getString("testapp-package-query-only", "false") 364 .equals("true")) { 365 if (!mPackageName.isQueryingForExactMatch()) { 366 return false; 367 } 368 } 369 370 return true; 371 } 372 373 @Override describeQuery(String fieldName)374 public String describeQuery(String fieldName) { 375 return "{" + Queryable.joinQueryStrings( 376 mPackageName.describeQuery("packageName"), 377 mLabel.describeQuery("label"), 378 mMetadata.describeQuery("metadata"), 379 mMinSdkVersion.describeQuery("minSdkVersion"), 380 mMaxSdkVersion.describeQuery("maxSdkVersion"), 381 mTargetSdkVersion.describeQuery("targetSdkVersion"), 382 mActivities.describeQuery("activities"), 383 mActivityAliases.describeQuery("activityAliases"), 384 mServices.describeQuery("services"), 385 mPermissions.describeQuery("permissions"), 386 mSharedUserId.describeQuery("sharedUserId"), 387 mTestOnly.describeQuery("testOnly"), 388 mCrossProfile.describeQuery("crossProfile"), 389 mIsDeviceAdmin.describeQuery("isDeviceAdmin"), 390 mIsHeadlessDOSingleUser.describeQuery("isHeadlessDOSingleUser") 391 ) + "}"; 392 } 393 394 @Override toString()395 public String toString() { 396 return "TestAppQueryBuilder" + describeQuery(null); 397 } 398 toAnnotation()399 public Query toAnnotation() { 400 return query(mPackageName.toAnnotation(), 401 mTargetSdkVersion.toAnnotation(), 402 mMinSdkVersion.toAnnotation(), 403 mMaxSdkVersion.toAnnotation(), 404 mIsDeviceAdmin.toAnnotation(), 405 mIsHeadlessDOSingleUser.toAnnotation()); 406 } 407 408 @AutoAnnotation query( com.android.queryable.annotations.StringQuery packageName, com.android.queryable.annotations.IntegerQuery targetSdkVersion, com.android.queryable.annotations.IntegerQuery minSdkVersion, com.android.queryable.annotations.IntegerQuery maxSdkVersion, com.android.queryable.annotations.BooleanQuery isDeviceAdmin, com.android.queryable.annotations.BooleanQuery isHeadlessDOSingleUser)409 private static Query query( 410 com.android.queryable.annotations.StringQuery packageName, 411 com.android.queryable.annotations.IntegerQuery targetSdkVersion, 412 com.android.queryable.annotations.IntegerQuery minSdkVersion, 413 com.android.queryable.annotations.IntegerQuery maxSdkVersion, 414 com.android.queryable.annotations.BooleanQuery isDeviceAdmin, 415 com.android.queryable.annotations.BooleanQuery isHeadlessDOSingleUser) { 416 return new AutoAnnotation_TestAppQueryBuilder_query( 417 packageName, targetSdkVersion, minSdkVersion, maxSdkVersion, isDeviceAdmin, 418 isHeadlessDOSingleUser); 419 } 420 } 421