1663744c839163924037ff1e8c818f8b540675170David Tseng/* 2663744c839163924037ff1e8c818f8b540675170David Tseng * Copyright 2010 Google Inc. 3663744c839163924037ff1e8c818f8b540675170David Tseng * 4663744c839163924037ff1e8c818f8b540675170David Tseng * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5663744c839163924037ff1e8c818f8b540675170David Tseng * use this file except in compliance with the License. You may obtain a copy of 6663744c839163924037ff1e8c818f8b540675170David Tseng * the License at 7663744c839163924037ff1e8c818f8b540675170David Tseng * 8663744c839163924037ff1e8c818f8b540675170David Tseng * http://www.apache.org/licenses/LICENSE-2.0 9663744c839163924037ff1e8c818f8b540675170David Tseng * 10663744c839163924037ff1e8c818f8b540675170David Tseng * Unless required by applicable law or agreed to in writing, software 11663744c839163924037ff1e8c818f8b540675170David Tseng * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12663744c839163924037ff1e8c818f8b540675170David Tseng * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13663744c839163924037ff1e8c818f8b540675170David Tseng * License for the specific language governing permissions and limitations under 14663744c839163924037ff1e8c818f8b540675170David Tseng * the License. 15663744c839163924037ff1e8c818f8b540675170David Tseng */ 16663744c839163924037ff1e8c818f8b540675170David Tseng 17663744c839163924037ff1e8c818f8b540675170David Tsengpackage com.android.accessibility; 18663744c839163924037ff1e8c818f8b540675170David Tseng 19663744c839163924037ff1e8c818f8b540675170David Tsengimport org.xml.sax.InputSource; 20663744c839163924037ff1e8c818f8b540675170David Tsengimport org.xml.sax.SAXException; 21663744c839163924037ff1e8c818f8b540675170David Tsengimport org.xml.sax.XMLReader; 22663744c839163924037ff1e8c818f8b540675170David Tsengimport org.xml.sax.helpers.XMLReaderFactory; 23663744c839163924037ff1e8c818f8b540675170David Tseng 24663744c839163924037ff1e8c818f8b540675170David Tsengimport java.io.*; 25663744c839163924037ff1e8c818f8b540675170David Tsengimport java.util.ArrayList; 26663744c839163924037ff1e8c818f8b540675170David Tsengimport java.util.List; 27663744c839163924037ff1e8c818f8b540675170David Tsengimport java.util.logging.Logger; 28663744c839163924037ff1e8c818f8b540675170David Tseng 29663744c839163924037ff1e8c818f8b540675170David Tseng/** 30663744c839163924037ff1e8c818f8b540675170David Tseng * An object that fetches all Android layout files and manages the testing of 31663744c839163924037ff1e8c818f8b540675170David Tseng * the files with the use of the AccessibilityValidationContentHandler. This 32663744c839163924037ff1e8c818f8b540675170David Tseng * object also reports on any errors encountered during the testing. 33663744c839163924037ff1e8c818f8b540675170David Tseng * 34663744c839163924037ff1e8c818f8b540675170David Tseng * @author dtseng@google.com (David Tseng) 35663744c839163924037ff1e8c818f8b540675170David Tseng */ 36663744c839163924037ff1e8c818f8b540675170David Tsengpublic class AccessibilityValidator { 37663744c839163924037ff1e8c818f8b540675170David Tseng /** The root path to scan for Android layout files. */ 38663744c839163924037ff1e8c818f8b540675170David Tseng private final File mRootFilePath; 39663744c839163924037ff1e8c818f8b540675170David Tseng /** Errors generated by thrown exceptions (and not by validation errors). */ 40663744c839163924037ff1e8c818f8b540675170David Tseng private final List<String> mGeneralErrors = new ArrayList<String>(); 41663744c839163924037ff1e8c818f8b540675170David Tseng /** A list of files we wish to have tested. */ 42663744c839163924037ff1e8c818f8b540675170David Tseng private List<InputSource> mLayoutFiles; 43663744c839163924037ff1e8c818f8b540675170David Tseng /** The total number of validation test errors across all files. */ 44663744c839163924037ff1e8c818f8b540675170David Tseng private int mTotalValidationErrors = 0; 45663744c839163924037ff1e8c818f8b540675170David Tseng /** The path to the Android sdk jar. */ 46663744c839163924037ff1e8c818f8b540675170David Tseng private final File mAndroidSdkPath; 47663744c839163924037ff1e8c818f8b540675170David Tseng 48663744c839163924037ff1e8c818f8b540675170David Tseng /** The object that handles our logging. */ 49663744c839163924037ff1e8c818f8b540675170David Tseng private static final Logger sLogger = Logger.getLogger("android.accessibility"); 50663744c839163924037ff1e8c818f8b540675170David Tseng 51663744c839163924037ff1e8c818f8b540675170David Tseng /** 52663744c839163924037ff1e8c818f8b540675170David Tseng * The entry point to this tool. 53663744c839163924037ff1e8c818f8b540675170David Tseng * 54663744c839163924037ff1e8c818f8b540675170David Tseng * @param55663744c839163924037ff1e8c818f8b540675170David Tseng * path on which to search for layout xml files that need 56663744c839163924037ff1e8c818f8b540675170David Tseng * validation 57663744c839163924037ff1e8c818f8b540675170David Tseng */ 58663744c839163924037ff1e8c818f8b540675170David Tseng public static void main(String[] args) { 59663744c839163924037ff1e8c818f8b540675170David Tseng sLogger.info("AccessibilityValidator"); 60663744c839163924037ff1e8c818f8b540675170David Tseng if (args.length == 2) { 61663744c839163924037ff1e8c818f8b540675170David Tseng sLogger.info("Validating classes using android jar for subclasses of ImageView"); 62663744c839163924037ff1e8c818f8b540675170David Tseng new AccessibilityValidator(args[0], args[1]).run(); 63663744c839163924037ff1e8c818f8b540675170David Tseng } else { 64663744c839163924037ff1e8c818f8b540675170David Tseng sLogger.info("Usage: java AccessibilityValidator <path> <Android jar path>"); 65663744c839163924037ff1e8c818f8b540675170David Tseng return; 66663744c839163924037ff1e8c818f8b540675170David Tseng } 67663744c839163924037ff1e8c818f8b540675170David Tseng } 68663744c839163924037ff1e8c818f8b540675170David Tseng 69663744c839163924037ff1e8c818f8b540675170David Tseng /** 70663744c839163924037ff1e8c818f8b540675170David Tseng * Constructs an AccessibilityValidator object using the root path and the 71663744c839163924037ff1e8c818f8b540675170David Tseng * android jar path. 72663744c839163924037ff1e8c818f8b540675170David Tseng */ 73663744c839163924037ff1e8c818f8b540675170David Tseng public AccessibilityValidator(String rootPath, String androidSdkPath) { 74663744c839163924037ff1e8c818f8b540675170David Tseng mRootFilePath = new File(rootPath); 75663744c839163924037ff1e8c818f8b540675170David Tseng mAndroidSdkPath = new File(androidSdkPath); 76663744c839163924037ff1e8c818f8b540675170David Tseng 77663744c839163924037ff1e8c818f8b540675170David Tseng if (!mRootFilePath.exists()) { 78663744c839163924037ff1e8c818f8b540675170David Tseng throw new IllegalArgumentException("Invalid root path specified " 79663744c839163924037ff1e8c818f8b540675170David Tseng + rootPath); 80663744c839163924037ff1e8c818f8b540675170David Tseng } else if (!mAndroidSdkPath.exists()) { 81663744c839163924037ff1e8c818f8b540675170David Tseng throw new IllegalArgumentException( 82663744c839163924037ff1e8c818f8b540675170David Tseng "Invalid android sdk path specified " + androidSdkPath); 83663744c839163924037ff1e8c818f8b540675170David Tseng } 84663744c839163924037ff1e8c818f8b540675170David Tseng } 85663744c839163924037ff1e8c818f8b540675170David Tseng 86663744c839163924037ff1e8c818f8b540675170David Tseng /** 87663744c839163924037ff1e8c818f8b540675170David Tseng * Performs validation of Android layout files and logs errors that have 88663744c839163924037ff1e8c818f8b540675170David Tseng * been encountered during the validation. Returns true if the validation 89663744c839163924037ff1e8c818f8b540675170David Tseng * passes. 90663744c839163924037ff1e8c818f8b540675170David Tseng */ 91663744c839163924037ff1e8c818f8b540675170David Tseng private boolean run() { 92663744c839163924037ff1e8c818f8b540675170David Tseng sLogger.info("Validating files under " + mRootFilePath); 93663744c839163924037ff1e8c818f8b540675170David Tseng mLayoutFiles = findLayoutFiles(mRootFilePath); 94663744c839163924037ff1e8c818f8b540675170David Tseng validateFiles(); 95663744c839163924037ff1e8c818f8b540675170David Tseng for (String error : mGeneralErrors) { 96663744c839163924037ff1e8c818f8b540675170David Tseng sLogger.info(error); 97663744c839163924037ff1e8c818f8b540675170David Tseng } 98663744c839163924037ff1e8c818f8b540675170David Tseng sLogger.info("done with validation"); 99663744c839163924037ff1e8c818f8b540675170David Tseng return mGeneralErrors.size() == 0; 100663744c839163924037ff1e8c818f8b540675170David Tseng } 101663744c839163924037ff1e8c818f8b540675170David Tseng 102663744c839163924037ff1e8c818f8b540675170David Tseng /** 103663744c839163924037ff1e8c818f8b540675170David Tseng * Accumulates a list of files under the path that meet two constraints. 104663744c839163924037ff1e8c818f8b540675170David Tseng * Firstly, it has a containing (parent) directory of "layout". Secondly, it 105663744c839163924037ff1e8c818f8b540675170David Tseng * has an xml extension 106663744c839163924037ff1e8c818f8b540675170David Tseng */ 107663744c839163924037ff1e8c818f8b540675170David Tseng private List<InputSource> findLayoutFiles(File directory) { 108663744c839163924037ff1e8c818f8b540675170David Tseng List<InputSource> layoutFiles = new ArrayList<InputSource>(); 109663744c839163924037ff1e8c818f8b540675170David Tseng 110663744c839163924037ff1e8c818f8b540675170David Tseng for (File file : directory.listFiles()) { 111663744c839163924037ff1e8c818f8b540675170David Tseng // The file is a directory; recurse on the file. 112663744c839163924037ff1e8c818f8b540675170David Tseng if (file.isDirectory()) { 113663744c839163924037ff1e8c818f8b540675170David Tseng List<InputSource> directoryFiles = findLayoutFiles(file); 114663744c839163924037ff1e8c818f8b540675170David Tseng layoutFiles.addAll(directoryFiles); 115663744c839163924037ff1e8c818f8b540675170David Tseng // Does the containing directory and filename meet our 116663744c839163924037ff1e8c818f8b540675170David Tseng // constraints? 117663744c839163924037ff1e8c818f8b540675170David Tseng } else if (directory.getName().toLowerCase().contains("layout") 118663744c839163924037ff1e8c818f8b540675170David Tseng && file.getName().toLowerCase().endsWith(".xml")) { 119663744c839163924037ff1e8c818f8b540675170David Tseng InputSource addition; 120663744c839163924037ff1e8c818f8b540675170David Tseng try { 121663744c839163924037ff1e8c818f8b540675170David Tseng addition = new InputSource(new FileReader(file)); 122663744c839163924037ff1e8c818f8b540675170David Tseng // Store this explicitly for logging. 123663744c839163924037ff1e8c818f8b540675170David Tseng addition.setPublicId(file.toString()); 124663744c839163924037ff1e8c818f8b540675170David Tseng layoutFiles.add(addition); 125663744c839163924037ff1e8c818f8b540675170David Tseng } catch (FileNotFoundException fileNotFoundException) { 126663744c839163924037ff1e8c818f8b540675170David Tseng mGeneralErrors.add("File not found " 127663744c839163924037ff1e8c818f8b540675170David Tseng + fileNotFoundException); 128663744c839163924037ff1e8c818f8b540675170David Tseng } 129663744c839163924037ff1e8c818f8b540675170David Tseng } 130663744c839163924037ff1e8c818f8b540675170David Tseng } 131663744c839163924037ff1e8c818f8b540675170David Tseng 132663744c839163924037ff1e8c818f8b540675170David Tseng return layoutFiles; 133663744c839163924037ff1e8c818f8b540675170David Tseng } 134663744c839163924037ff1e8c818f8b540675170David Tseng 135663744c839163924037ff1e8c818f8b540675170David Tseng /* 136663744c839163924037ff1e8c818f8b540675170David Tseng * Processes a list of files via an AccessibilityValidationContentHandler. 137663744c839163924037ff1e8c818f8b540675170David Tseng * The caller will only be notified of errors via logging. 138663744c839163924037ff1e8c818f8b540675170David Tseng */ 139663744c839163924037ff1e8c818f8b540675170David Tseng public void validateFiles() { 140663744c839163924037ff1e8c818f8b540675170David Tseng sLogger.info("Validating " + getLayoutFiles().size()); 141663744c839163924037ff1e8c818f8b540675170David Tseng XMLReader reader; 142663744c839163924037ff1e8c818f8b540675170David Tseng try { 143663744c839163924037ff1e8c818f8b540675170David Tseng reader = XMLReaderFactory.createXMLReader(); 144663744c839163924037ff1e8c818f8b540675170David Tseng } catch (SAXException saxExp) { 145663744c839163924037ff1e8c818f8b540675170David Tseng mGeneralErrors.add("Error " + saxExp); 146663744c839163924037ff1e8c818f8b540675170David Tseng return; 147663744c839163924037ff1e8c818f8b540675170David Tseng } 148663744c839163924037ff1e8c818f8b540675170David Tseng for (InputSource file : getLayoutFiles()) { 149663744c839163924037ff1e8c818f8b540675170David Tseng try { 150663744c839163924037ff1e8c818f8b540675170David Tseng AccessibilityValidationContentHandler contentHandler 151663744c839163924037ff1e8c818f8b540675170David Tseng = new AccessibilityValidationContentHandler( 152663744c839163924037ff1e8c818f8b540675170David Tseng file.getPublicId(), mAndroidSdkPath); 153663744c839163924037ff1e8c818f8b540675170David Tseng reader.setContentHandler(contentHandler); 154663744c839163924037ff1e8c818f8b540675170David Tseng reader.parse(file); 155663744c839163924037ff1e8c818f8b540675170David Tseng mTotalValidationErrors += contentHandler.getValidationErrors(); 156663744c839163924037ff1e8c818f8b540675170David Tseng } catch (IOException ioExp) { 157663744c839163924037ff1e8c818f8b540675170David Tseng mGeneralErrors.add("Error reading file " + ioExp); 158663744c839163924037ff1e8c818f8b540675170David Tseng } catch (SAXException saxExp) { 159663744c839163924037ff1e8c818f8b540675170David Tseng mGeneralErrors.add("Error " + saxExp); 160663744c839163924037ff1e8c818f8b540675170David Tseng } 161663744c839163924037ff1e8c818f8b540675170David Tseng } 162663744c839163924037ff1e8c818f8b540675170David Tseng } 163663744c839163924037ff1e8c818f8b540675170David Tseng 164663744c839163924037ff1e8c818f8b540675170David Tseng /** 165663744c839163924037ff1e8c818f8b540675170David Tseng * Returns the number of general errors (considered caught exceptions). 166663744c839163924037ff1e8c818f8b540675170David Tseng */ 167663744c839163924037ff1e8c818f8b540675170David Tseng public List<String> getGeneralErrors() { 168663744c839163924037ff1e8c818f8b540675170David Tseng return mGeneralErrors; 169663744c839163924037ff1e8c818f8b540675170David Tseng } 170663744c839163924037ff1e8c818f8b540675170David Tseng 171663744c839163924037ff1e8c818f8b540675170David Tseng /** 172663744c839163924037ff1e8c818f8b540675170David Tseng * Sets the files to be tested. 173663744c839163924037ff1e8c818f8b540675170David Tseng */ 174663744c839163924037ff1e8c818f8b540675170David Tseng public void setLayoutFiles(List<InputSource> layoutFiles) { 175663744c839163924037ff1e8c818f8b540675170David Tseng this.mLayoutFiles = layoutFiles; 176663744c839163924037ff1e8c818f8b540675170David Tseng } 177663744c839163924037ff1e8c818f8b540675170David Tseng 178663744c839163924037ff1e8c818f8b540675170David Tseng /** 179663744c839163924037ff1e8c818f8b540675170David Tseng * Gets the files to be tested. 180663744c839163924037ff1e8c818f8b540675170David Tseng */ 181663744c839163924037ff1e8c818f8b540675170David Tseng public List<InputSource> getLayoutFiles() { 182663744c839163924037ff1e8c818f8b540675170David Tseng return mLayoutFiles; 183663744c839163924037ff1e8c818f8b540675170David Tseng } 184663744c839163924037ff1e8c818f8b540675170David Tseng 185663744c839163924037ff1e8c818f8b540675170David Tseng /** 186663744c839163924037ff1e8c818f8b540675170David Tseng * Gets the total number of test validation errors across all files. 187663744c839163924037ff1e8c818f8b540675170David Tseng */ 188663744c839163924037ff1e8c818f8b540675170David Tseng public int getTotalValidationErrors() { 189663744c839163924037ff1e8c818f8b540675170David Tseng return mTotalValidationErrors; 190663744c839163924037ff1e8c818f8b540675170David Tseng } 191663744c839163924037ff1e8c818f8b540675170David Tseng} 192