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