Home | History | Annotate | Download | only in verifier
      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