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