Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2008 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 package com.android.cts;
     17 
     18 import java.io.BufferedOutputStream;
     19 import java.io.File;
     20 import java.io.FileNotFoundException;
     21 import java.io.FileOutputStream;
     22 import java.io.IOException;
     23 import java.io.InputStream;
     24 import java.io.OutputStream;
     25 import java.security.NoSuchAlgorithmException;
     26 import java.util.ArrayList;
     27 import java.util.Arrays;
     28 import java.util.Collection;
     29 import java.util.Enumeration;
     30 import java.util.HashMap;
     31 import java.util.List;
     32 import java.util.zip.ZipEntry;
     33 import java.util.zip.ZipFile;
     34 
     35 import javax.xml.parsers.DocumentBuilderFactory;
     36 import javax.xml.parsers.ParserConfigurationException;
     37 
     38 import org.w3c.dom.Document;
     39 import org.w3c.dom.Node;
     40 import org.w3c.dom.NodeList;
     41 import org.xml.sax.SAXException;
     42 
     43 /**
     44  * Holds CTS host configuration information, such as:
     45  * <ul>
     46  *    <li> test case repository
     47  *    <li> test plan repository
     48  *    <li> test result repository
     49  * </ul>
     50  */
     51 public class HostConfig extends XMLResourceHandler {
     52     public static boolean DEBUG = false;
     53 
     54     public static final String ALL = "all";
     55 
     56     static final String SIGNATURE_TEST_PACKAGE_NAME = "SignatureTest";
     57     static final String DEFAULT_HOST_CONFIG_FILE_NAME = "host_config.xml";
     58     static final String FILE_SUFFIX_XML = ".xml";
     59     static final String FILE_SUFFIX_APK = ".apk";
     60     static final String FILE_SUFFIX_ZIP = ".zip";
     61     static final String FILE_SUFFIX_JAR = ".jar";
     62     static final String[] CTS_RESULT_RESOURCES = {"cts_result.xsl", "cts_result.css",
     63                                                   "logo.gif", "newrule-green.png"};
     64 
     65     private String mConfigRoot;
     66     private CaseRepository mCaseRepos;
     67     private ResultRepository mResultRepos;
     68     private PlanRepository mPlanRepos;
     69 
     70     // key: app package name
     71     // value: TestPackage
     72     private HashMap<String, TestPackage> mTestPackageMap;
     73 
     74     enum Ints {
     75         // Number of tests executed between reboots. A value <= 0 disables reboots.
     76         maxTestCount (200),
     77         // Max size [tests] for a package to be run in batch mode
     78         maxTestsInBatchMode (0),
     79         // Max time [ms] between test status updates for both individual and batch mode.
     80         testStatusTimeoutMs (5 * 60 * 1000),
     81         // Max time [ms] from start of package in batch mode and the first test status update.
     82         batchStartTimeoutMs (30 * 60 * 1000),
     83         // Max time [ms] from start of test in individual mode to the first test status update.
     84         individualStartTimeoutMs (5 * 60 * 1000),
     85         // Timeout [ms] for the signature check
     86         signatureTestTimeoutMs (10 * 60 * 1000),
     87         // Timeout [ms] for package installations
     88         packageInstallTimeoutMs (2 * 60 * 1000),
     89         // Time to wait [ms] after a package installation or removal
     90         postInstallWaitMs (30 * 1000);
     91 
     92         private int value;
     93 
     94         Ints(int value) {
     95             this.value = value;
     96         }
     97 
     98         int value() {
     99             return value;
    100         }
    101 
    102         void setValue(int value) {
    103             this.value = value;
    104         }
    105     }
    106 
    107     private final static HostConfig sInstance = new HostConfig();
    108 
    109     private HostConfig() {
    110         mTestPackageMap = new HashMap<String, TestPackage>();
    111     }
    112 
    113     public static HostConfig getInstance() {
    114         return sInstance;
    115     }
    116 
    117     /**
    118      * Returns the max number of tests to run between reboots. A value of 0 or smaller indicates
    119      * that reboots should not be used.
    120      */
    121     public static int getMaxTestCount() {
    122         return Ints.maxTestCount.value();
    123     }
    124 
    125     /**
    126      * Load configuration.
    127      *
    128      * @param configPath The configuration path.
    129      * @return If succeed in loading, return true; else, return false.
    130      */
    131     public boolean load(String configPath) throws SAXException, IOException,
    132             ParserConfigurationException {
    133 
    134         String fileName = null;
    135         String[] subDirs = configPath.split("\\" + File.separator);
    136         for (String d : subDirs) {
    137             if (d.contains(FILE_SUFFIX_XML)) {
    138                 fileName = d;
    139             }
    140         }
    141 
    142         String configFile = null;
    143         if (fileName == null) {
    144             //remove the possible trailing "/" of the path
    145             if (File.separatorChar == configPath.charAt(configPath.length() - 1)) {
    146                 configPath = configPath.substring(0, configPath.length() - 1);
    147             }
    148             mConfigRoot = configPath;
    149             fileName = DEFAULT_HOST_CONFIG_FILE_NAME;
    150         } else {
    151             mConfigRoot = configPath.substring(0, configPath.length() - fileName.length() - 1);
    152         }
    153         configFile = mConfigRoot + File.separator + fileName;
    154 
    155         Document doc = DocumentBuilderFactory.newInstance()
    156                 .newDocumentBuilder().parse(new File(configFile));
    157 
    158         String repositoryRoot = getStringAttributeValue(doc
    159                 .getElementsByTagName("Repository").item(0), "root");
    160         if ((null == repositoryRoot) || (repositoryRoot.length() == 0)) {
    161             repositoryRoot = mConfigRoot;
    162         }
    163 
    164         String caseCfg = getStringAttributeValue(doc, "TestCase", "path", fileName);
    165         String planCfg = getStringAttributeValue(doc, "TestPlan", "path", fileName);
    166         String resCfg = getStringAttributeValue(doc, "TestResult", "path", fileName);
    167         if ((caseCfg == null) || (planCfg == null) || (resCfg == null)) {
    168             return false;
    169         }
    170 
    171         getConfigValues(doc);
    172 
    173         String caseRoot = repositoryRoot + File.separator + caseCfg;
    174         String planRoot = repositoryRoot + File.separator + planCfg;
    175         String resRoot = repositoryRoot + File.separator + resCfg;
    176 
    177         boolean validCase = true;
    178         if (!validateDirectory(caseRoot)) {
    179             validCase = new File(caseRoot).mkdirs();
    180         }
    181         boolean validRes = true;
    182         if (!validateDirectory(resRoot)) {
    183             validRes = new File(resRoot).mkdirs();
    184         }
    185         if (validRes) {
    186             extractResultResources(resRoot);
    187         }
    188         boolean validPlan = true;
    189         if (!validateDirectory(planRoot)) {
    190             validPlan = new File(planRoot).mkdirs();
    191         }
    192 
    193         mCaseRepos = new CaseRepository(caseRoot);
    194         mResultRepos = new ResultRepository(resRoot);
    195         mPlanRepos = new PlanRepository(planRoot);
    196 
    197         return validCase && validRes && validPlan;
    198     }
    199 
    200     /**
    201      * Extract the result resources into the specified directory.
    202      *
    203      * @param resRoot the directory to extract the resources into.
    204      */
    205     public void extractResultResources(String resRoot) {
    206         for (String res: CTS_RESULT_RESOURCES) {
    207             extractResource(res, resRoot);
    208         }
    209     }
    210 
    211     /**
    212      * Get the test packages.
    213      *
    214      * @return The test packages.
    215      */
    216     public Collection<TestPackage> getTestPackages() {
    217         return mTestPackageMap.values();
    218     }
    219 
    220     /**
    221      * Get the test package by the JAVA package name of the test package.
    222      *
    223      * @param packageName The JAVA package name.
    224      * @return The test package.
    225      */
    226     public TestPackage getTestPackage(final String packageName) {
    227         return mTestPackageMap.get(packageName);
    228     }
    229 
    230     /**
    231      * Load repositories.
    232      */
    233     public void loadRepositories() throws NoSuchAlgorithmException {
    234         loadTestPackages();
    235         loadTestResults();
    236     }
    237 
    238     /**
    239      * Load test results to create session accordingly.
    240      */
    241     private void loadTestResults() {
    242         getResultRepository().loadTestResults();
    243     }
    244 
    245     /**
    246      * Load all of the test packages.
    247      */
    248     public void loadTestPackages() throws NoSuchAlgorithmException {
    249         if (mTestPackageMap.size() == 0) {
    250             mCaseRepos.loadTestPackages();
    251         }
    252     }
    253 
    254     /**
    255      * Remove all of the test packages.
    256      */
    257     public void removeTestPacakges() {
    258         mTestPackageMap.clear();
    259     }
    260 
    261     /**
    262      * Get the package binary name.
    263      *
    264      * @param appPackageName The JAVA package name.
    265      * @return The binary name of the package.
    266      */
    267     public String getPackageBinaryName(String appPackageName) {
    268 
    269         for (TestPackage pkg : mTestPackageMap.values()) {
    270             if (appPackageName.equals(pkg.getAppPackageName())) {
    271                 return pkg.getAppBinaryName();
    272             }
    273         }
    274 
    275         return null;
    276     }
    277 
    278     /**
    279      * Get the root directory of configuration.
    280      *
    281      * @return The root directory of configuration.
    282      */
    283     public String getConfigRoot() {
    284         return mConfigRoot;
    285     }
    286 
    287     /**
    288      * Get string attribute value.
    289      *
    290      * @param doc The document.
    291      * @param tagName The tag name.
    292      * @param attrName The attribute name.
    293      * @param fileName The file name.
    294      * @return The attribute value.
    295      */
    296     private String getStringAttributeValue(final Document doc,
    297             final String tagName, final String attrName, final String fileName) {
    298 
    299         String cfgStr = null;
    300         try {
    301             cfgStr = getStringAttributeValue(doc
    302                     .getElementsByTagName(tagName).item(0), attrName);
    303             if ((null == cfgStr) || (cfgStr.length() == 0)) {
    304                 Log.e("Configure error (in " + fileName
    305                         + "), pls make sure <" + tagName + ">'s attribute <"
    306                         + attrName + ">'s value is correctly set.", null);
    307                 return null;
    308             }
    309         } catch (Exception e) {
    310             Log.e("Configure error (in " + fileName
    311                     + "), pls make sure <" + tagName
    312                     + ">'s value is correctly set.", null);
    313             return null;
    314         }
    315 
    316         return cfgStr;
    317     }
    318 
    319     /**
    320      * Load configuration values from config file.
    321      *
    322      * @param doc The document from which to load the values.
    323      */
    324     private void getConfigValues(final Document doc) {
    325         NodeList intValues = doc.getElementsByTagName("IntValue");
    326         for (int i = 0; i < intValues.getLength(); i++) {
    327             Node n = intValues.item(i);
    328             String name = getStringAttributeValue(n, "name");
    329             String value = getStringAttributeValue(n, "value");
    330             try {
    331                 Integer v = Integer.parseInt(value);
    332                 Ints.valueOf(name).setValue(v);
    333             } catch (NumberFormatException e) {
    334                 Log.e("Configuration error. Illegal value for " + name, e);
    335             } catch (IllegalArgumentException e) {
    336                 Log.e("Unknown configuration value " + name, e);
    337             }
    338         }
    339     }
    340 
    341     /**
    342      * Validate the directory.
    343      *
    344      * @param path The path to be validated.
    345      * @return If valid directory, return true; else, return false.
    346      */
    347     private boolean validateDirectory(final String path) {
    348 
    349         File pathFile = new File(path);
    350         if ((null == pathFile) || (pathFile.exists() == false)
    351                 || (pathFile.isDirectory() == false)) {
    352             return false;
    353         }
    354 
    355         return true;
    356     }
    357 
    358     /**
    359      * Extract a resource into the given destination directory. This is used
    360      * for resource files such as images and CSS.
    361      *
    362      * @param name The name of the resource.
    363      * @param dest The directory the resource should be extracted to.
    364      * @return true, if successful, or the file already existed.
    365      */
    366     private boolean extractResource(String name, String dest) {
    367         File file = new File(dest, name);
    368         if (!file.exists()) {
    369             // do not extract again if the file is already there
    370             InputStream in = getClass().getResourceAsStream(File.separator + name);
    371             if (in != null) {
    372                 try {
    373                     FileOutputStream fout = new FileOutputStream(file);
    374                     byte[] data = new byte[512];
    375                     int len = in.read(data);
    376                     while (len > 0) {
    377                         fout.write(data, 0, len);
    378                         len = in.read(data);
    379                     }
    380                     fout.flush();
    381                     fout.close();
    382                     in.close();
    383                 } catch (FileNotFoundException e) {
    384                     return false;
    385                 } catch (IOException e) {
    386                     return false;
    387                 }
    388             }
    389         }
    390         return true;
    391     }
    392 
    393     /**
    394      * Get the case repository.
    395      *
    396      * @return The case repository.
    397      */
    398     public CaseRepository getCaseRepository() {
    399         return mCaseRepos;
    400     }
    401 
    402     /**
    403      * Get the plan repository.
    404      *
    405      * @return The plan repository.
    406      */
    407     public PlanRepository getPlanRepository() {
    408         return mPlanRepos;
    409     }
    410 
    411     /**
    412      * Get the result repository.
    413      *
    414      * @return The result repository.
    415      */
    416     public ResultRepository getResultRepository() {
    417         return mResultRepos;
    418     }
    419 
    420     /**
    421      * Storing the root information of some repository.
    422      *
    423      */
    424     class Repository {
    425         protected String mRoot;
    426 
    427         Repository(String root) {
    428             mRoot = root;
    429         }
    430 
    431         /**
    432          * Get the root of the repository.
    433          *
    434          * @return The root of the repository.
    435          */
    436         public String getRoot() {
    437             return mRoot;
    438         }
    439 
    440         /**
    441          * Check if the specified file is a valid XML file.
    442          *
    443          * @param f The file to be valid.
    444          * @return If valid XML file, return true; else, return false.
    445          */
    446         public boolean isValidXmlFile(File f) {
    447             if (f.getPath().endsWith(FILE_SUFFIX_XML)) {
    448                 return true;
    449             }
    450 
    451             return false;
    452         }
    453     }
    454 
    455     /**
    456      * Storing the information of result repository.
    457      */
    458     class ResultRepository extends Repository {
    459 
    460         ResultRepository(String root) {
    461             super(root);
    462         }
    463 
    464         /**
    465          * Load test results to create session accordingly.
    466          */
    467         public void loadTestResults() {
    468 
    469             for (File f : new File(mRoot).listFiles()) {
    470                 if (f.isDirectory()) {
    471                     String pathName = mRoot + File.separator + f.getName()
    472                                 + File.separator + TestSessionLog.CTS_RESULT_FILE_NAME;
    473                     if (HostUtils.isFileExist(pathName)) {
    474                         try {
    475                             TestSessionLog log =
    476                                 TestSessionLogBuilder.getInstance().build(pathName);
    477                             TestSession ts = TestSessionBuilder.getInstance().build(log);
    478                             if (ts != null) {
    479                                 TestHost.getInstance().addSession(ts);
    480                             }
    481                         } catch (Exception e) {
    482                             Log.e("Error importing existing result from " + pathName, e);
    483                         }
    484                     }
    485                 }
    486             }
    487         }
    488      }
    489 
    490     /**
    491      * Storing the information of case repository.
    492      */
    493     class CaseRepository extends Repository {
    494         CaseRepository(String root) {
    495             super(root);
    496         }
    497 
    498         /**
    499          * Get package names.
    500          *
    501          * @return The JAVA package names.
    502          */
    503         public ArrayList<String> getPackageNames() {
    504             ArrayList<String> packageNames = new ArrayList<String>();
    505             for (TestPackage pkg : mTestPackageMap.values()) {
    506                 String binaryName = pkg.getAppBinaryName();
    507                 if (binaryName.equals(SIGNATURE_TEST_PACKAGE_NAME)) {
    508                     packageNames.add(0, binaryName);
    509                 } else {
    510                     packageNames.add(pkg.getAppPackageName());
    511                 }
    512             }
    513 
    514             return packageNames;
    515         }
    516 
    517         /**
    518          * Get package binary names.
    519          *
    520          * @return The package binary names.
    521          */
    522         public ArrayList<String> getPackageBinaryNames() {
    523             ArrayList<String> pkgBinaryNames = new ArrayList<String>();
    524             for (TestPackage pkg : mTestPackageMap.values()) {
    525                 String pkgBinaryName = pkg.getAppBinaryName();
    526                 if (pkgBinaryName.equals(SIGNATURE_TEST_PACKAGE_NAME)) {
    527                     pkgBinaryNames.add(0, pkgBinaryName);
    528                 } else {
    529                     pkgBinaryNames.add(pkg.getAppBinaryName());
    530                 }
    531             }
    532 
    533             return pkgBinaryNames;
    534         }
    535 
    536         /**
    537          * Load package XML file names.
    538          *
    539          * @return The package XML file names.
    540          */
    541         public List<String> loadPackageXmlFileNames() {
    542             ArrayList<String> packageXmlFileNames = new ArrayList<String>();
    543 
    544             for (File f : new File(mRoot).listFiles()) {
    545                 if (isValidXmlFile(f)) {
    546                     String fileName = f.getName();
    547                     String name = fileName.substring(0, fileName.lastIndexOf("."));
    548                     packageXmlFileNames.add(name);
    549                 }
    550             }
    551 
    552             return packageXmlFileNames;
    553         }
    554 
    555         /**
    556          * Load test packages.
    557          */
    558         public void loadTestPackages() throws NoSuchAlgorithmException {
    559             List<String> pkgXmlFileNameList = loadPackageXmlFileNames();
    560             for (String pkgXmlFileName : pkgXmlFileNameList) {
    561                 String xmlPath = getRoot() + File.separator
    562                         + pkgXmlFileName + FILE_SUFFIX_XML;
    563                 TestPackage pkg = loadPackage(xmlPath);
    564                 if (isValidPackage(pkg)) {
    565                     mTestPackageMap.put(pkg.getAppPackageName(), pkg);
    566                 }
    567             }
    568         }
    569 
    570         /**
    571          * Get package binary name.
    572          *
    573          * @param packagePath The package path.
    574          * @return The package binary name.
    575          */
    576         private String getPackageBinaryName(String packagePath) {
    577             return packagePath.substring(packagePath.lastIndexOf(File.separator) + 1,
    578                     packagePath.lastIndexOf("."));
    579         }
    580 
    581         /**
    582          * Check if the specified package is a valid package.
    583          *
    584          * @param pkg The specified package to be checked.
    585          * @return If valid package, return true; else, return false.
    586          */
    587         private boolean isValidPackage(TestPackage pkg) {
    588             if (pkg == null) {
    589                 return false;
    590             }
    591 
    592             String pkgFileName = pkg.getAppBinaryName();
    593             String apkFilePath = mRoot + File.separator + pkgFileName + FILE_SUFFIX_APK;
    594             String xmlFilePath = mRoot + File.separator + pkgFileName + FILE_SUFFIX_XML;
    595             File xmlFile = new File(xmlFilePath);
    596             if (pkg.isHostSideOnly()) {
    597                 if (xmlFile.exists() && xmlFile.isFile()) {
    598                     return true;
    599                 }
    600             } else {
    601                 File apkFile = new File(apkFilePath);
    602                 if (xmlFile.exists() && xmlFile.isFile()
    603                         && apkFile.exists() && apkFile.isFile()) {
    604                     return true;
    605                 }
    606             }
    607 
    608             return false;
    609         }
    610 
    611         /**
    612          * Add package to case repository.
    613          *
    614          * @param packagePath The package to be added.
    615          * @return If valid package and succeed in add it, return true; else, return false.
    616          */
    617         public boolean addPackage(String packagePath) throws FileNotFoundException,
    618                 IOException, NoSuchAlgorithmException {
    619             ZipFile zipFile = new ZipFile(packagePath);
    620             Enumeration<? extends ZipEntry> entries = zipFile.entries();
    621 
    622             ArrayList<String> filePathList = new ArrayList<String>();
    623             String xmlFilePath = null;
    624             while (entries.hasMoreElements()) {
    625                 ZipEntry entry = entries.nextElement();
    626 
    627                 String name = entry.getName();
    628                 if (name.endsWith(FILE_SUFFIX_APK)
    629                         || name.endsWith(FILE_SUFFIX_XML)
    630                         || name.endsWith(FILE_SUFFIX_JAR)) {
    631                     int index = name.lastIndexOf(File.separator);
    632                     String fileName = name;
    633                     if (index != -1) {
    634                         fileName = name.substring(index + 1);
    635                     }
    636                     String filePath = mRoot + File.separator + fileName;
    637                     writeToFile(zipFile.getInputStream(entry), filePath);
    638                     filePathList.add(filePath);
    639                     if (name.endsWith(FILE_SUFFIX_XML)) {
    640                         xmlFilePath = filePath;
    641                     }
    642                 }
    643             }
    644 
    645             String packageName = getPackageBinaryName(packagePath);
    646             PackageZipFileValidator zipValidator = new PackageZipFileValidator();
    647             if (zipValidator.validate(filePathList, packageName, xmlFilePath) == false) {
    648                 for (String filePath : filePathList) {
    649                     deleteFile(filePath);
    650                 }
    651                 return false;
    652             }
    653 
    654             TestPackage pkg = loadPackage(xmlFilePath);
    655             if (pkg != null) {
    656                 mTestPackageMap.put(pkg.getAppPackageName(), pkg);
    657             }
    658 
    659             return true;
    660         }
    661 
    662         /**
    663          * Load the package from the package description XML file.
    664          *
    665          * @param xmlFileName The package description XML file.
    666          * @return The TestPackage.
    667          */
    668         private TestPackage loadPackage(String xmlFileName) throws NoSuchAlgorithmException {
    669             if ((xmlFileName == null) || (xmlFileName.length() == 0)) {
    670                 return null;
    671             }
    672 
    673             File xmlFile = new File(xmlFileName);
    674             TestSessionBuilder sessionBuilder;
    675             TestPackage pkg = null;
    676             try {
    677                 sessionBuilder = TestSessionBuilder.getInstance();
    678                 pkg = sessionBuilder.loadPackage(xmlFile, null);
    679             } catch (ParserConfigurationException e) {
    680             } catch (SAXException e) {
    681             } catch (IOException e) {
    682             }
    683             return pkg;
    684         }
    685 
    686         /**
    687          * check if the packagePath is valid against the case repository
    688          *
    689          * @param packagePath the path to be checked
    690          * @return if the path isn't suffixed with .zip, return false;
    691          *         if the package name exists in case repository, return false;
    692          *         for other conditions, return true;
    693          */
    694         public boolean isValidPackageName(String packagePath) {
    695             if (!packagePath.endsWith(FILE_SUFFIX_ZIP)) {
    696                 Log.e("Package error: package name " + packagePath + " is not a zip file.", null);
    697                 return false;
    698             }
    699 
    700             String fileName = packagePath.substring(packagePath.lastIndexOf(File.separator) + 1,
    701                        packagePath.length() - FILE_SUFFIX_ZIP.length());
    702 
    703             String path = mRoot + File.separator + fileName;
    704             if (HostUtils.isFileExist(path + FILE_SUFFIX_APK)
    705                     || HostUtils.isFileExist(path + FILE_SUFFIX_XML)) {
    706                 Log.e("Package error: package name " + fileName + " exists already.", null);
    707                 return false;
    708             }
    709 
    710             return true;
    711         }
    712 
    713         /**
    714          * Validate zipped package file against package logic
    715          */
    716         class PackageZipFileValidator {
    717             /**
    718              * validate the package content to see if it contains enough data
    719              **
    720              * @param filePathList The file path list contained in the package zip file.
    721              * @param packageName The package name.
    722              * @param xmlFilePath The description XML file path.
    723              * @return If valid, return true; else, return false.
    724              */
    725             public boolean validate(ArrayList<String> filePathList, String packageName,
    726                     String xmlFilePath) throws NoSuchAlgorithmException {
    727                 if (xmlFilePath == null) {
    728                     Log.e("Package error: package doesn't contain XML file: "
    729                             + packageName + FILE_SUFFIX_XML, null);
    730                     return false;
    731                 } else {
    732                     TestPackage pkg = loadPackage(xmlFilePath);
    733                     if (pkg == null) {
    734                         Log.e("Package error: the description XML file contained in : "
    735                                 + packageName + FILE_SUFFIX_APK + " is invalid.", null);
    736                         return false;
    737                     } else {
    738                         if (!validateTargetApk(filePathList, pkg.getTargetBinaryName())) {
    739                             return false;
    740                         }
    741 
    742                         if (!validateHostControllerJar(filePathList, pkg.getJarPath())) {
    743                             return false;
    744                         }
    745 
    746                         String apkFilePath = mRoot + File.separator
    747                                 + packageName + FILE_SUFFIX_APK;
    748                         if (!filePathList.contains(apkFilePath)) {
    749                             Log.e("Package error: package doesn't contain APK file: "
    750                                             + packageName + FILE_SUFFIX_APK, null);
    751                             return false;
    752                         }
    753                     }
    754                 }
    755 
    756                 return true;
    757             }
    758 
    759             /**
    760              * Validate host controller jar file described in the package description XML file.
    761              *
    762              * @param filePathList  The files contained in the zipped package file.
    763              * @param hostControllerJarPath The host controller jar file path.
    764              * @return If the host controller jar file contained in the zipped package,
    765              *         return true; else, return false.
    766              */
    767             private boolean validateHostControllerJar(ArrayList<String> filePathList,
    768                     String hostControllerJarPath) {
    769                 if ((hostControllerJarPath != null) && (hostControllerJarPath.length() != 0)) {
    770                     String targetFilePath =
    771                         mRoot + File.separator + hostControllerJarPath + FILE_SUFFIX_JAR;
    772                     if (filePathList.contains(targetFilePath)) {
    773                         return true;
    774                     }
    775                 } else {
    776                     return true;
    777                 }
    778 
    779                 //String jarFileName = getPackageName(hostControllerJarPath);
    780                 Log.e("Package error: host controler jar file "
    781                         + hostControllerJarPath + FILE_SUFFIX_JAR
    782                         + " is not contained in the package zip file.", null);
    783                 return false;
    784             }
    785 
    786             /**
    787              * Validate target APK file described in the package description XML file.
    788              *
    789              * @param filePathList The files contained in the zipped package file.
    790              * @param targetName The target APK name.
    791              * @return If the target APK file contained in the zipped package file, return true;
    792              *         else, return false.
    793              */
    794             private boolean validateTargetApk(ArrayList<String> filePathList, String targetName) {
    795                 if ((targetName != null) && (targetName.length() != 0)) {
    796                     String targetFileName = mRoot + File.separator + targetName + FILE_SUFFIX_APK;
    797                     if (filePathList.contains(targetFileName)) {
    798                         return true;
    799                     }
    800                 } else {
    801                     return true;
    802                 }
    803 
    804                 Log.e("Package error: target file " + targetName + FILE_SUFFIX_APK
    805                         + " is not contained in the package zip file.", null);
    806                 return false;
    807             }
    808         }
    809 
    810         /**
    811          * Write the input stream to file.
    812          *
    813          * @param in The input stream.
    814          * @param path The file to write to.
    815          */
    816         private void writeToFile(InputStream in, String path) throws IOException {
    817             OutputStream out = new BufferedOutputStream(new FileOutputStream(path));
    818             byte[] buffer = new byte[1024];
    819             int len;
    820 
    821             while ((len = in.read(buffer)) >= 0) {
    822                 out.write(buffer, 0, len);
    823             }
    824 
    825             in.close();
    826             out.close();
    827         }
    828 
    829         /**
    830          * Remove packages from case repository.
    831          *
    832          * @param packageName Package to be removed.
    833          */
    834         public void removePackages(String packageName) {
    835             if ((packageName == null) || (packageName.length() == 0)) {
    836                 return;
    837             }
    838 
    839             if (packageName.equals(ALL)) {
    840                 ArrayList<String> packageNames = getCaseRepository().getPackageNames();
    841                 for (String pkgName : packageNames) {
    842                     removePackage(pkgName);
    843                 }
    844             } else {
    845                 if (!getPackageNames().contains(packageName)) {
    846                     Log.e("Package " + packageName + " doesn't exist in repository!", null);
    847                     return;
    848                 }
    849                 removePackage(packageName);
    850             }
    851         }
    852 
    853         /**
    854          * Remove the specified package.
    855          *
    856          * @param packageName The package name.
    857          */
    858         private void removePackage(String packageName) {
    859             TestPackage pkg = getTestPackage(packageName);
    860             if (pkg != null) {
    861                 ArrayList<String> targetBinaryNames = getTargetBinaryNames();
    862                 String targetBinaryName = pkg.getTargetBinaryName();
    863                 if ((targetBinaryName != null) && (targetBinaryName.length() != 0)
    864                         && (getReferenceCount(targetBinaryNames, targetBinaryName) == 1)) {
    865                     String targetBinaryFileName = mRoot + File.separator + targetBinaryName
    866                             + FILE_SUFFIX_APK;
    867                     deleteFile(targetBinaryFileName);
    868                 }
    869 
    870                 ArrayList<String> hostControllers = getHostControllers();
    871                 String hostControllerPath = pkg.getJarPath();
    872                 if ((hostControllerPath != null) && (hostControllerPath.length() != 0)
    873                         && (getReferenceCount(hostControllers, hostControllerPath) == 1)) {
    874                     String jarFilePath = mRoot + File.separator
    875                             + hostControllerPath + FILE_SUFFIX_JAR;
    876                     deleteFile(jarFilePath);
    877                 }
    878             }
    879 
    880             String packageBinaryName = pkg.getAppBinaryName();
    881             mTestPackageMap.remove(pkg.getAppPackageName());
    882 
    883             String apkPath = mRoot + File.separator + packageBinaryName + FILE_SUFFIX_APK;
    884             String xmlPath = mRoot + File.separator + packageBinaryName + FILE_SUFFIX_XML;
    885             deleteFile(apkPath);
    886             deleteFile(xmlPath);
    887         }
    888 
    889         /**
    890          * Get the reference count of the specific value against the value list.
    891          *
    892          * @param list The value list to be checked against.
    893          * @param value The value to be checked.
    894          * @return The reference count.
    895          */
    896         private int getReferenceCount(ArrayList<String> list, String value) {
    897             if ((list == null) || (list.size() == 0) || (value == null)) {
    898                 return 0;
    899             }
    900 
    901             int count = 0;
    902             for (String str : list) {
    903                 if (value.equals(str)) {
    904                     count ++;
    905                 }
    906             }
    907 
    908             return count;
    909         }
    910 
    911         /**
    912          * Get the target binary names contained with the test package description XML files.
    913          *
    914          * @return The target binary names.
    915          */
    916         private ArrayList<String> getTargetBinaryNames() {
    917             ArrayList<String> targetBinaryNames = new ArrayList<String>();
    918             for (TestPackage pkg : mTestPackageMap.values()) {
    919                 targetBinaryNames.add(pkg.getTargetBinaryName());
    920             }
    921             return targetBinaryNames;
    922         }
    923 
    924         /**
    925          * Get the host controllers contained with the test package description XML files.
    926          *
    927          * @return The host controllers.
    928          */
    929         private ArrayList<String> getHostControllers() {
    930             ArrayList<String> hostControllers = new ArrayList<String>();
    931             for (TestPackage pkg : mTestPackageMap.values()) {
    932                 hostControllers.add(pkg.getJarPath());
    933             }
    934             return hostControllers;
    935         }
    936 
    937         /**
    938          * Delete the specific file.
    939          *
    940          * @param filepath The file to be deleted.
    941          */
    942         private void deleteFile(String filepath) {
    943             File file = new File(filepath);
    944             if (file.exists() && file.isFile()) {
    945                 file.delete();
    946             }
    947         }
    948 
    949         /**
    950          * Get package's APK file path via the package name.
    951          *
    952          * @param packageName The package name.
    953          * @return The package's APK file path.
    954          */
    955         public String getApkPath(String packageName) {
    956             return mRoot + File.separator + packageName + FILE_SUFFIX_APK;
    957         }
    958 
    959         /**
    960          * Get package's XML file path via the package name.
    961          * @param packageName The package name.
    962          * @return The package's XML file path.
    963          */
    964         public String getXmlPath(String packageName) {
    965             return mRoot + File.separator + packageName + FILE_SUFFIX_XML;
    966         }
    967 
    968         /**
    969          * List available package and suite.
    970          *
    971          * @param expectPackage expected package name
    972          * @return list which contains available packages, suites and cases.
    973          */
    974         @SuppressWarnings("unchecked")
    975         public List<ArrayList<String>> listAvailablePackage(String expectPackage) {
    976             ArrayList<String> packageList = new ArrayList<String>();
    977             ArrayList<String> suiteList = new ArrayList<String>();
    978             ArrayList<String> caseList = new ArrayList<String>();
    979             ArrayList<String> testList = new ArrayList<String>();
    980 
    981             for (TestPackage testPackage : mTestPackageMap.values()) {
    982                 String appPackageName = testPackage.getAppPackageName();
    983                 if (expectPackage.equals(appPackageName)) {
    984                     testPackage.getTestSuiteNames(appPackageName, suiteList, caseList);
    985                 } else if (appPackageName.startsWith(expectPackage)) {
    986                     packageList.add(appPackageName);
    987                 } else {
    988                     if (expectPackage.indexOf(Test.METHOD_SEPARATOR) == -1) {
    989                         testPackage.getTestCaseNames(expectPackage, caseList, testList);
    990                     } else {
    991                         testPackage.getTestNames(expectPackage, testList);
    992                     }
    993                 }
    994             }
    995 
    996             return Arrays.asList(packageList, suiteList, caseList, testList);
    997         }
    998     }
    999 
   1000     /**
   1001      * Storing information of test plans.
   1002      *
   1003      */
   1004     class PlanRepository extends Repository {
   1005 
   1006         PlanRepository(String root) {
   1007             super(root);
   1008         }
   1009 
   1010         /**
   1011          * Get the path of the specified plan.
   1012          *
   1013          * @param name The plan name.
   1014          * @return The plan path.
   1015          */
   1016         public String getPlanPath(String name) {
   1017             if (mRoot == null) {
   1018                 Log.e("Repository uninitialized!", null);
   1019                 return null;
   1020             }
   1021 
   1022             return mRoot + File.separator + name + FILE_SUFFIX_XML;
   1023         }
   1024 
   1025         /**
   1026          * Get all plan names in the plan repository.
   1027          * @return Plan names.
   1028          */
   1029         public ArrayList<String> getAllPlanNames() {
   1030             ArrayList<String> plans = new ArrayList<String>();
   1031 
   1032             if (mRoot == null) {
   1033                 Log.e("Not specify repository, please check your cts config",
   1034                         null);
   1035                 return plans;
   1036             }
   1037 
   1038             File planRepository = new File(mRoot);
   1039             if (!planRepository.exists()) {
   1040                 Log.e("Plan Repository doesn't exist: " + mRoot, null);
   1041                 return null;
   1042             }
   1043 
   1044             for (File f : planRepository.listFiles()) {
   1045                 String name = f.getName();
   1046 
   1047                 if (name.endsWith(FILE_SUFFIX_XML)) {
   1048                     plans.add(name.substring(0, name.length() - FILE_SUFFIX_XML.length()));
   1049                 }
   1050             }
   1051 
   1052             return plans;
   1053         }
   1054     }
   1055 }
   1056