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