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.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