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 android.content.Context;
20 import android.content.IntentFilter;
21 import android.os.Bundle;
22 import android.util.Log;
23 
24 import com.android.bedstead.nene.TestApis;
25 import com.android.queryable.annotations.Query;
26 import com.android.queryable.info.ActivityInfo;
27 import com.android.queryable.info.ReceiverInfo;
28 import com.android.queryable.info.ServiceInfo;
29 
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.Comparator;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Set;
38 
39 /** Entry point to Test App. Used for querying for {@link TestApp} instances. */
40 public final class TestAppProvider {
41 
42     private static final String TAG = TestAppProvider.class.getSimpleName();
43 
44     // Must be instrumentation context to access resources
45     private static final Context sContext = TestApis.context().instrumentationContext();
46     private boolean mTestAppsInitialised = false;
47     private final List<TestAppDetails> mTestApps = new ArrayList<>();
48     private Set<TestAppDetails> mTestAppsSnapshot = null;
49 
TestAppProvider()50     public TestAppProvider() {
51         initTestApps();
52     }
53 
54     /** Begin a query for a {@link TestApp}. */
query()55     public TestAppQueryBuilder query() {
56         return new TestAppQueryBuilder(this);
57     }
58 
59     /** Create a query for a {@link TestApp} starting with a {@link Query}. */
query(Query query)60     public TestAppQueryBuilder query(Query query) {
61         return query().applyAnnotation(query);
62     }
63 
64     /** Get any {@link TestApp}. */
any()65     public TestApp any() {
66         TestApp testApp = query().get();
67         Log.d(TAG, "any(): returning " + testApp);
68         return testApp;
69     }
70 
testApps()71     List<TestAppDetails> testApps() {
72         return mTestApps;
73     }
74 
75     /** Save the state of the provider, to be reset by {@link #restore()}. */
snapshot()76     public void snapshot() {
77         mTestAppsSnapshot = new HashSet<>(mTestApps);
78     }
79 
80     /**
81      * Restore the state of the provider to that recorded by {@link #snapshot()}.
82      */
restore()83     public void restore() {
84         if (mTestAppsSnapshot == null) {
85             throw new IllegalStateException("You must call snapshot() before restore()");
86         }
87         mTestApps.clear();
88         mTestApps.addAll(mTestAppsSnapshot);
89     }
90 
91     /**
92      * Release resources.
93      * <br><br>
94      * Note: This method is intended for internal use and should <b>not</b> be called outside core
95      * Bedstead infrastructure.
96      */
releaseResources()97     public void releaseResources() {
98         mTestApps.clear();
99         mTestAppsSnapshot.clear();
100     }
101 
initTestApps()102     private void initTestApps() {
103         if (mTestAppsInitialised) {
104             return;
105         }
106         mTestAppsInitialised = true;
107 
108         int indexId = sContext.getResources().getIdentifier(
109                 "raw/index", /* defType= */ null, sContext.getPackageName());
110 
111         try (InputStream inputStream = sContext.getResources().openRawResource(indexId)) {
112             TestappProtos.TestAppIndex index = TestappProtos.TestAppIndex.parseFrom(inputStream);
113             for (int i = 0; i < index.getAppsCount(); i++) {
114                 loadApk(index.getApps(i));
115             }
116             Collections.sort(mTestApps,
117                     Comparator.comparing((testAppDetails) -> testAppDetails.mApp.getPackageName()));
118         } catch (IOException e) {
119             throw new RuntimeException("Error loading testapp index", e);
120         }
121     }
122 
loadApk(TestappProtos.AndroidApp app)123     private void loadApk(TestappProtos.AndroidApp app) {
124         TestAppDetails details = new TestAppDetails();
125         details.mApp = app;
126 
127         details.mResourceIdentifier = sContext.getResources().getIdentifier(
128                 "raw/" + getApkNameWithoutSuffix(app.getApkName()),
129                 /* defType= */ null, sContext.getPackageName());
130 
131         for (int i = 0; i < app.getMetadataCount(); i++) {
132             TestappProtos.Metadata metadataEntry = app.getMetadata(i);
133             details.mMetadata.putString(metadataEntry.getName(), metadataEntry.getValue());
134         }
135 
136         for (int i = 0; i < app.getPermissionsCount(); i++) {
137             details.mPermissions.add(app.getPermissions(i).getName());
138         }
139 
140         for (int i = 0; i < app.getActivitiesCount(); i++) {
141             TestappProtos.Activity activityEntry = app.getActivities(i);
142             details.mActivities.add(ActivityInfo.builder()
143                     .activityClass(activityEntry.getName())
144                     .exported(activityEntry.getExported())
145                     .intentFilters(intentFilterSetFromProtoList(
146                             activityEntry.getIntentFiltersList()))
147                     .permission(activityEntry.getPermission().equals("") ? null
148                             : activityEntry.getPermission())
149                     .build());
150         }
151 
152         for (int i = 0; i < app.getActivityAliasesCount(); i++) {
153             TestappProtos.ActivityAlias activityAliasEntry = app.getActivityAliases(i);
154             ActivityInfo activityInfo = ActivityInfo.builder()
155                     .activityClass(activityAliasEntry.getName())
156                     .exported(activityAliasEntry.getExported())
157                     .intentFilters(intentFilterSetFromProtoList(
158                             activityAliasEntry.getIntentFiltersList()))
159                     .permission(activityAliasEntry.getPermission().equals("") ? null
160                             : activityAliasEntry.getPermission())
161                     .build();
162 
163             details.mActivityAliases.add(activityInfo);
164 
165         }
166 
167         for (int i = 0; i < app.getServicesCount(); i++) {
168             TestappProtos.Service serviceEntry = app.getServices(i);
169             details.mServices.add(ServiceInfo.builder()
170                     .serviceClass(serviceEntry.getName())
171                     .intentFilters(intentFilterSetFromProtoList(
172                             serviceEntry.getIntentFiltersList()))
173                     .build());
174         }
175 
176         for (int i = 0; i < app.getReceiversCount(); i++) {
177             TestappProtos.Receiver receiverEntry = app.getReceivers(i);
178             details.mReceivers.add(ReceiverInfo.builder()
179                     .name(receiverEntry.getName())
180                     .metadata(metadataSetFromProtoList(receiverEntry.getMetadataList()))
181                     .build());
182         }
183 
184         mTestApps.add(details);
185     }
186 
intentFilterSetFromProtoList( List<TestappProtos.IntentFilter> list)187     private Set<IntentFilter> intentFilterSetFromProtoList(
188             List<TestappProtos.IntentFilter> list) {
189         Set<IntentFilter> filterInfoSet = new HashSet<>();
190 
191         for (TestappProtos.IntentFilter filter : list) {
192             IntentFilter filterInfo = intentFilterFromProto(filter);
193             filterInfoSet.add(filterInfo);
194         }
195 
196         return filterInfoSet;
197     }
198 
intentFilterFromProto(TestappProtos.IntentFilter filterProto)199     private IntentFilter intentFilterFromProto(TestappProtos.IntentFilter filterProto) {
200         IntentFilter filter = new IntentFilter();
201 
202         for (String action : filterProto.getActionsList()) {
203             filter.addAction(action);
204         }
205         for (String category : filterProto.getCategoriesList()) {
206             filter.addCategory(category);
207         }
208 
209         return filter;
210     }
211 
metadataSetFromProtoList( List<TestappProtos.Metadata> list)212     private Set<Bundle> metadataSetFromProtoList(
213             List<TestappProtos.Metadata> list) {
214         Set<Bundle> metadataSet = new HashSet<>();
215 
216         for (TestappProtos.Metadata metadata : list) {
217             Bundle metadataBundle = new Bundle();
218             metadataBundle.putString(metadata.getName(), metadata.getValue());
219             metadataSet.add(metadataBundle);
220         }
221 
222         return metadataSet;
223     }
224 
getApkNameWithoutSuffix(String apkName)225     private String getApkNameWithoutSuffix(String apkName) {
226         return apkName.split("\\.", 2)[0];
227     }
228 
markTestAppUsed(TestAppDetails testApp)229     void markTestAppUsed(TestAppDetails testApp) {
230         mTestApps.remove(testApp);
231     }
232 }
233