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