1 /* 2 * Copyright (C) 2011 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.cts.verifier; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.pm.ActivityInfo; 22 import android.content.pm.PackageManager; 23 import android.content.pm.ResolveInfo; 24 import android.os.Bundle; 25 import android.util.Log; 26 import android.widget.ListView; 27 28 import java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.Comparator; 31 import java.util.HashMap; 32 import java.util.HashSet; 33 import java.util.Iterator; 34 import java.util.List; 35 import java.util.Map; 36 37 /** 38 * {@link TestListAdapter} that populates the {@link TestListActivity}'s {@link ListView} by 39 * reading data from the CTS Verifier's AndroidManifest.xml. 40 * <p> 41 * Making a new test activity to appear in the list requires the following steps: 42 * 43 * <ol> 44 * <li>REQUIRED: Add an activity to the AndroidManifest.xml with an intent filter with a 45 * main action and the MANUAL_TEST category. 46 * <pre> 47 * <intent-filter> 48 * <action android:name="android.intent.action.MAIN" /> 49 * <category android:name="android.cts.intent.category.MANUAL_TEST" /> 50 * </intent-filter> 51 * </pre> 52 * </li> 53 * <li>OPTIONAL: Add a meta data attribute to indicate what category of tests the activity 54 * should belong to. If you don't add this attribute, your test will show up in the 55 * "Other" tests category. 56 * <pre> 57 * <meta-data android:name="test_category" android:value="@string/test_category_security" /> 58 * </pre> 59 * </li> 60 * <li>OPTIONAL: Add a meta data attribute to indicate whether this test has a parent test. 61 * <pre> 62 * <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" /> 63 * </pre> 64 * </li> 65 * <li>OPTIONAL: Add a meta data attribute to indicate what features are required to run the 66 * test. If the device does not have all of the required features then it will not appear 67 * in the test list. Use a colon (:) to specify multiple required features. 68 * <pre> 69 * <meta-data android:name="test_required_features" android:value="android.hardware.sensor.accelerometer" /> 70 * </pre> 71 * </li> 72 * <li>OPTIONAL: Add a meta data attribute to indicate features such that, if any present, the 73 * test gets excluded from being shown. If the device has any of the excluded features then 74 * the test will not appear in the test list. Use a colon (:) to specify multiple features 75 * to exclude for the test. Note that the colon means "or" in this case. 76 * <pre> 77 * <meta-data android:name="test_excluded_features" android:value="android.hardware.type.television" /> 78 * </pre> 79 * </li> 80 * <li>OPTIONAL: Add a meta data attribute to indicate features such that, if any present, 81 * the test is applicable to run. If the device has any of the applicable features then 82 * the test will appear in the test list. Use a colon (:) to specify multiple features 83 * <pre> 84 * <meta-data android:name="test_applicable_features" android:value="android.hardware.sensor.compass" /> 85 * </pre> 86 * </li> 87 * 88 * </ol> 89 */ 90 public class ManifestTestListAdapter extends TestListAdapter { 91 92 private static final String TEST_CATEGORY_META_DATA = "test_category"; 93 94 private static final String TEST_PARENT_META_DATA = "test_parent"; 95 96 private static final String TEST_REQUIRED_FEATURES_META_DATA = "test_required_features"; 97 98 private static final String TEST_EXCLUDED_FEATURES_META_DATA = "test_excluded_features"; 99 100 private static final String TEST_APPLICABLE_FEATURES_META_DATA = "test_applicable_features"; 101 102 private final HashSet<String> mDisabledTests; 103 104 private Context mContext; 105 106 private String mTestParent; 107 108 public ManifestTestListAdapter(Context context, String testParent, String[] disabledTestArray) { 109 super(context); 110 mContext = context; 111 mTestParent = testParent; 112 mDisabledTests = new HashSet<>(disabledTestArray.length); 113 for (int i = 0; i < disabledTestArray.length; i++) { 114 mDisabledTests.add(disabledTestArray[i]); 115 } 116 } 117 118 public ManifestTestListAdapter(Context context, String testParent) { 119 this(context, testParent, context.getResources().getStringArray(R.array.disabled_tests)); 120 } 121 122 @Override 123 protected List<TestListItem> getRows() { 124 125 /* 126 * 1. Get all the tests belonging to the test parent. 127 * 2. Get all the tests keyed by their category. 128 * 3. Flatten the tests and categories into one giant list for the list view. 129 */ 130 131 List<ResolveInfo> infos = getResolveInfosForParent(); 132 Map<String, List<TestListItem>> testsByCategory = getTestsByCategory(infos); 133 134 List<String> testCategories = new ArrayList<String>(testsByCategory.keySet()); 135 Collections.sort(testCategories); 136 137 List<TestListItem> allRows = new ArrayList<TestListItem>(); 138 for (String testCategory : testCategories) { 139 List<TestListItem> tests = filterTests(testsByCategory.get(testCategory)); 140 if (!tests.isEmpty()) { 141 allRows.add(TestListItem.newCategory(testCategory)); 142 Collections.sort(tests, new Comparator<TestListItem>() { 143 @Override 144 public int compare(TestListItem item, TestListItem otherItem) { 145 return item.title.compareTo(otherItem.title); 146 } 147 }); 148 allRows.addAll(tests); 149 } 150 } 151 return allRows; 152 } 153 154 List<ResolveInfo> getResolveInfosForParent() { 155 Intent mainIntent = new Intent(Intent.ACTION_MAIN); 156 mainIntent.addCategory(CATEGORY_MANUAL_TEST); 157 mainIntent.setPackage(mContext.getPackageName()); 158 159 PackageManager packageManager = mContext.getPackageManager(); 160 List<ResolveInfo> list = packageManager.queryIntentActivities(mainIntent, 161 PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA); 162 int size = list.size(); 163 164 List<ResolveInfo> matchingList = new ArrayList<ResolveInfo>(); 165 for (int i = 0; i < size; i++) { 166 ResolveInfo info = list.get(i); 167 String parent = getTestParent(info.activityInfo.metaData); 168 if ((mTestParent == null && parent == null) 169 || (mTestParent != null && mTestParent.equals(parent))) { 170 matchingList.add(info); 171 } 172 } 173 return matchingList; 174 } 175 176 Map<String, List<TestListItem>> getTestsByCategory(List<ResolveInfo> list) { 177 Map<String, List<TestListItem>> testsByCategory = 178 new HashMap<String, List<TestListItem>>(); 179 180 int size = list.size(); 181 for (int i = 0; i < size; i++) { 182 ResolveInfo info = list.get(i); 183 if (info.activityInfo == null || mDisabledTests.contains(info.activityInfo.name)) { 184 Log.w("CtsVerifier", "ignoring disabled test: " + info.activityInfo.name); 185 continue; 186 } 187 String title = getTitle(mContext, info.activityInfo); 188 String testName = info.activityInfo.name; 189 Intent intent = getActivityIntent(info.activityInfo); 190 String[] requiredFeatures = getRequiredFeatures(info.activityInfo.metaData); 191 String[] excludedFeatures = getExcludedFeatures(info.activityInfo.metaData); 192 String[] applicableFeatures = getApplicableFeatures(info.activityInfo.metaData); 193 TestListItem item = TestListItem.newTest(title, testName, intent, requiredFeatures, 194 excludedFeatures, applicableFeatures); 195 196 String testCategory = getTestCategory(mContext, info.activityInfo.metaData); 197 addTestToCategory(testsByCategory, testCategory, item); 198 } 199 200 return testsByCategory; 201 } 202 203 static String getTestCategory(Context context, Bundle metaData) { 204 String testCategory = null; 205 if (metaData != null) { 206 testCategory = metaData.getString(TEST_CATEGORY_META_DATA); 207 } 208 if (testCategory != null) { 209 return testCategory; 210 } else { 211 return context.getString(R.string.test_category_other); 212 } 213 } 214 215 static String getTestParent(Bundle metaData) { 216 return metaData != null ? metaData.getString(TEST_PARENT_META_DATA) : null; 217 } 218 219 static String[] getRequiredFeatures(Bundle metaData) { 220 if (metaData == null) { 221 return null; 222 } else { 223 String value = metaData.getString(TEST_REQUIRED_FEATURES_META_DATA); 224 if (value == null) { 225 return null; 226 } else { 227 return value.split(":"); 228 } 229 } 230 } 231 232 static String[] getExcludedFeatures(Bundle metaData) { 233 if (metaData == null) { 234 return null; 235 } else { 236 String value = metaData.getString(TEST_EXCLUDED_FEATURES_META_DATA); 237 if (value == null) { 238 return null; 239 } else { 240 return value.split(":"); 241 } 242 } 243 } 244 245 static String[] getApplicableFeatures(Bundle metaData) { 246 if (metaData == null) { 247 return null; 248 } else { 249 String value = metaData.getString(TEST_APPLICABLE_FEATURES_META_DATA); 250 if (value == null) { 251 return null; 252 } else { 253 return value.split(":"); 254 } 255 } 256 } 257 258 static String getTitle(Context context, ActivityInfo activityInfo) { 259 if (activityInfo.labelRes != 0) { 260 return context.getString(activityInfo.labelRes); 261 } else { 262 return activityInfo.name; 263 } 264 } 265 266 static Intent getActivityIntent(ActivityInfo activityInfo) { 267 Intent intent = new Intent(); 268 intent.setClassName(activityInfo.packageName, activityInfo.name); 269 return intent; 270 } 271 272 static void addTestToCategory(Map<String, List<TestListItem>> testsByCategory, 273 String testCategory, TestListItem item) { 274 List<TestListItem> tests; 275 if (testsByCategory.containsKey(testCategory)) { 276 tests = testsByCategory.get(testCategory); 277 } else { 278 tests = new ArrayList<TestListItem>(); 279 } 280 testsByCategory.put(testCategory, tests); 281 tests.add(item); 282 } 283 284 private boolean hasAnyFeature(String[] features) { 285 if (features != null) { 286 PackageManager packageManager = mContext.getPackageManager(); 287 for (String feature : features) { 288 if (packageManager.hasSystemFeature(feature)) { 289 return true; 290 } 291 } 292 } 293 return false; 294 } 295 296 private boolean hasAllFeatures(String[] features) { 297 if (features != null) { 298 PackageManager packageManager = mContext.getPackageManager(); 299 for (String feature : features) { 300 if (!packageManager.hasSystemFeature(feature)) { 301 return false; 302 } 303 } 304 } 305 return true; 306 } 307 308 List<TestListItem> filterTests(List<TestListItem> tests) { 309 List<TestListItem> filteredTests = new ArrayList<TestListItem>(); 310 for (TestListItem test : tests) { 311 if (!hasAnyFeature(test.excludedFeatures) && hasAllFeatures(test.requiredFeatures)) { 312 if (test.applicableFeatures == null || hasAnyFeature(test.applicableFeatures)) { 313 filteredTests.add(test); 314 } 315 } 316 } 317 return filteredTests; 318 } 319 } 320