1 /* 2 * Copyright 2010 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.android.accessibility; 18 19 import org.xml.sax.InputSource; 20 import org.xml.sax.SAXException; 21 import org.xml.sax.XMLReader; 22 import org.xml.sax.helpers.XMLReaderFactory; 23 24 import java.io.*; 25 import java.util.ArrayList; 26 import java.util.List; 27 import java.util.logging.Logger; 28 29 /** 30 * An object that fetches all Android layout files and manages the testing of 31 * the files with the use of the AccessibilityValidationContentHandler. This 32 * object also reports on any errors encountered during the testing. 33 * 34 * @author dtseng@google.com (David Tseng) 35 */ 36 public class AccessibilityValidator { 37 /** The root path to scan for Android layout files. */ 38 private final File mRootFilePath; 39 /** Errors generated by thrown exceptions (and not by validation errors). */ 40 private final List<String> mGeneralErrors = new ArrayList<String>(); 41 /** A list of files we wish to have tested. */ 42 private List<InputSource> mLayoutFiles; 43 /** The total number of validation test errors across all files. */ 44 private int mTotalValidationErrors = 0; 45 /** The path to the Android sdk jar. */ 46 private final File mAndroidSdkPath; 47 48 /** The object that handles our logging. */ 49 private static final Logger sLogger = Logger.getLogger("android.accessibility"); 50 51 /** 52 * The entry point to this tool. 53 * 54 * @param <args> 55 * path on which to search for layout xml files that need 56 * validation 57 */ main(String[] args)58 public static void main(String[] args) { 59 sLogger.info("AccessibilityValidator"); 60 if (args.length == 2) { 61 sLogger.info("Validating classes using android jar for subclasses of ImageView"); 62 new AccessibilityValidator(args[0], args[1]).run(); 63 } else { 64 sLogger.info("Usage: java AccessibilityValidator <path> <Android jar path>"); 65 return; 66 } 67 } 68 69 /** 70 * Constructs an AccessibilityValidator object using the root path and the 71 * android jar path. 72 */ AccessibilityValidator(String rootPath, String androidSdkPath)73 public AccessibilityValidator(String rootPath, String androidSdkPath) { 74 mRootFilePath = new File(rootPath); 75 mAndroidSdkPath = new File(androidSdkPath); 76 77 if (!mRootFilePath.exists()) { 78 throw new IllegalArgumentException("Invalid root path specified " 79 + rootPath); 80 } else if (!mAndroidSdkPath.exists()) { 81 throw new IllegalArgumentException( 82 "Invalid android sdk path specified " + androidSdkPath); 83 } 84 } 85 86 /** 87 * Performs validation of Android layout files and logs errors that have 88 * been encountered during the validation. Returns true if the validation 89 * passes. 90 */ run()91 private boolean run() { 92 sLogger.info("Validating files under " + mRootFilePath); 93 mLayoutFiles = findLayoutFiles(mRootFilePath); 94 validateFiles(); 95 for (String error : mGeneralErrors) { 96 sLogger.info(error); 97 } 98 sLogger.info("done with validation"); 99 return mGeneralErrors.size() == 0; 100 } 101 102 /** 103 * Accumulates a list of files under the path that meet two constraints. 104 * Firstly, it has a containing (parent) directory of "layout". Secondly, it 105 * has an xml extension 106 */ findLayoutFiles(File directory)107 private List<InputSource> findLayoutFiles(File directory) { 108 List<InputSource> layoutFiles = new ArrayList<InputSource>(); 109 110 for (File file : directory.listFiles()) { 111 // The file is a directory; recurse on the file. 112 if (file.isDirectory()) { 113 List<InputSource> directoryFiles = findLayoutFiles(file); 114 layoutFiles.addAll(directoryFiles); 115 // Does the containing directory and filename meet our 116 // constraints? 117 } else if (directory.getName().toLowerCase().contains("layout") 118 && file.getName().toLowerCase().endsWith(".xml")) { 119 InputSource addition; 120 try { 121 addition = new InputSource(new FileReader(file)); 122 // Store this explicitly for logging. 123 addition.setPublicId(file.toString()); 124 layoutFiles.add(addition); 125 } catch (FileNotFoundException fileNotFoundException) { 126 mGeneralErrors.add("File not found " 127 + fileNotFoundException); 128 } 129 } 130 } 131 132 return layoutFiles; 133 } 134 135 /* 136 * Processes a list of files via an AccessibilityValidationContentHandler. 137 * The caller will only be notified of errors via logging. 138 */ validateFiles()139 public void validateFiles() { 140 sLogger.info("Validating " + getLayoutFiles().size()); 141 XMLReader reader; 142 try { 143 reader = XMLReaderFactory.createXMLReader(); 144 } catch (SAXException saxExp) { 145 mGeneralErrors.add("Error " + saxExp); 146 return; 147 } 148 for (InputSource file : getLayoutFiles()) { 149 try { 150 AccessibilityValidationContentHandler contentHandler 151 = new AccessibilityValidationContentHandler( 152 file.getPublicId(), mAndroidSdkPath); 153 reader.setContentHandler(contentHandler); 154 reader.parse(file); 155 mTotalValidationErrors += contentHandler.getValidationErrors(); 156 } catch (IOException ioExp) { 157 mGeneralErrors.add("Error reading file " + ioExp); 158 } catch (SAXException saxExp) { 159 mGeneralErrors.add("Error " + saxExp); 160 } 161 } 162 } 163 164 /** 165 * Returns the number of general errors (considered caught exceptions). 166 */ getGeneralErrors()167 public List<String> getGeneralErrors() { 168 return mGeneralErrors; 169 } 170 171 /** 172 * Sets the files to be tested. 173 */ setLayoutFiles(List<InputSource> layoutFiles)174 public void setLayoutFiles(List<InputSource> layoutFiles) { 175 this.mLayoutFiles = layoutFiles; 176 } 177 178 /** 179 * Gets the files to be tested. 180 */ getLayoutFiles()181 public List<InputSource> getLayoutFiles() { 182 return mLayoutFiles; 183 } 184 185 /** 186 * Gets the total number of test validation errors across all files. 187 */ getTotalValidationErrors()188 public int getTotalValidationErrors() { 189 return mTotalValidationErrors; 190 } 191 } 192