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