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