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