1c134b29628280a680fdf2696480166de56074b1eKevin Jin/* 2c134b29628280a680fdf2696480166de56074b1eKevin Jin * Copyright (C) 2013 DroidDriver committers 3c134b29628280a680fdf2696480166de56074b1eKevin Jin * 4c134b29628280a680fdf2696480166de56074b1eKevin Jin * Licensed under the Apache License, Version 2.0 (the "License"); 5c134b29628280a680fdf2696480166de56074b1eKevin Jin * you may not use this file except in compliance with the License. 6c134b29628280a680fdf2696480166de56074b1eKevin Jin * You may obtain a copy of the License at 7c134b29628280a680fdf2696480166de56074b1eKevin Jin * 8c134b29628280a680fdf2696480166de56074b1eKevin Jin * http://www.apache.org/licenses/LICENSE-2.0 9c134b29628280a680fdf2696480166de56074b1eKevin Jin * 10c134b29628280a680fdf2696480166de56074b1eKevin Jin * Unless required by applicable law or agreed to in writing, software 11c134b29628280a680fdf2696480166de56074b1eKevin Jin * distributed under the License is distributed on an "AS IS" BASIS, 12c134b29628280a680fdf2696480166de56074b1eKevin Jin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13c134b29628280a680fdf2696480166de56074b1eKevin Jin * See the License for the specific language governing permissions and 14c134b29628280a680fdf2696480166de56074b1eKevin Jin * limitations under the License. 15c134b29628280a680fdf2696480166de56074b1eKevin Jin */ 167576fbbba2bf515908b45293b7156b5bfe088938Kevin Jinpackage com.google.android.droiddriver.finders; 17c134b29628280a680fdf2696480166de56074b1eKevin Jin 1879884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport android.util.Log; 1979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 2079884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport com.google.android.droiddriver.UiElement; 21f50519233078e65a056cff49d7b4989d57c3e750Kevin Jinimport com.google.android.droiddriver.base.BaseUiElement; 22c134b29628280a680fdf2696480166de56074b1eKevin Jinimport com.google.android.droiddriver.exceptions.DroidDriverException; 2379884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport com.google.android.droiddriver.exceptions.ElementNotFoundException; 246316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport com.google.android.droiddriver.util.FileUtils; 2579884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport com.google.android.droiddriver.util.Logs; 2617342a5115d7575d44a99fed9c7032e3ab316dccKevin Jinimport com.google.android.droiddriver.util.Preconditions; 2717342a5115d7575d44a99fed9c7032e3ab316dccKevin Jinimport com.google.android.droiddriver.util.Strings; 28c134b29628280a680fdf2696480166de56074b1eKevin Jin 2979884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport org.w3c.dom.DOMException; 3079884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport org.w3c.dom.Document; 3179884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport org.w3c.dom.Element; 3279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 336316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport java.io.BufferedOutputStream; 3417342a5115d7575d44a99fed9c7032e3ab316dccKevin Jinimport java.util.HashMap; 3517342a5115d7575d44a99fed9c7032e3ab316dccKevin Jinimport java.util.Map; 366316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin 3779884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport javax.xml.parsers.DocumentBuilderFactory; 3879884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport javax.xml.parsers.ParserConfigurationException; 396316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport javax.xml.transform.OutputKeys; 406316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport javax.xml.transform.Transformer; 416316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport javax.xml.transform.TransformerFactory; 426316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport javax.xml.transform.dom.DOMSource; 436316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jinimport javax.xml.transform.stream.StreamResult; 44fc21930b0a5cc598f59c0c893274e4ce33abd235Kevin Jinimport javax.xml.xpath.XPath; 4579884ac460f8d357c28091ec3f42fe369964b809Kevin Jinimport javax.xml.xpath.XPathConstants; 46c134b29628280a680fdf2696480166de56074b1eKevin Jinimport javax.xml.xpath.XPathExpression; 47c134b29628280a680fdf2696480166de56074b1eKevin Jinimport javax.xml.xpath.XPathExpressionException; 48c134b29628280a680fdf2696480166de56074b1eKevin Jinimport javax.xml.xpath.XPathFactory; 49c134b29628280a680fdf2696480166de56074b1eKevin Jin 50fc21930b0a5cc598f59c0c893274e4ce33abd235Kevin Jin/** 51fc21930b0a5cc598f59c0c893274e4ce33abd235Kevin Jin * Find matching UiElement by XPath. 52fc21930b0a5cc598f59c0c893274e4ce33abd235Kevin Jin */ 537576fbbba2bf515908b45293b7156b5bfe088938Kevin Jinpublic class ByXPath implements Finder { 54fc21930b0a5cc598f59c0c893274e4ce33abd235Kevin Jin private static final XPath XPATH_COMPILER = XPathFactory.newInstance().newXPath(); 5579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin // document needs to be static so that when buildDomNode is called recursively 5679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin // on children they are in the same document to be appended. 5779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin private static Document document; 5817342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin // The two maps should be kept in sync 5974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private static final Map<BaseUiElement<?, ?>, Element> TO_DOM_MAP = 6074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin new HashMap<BaseUiElement<?, ?>, Element>(); 6174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private static final Map<Element, BaseUiElement<?, ?>> FROM_DOM_MAP = 6274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin new HashMap<Element, BaseUiElement<?, ?>>(); 63c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin 64c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin public static void clearData() { 6517342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin TO_DOM_MAP.clear(); 6617342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin FROM_DOM_MAP.clear(); 67c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin document = null; 68c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin } 69c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin 708e4d4bb6a6c2b2e4a470c0804833de2a92f154d5Kevin Jin private final String xPathString; 71c134b29628280a680fdf2696480166de56074b1eKevin Jin private final XPathExpression xPathExpression; 72c134b29628280a680fdf2696480166de56074b1eKevin Jin 738e4d4bb6a6c2b2e4a470c0804833de2a92f154d5Kevin Jin protected ByXPath(String xPathString) { 748e4d4bb6a6c2b2e4a470c0804833de2a92f154d5Kevin Jin this.xPathString = Preconditions.checkNotNull(xPathString); 75c134b29628280a680fdf2696480166de56074b1eKevin Jin try { 76fc21930b0a5cc598f59c0c893274e4ce33abd235Kevin Jin xPathExpression = XPATH_COMPILER.compile(xPathString); 77c134b29628280a680fdf2696480166de56074b1eKevin Jin } catch (XPathExpressionException e) { 786316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin throw new DroidDriverException("xPathString=" + xPathString, e); 79c134b29628280a680fdf2696480166de56074b1eKevin Jin } 80c134b29628280a680fdf2696480166de56074b1eKevin Jin } 81c134b29628280a680fdf2696480166de56074b1eKevin Jin 82c134b29628280a680fdf2696480166de56074b1eKevin Jin @Override 83c134b29628280a680fdf2696480166de56074b1eKevin Jin public String toString() { 8417342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin return Strings.toStringHelper(this).addValue(xPathString).toString(); 85c134b29628280a680fdf2696480166de56074b1eKevin Jin } 86c134b29628280a680fdf2696480166de56074b1eKevin Jin 8779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin @Override 8879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin public UiElement find(UiElement context) { 8974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin Element domNode = getDomNode((BaseUiElement<?, ?>) context, UiElement.VISIBLE); 9079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin try { 9179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin getDocument().appendChild(domNode); 9279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin Element foundNode = (Element) xPathExpression.evaluate(domNode, XPathConstants.NODE); 9379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin if (foundNode == null) { 946316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin Logs.log(Log.DEBUG, "XPath evaluation returns null for " + xPathString); 9579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin throw new ElementNotFoundException(this); 9679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 97eb4e1921c193bb90eb0122ea7b0fd37cef60e8e1Kevin Jin 9817342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin UiElement match = FROM_DOM_MAP.get(foundNode); 99eb4e1921c193bb90eb0122ea7b0fd37cef60e8e1Kevin Jin Logs.log(Log.INFO, "Found match: " + match); 100eb4e1921c193bb90eb0122ea7b0fd37cef60e8e1Kevin Jin return match; 10179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } catch (XPathExpressionException e) { 10279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin throw new ElementNotFoundException(this, e); 10379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } finally { 10479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin try { 10579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin getDocument().removeChild(domNode); 1060b9344441daed36d371df59ca4735d1e0e008189Kevin Jin } catch (DOMException e) { 1070b9344441daed36d371df59ca4735d1e0e008189Kevin Jin Logs.log(Log.ERROR, e, "Failed to clear document"); 10879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin document = null; // getDocument will create new 10979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 11079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 11179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 11279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 11379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin private static Document getDocument() { 11479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin if (document == null) { 11579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin try { 11679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 11779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } catch (ParserConfigurationException e) { 11879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin throw new DroidDriverException(e); 11979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 12079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 12179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin return document; 12279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 12379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 12479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin /** 125c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin * Returns the DOM node representing this UiElement. 12679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin */ 12774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private static Element getDomNode(BaseUiElement<?, ?> uiElement, 12874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin Predicate<? super UiElement> predicate) { 12917342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin Element domNode = TO_DOM_MAP.get(uiElement); 130c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin if (domNode == null) { 131c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin domNode = buildDomNode(uiElement, predicate); 132c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin } 133c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin return domNode; 134c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin } 135c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin 13674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private static Element buildDomNode(BaseUiElement<?, ?> uiElement, 137c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin Predicate<? super UiElement> predicate) { 13879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin String className = uiElement.getClassName(); 13979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin if (className == null) { 14079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin className = "UNKNOWN"; 14179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 14279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin Element element = getDocument().createElement(XPaths.tag(className)); 14317342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin TO_DOM_MAP.put(uiElement, element); 14417342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin FROM_DOM_MAP.put(element, uiElement); 14579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 14679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.CLASS, className); 14779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.RESOURCE_ID, uiElement.getResourceId()); 14879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.PACKAGE, uiElement.getPackageName()); 14979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.CONTENT_DESC, uiElement.getContentDescription()); 15079884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.TEXT, uiElement.getText()); 15179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.CHECKABLE, uiElement.isCheckable()); 15279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.CHECKED, uiElement.isChecked()); 15379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.CLICKABLE, uiElement.isClickable()); 15479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.ENABLED, uiElement.isEnabled()); 15579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.FOCUSABLE, uiElement.isFocusable()); 15679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.FOCUSED, uiElement.isFocused()); 15779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.SCROLLABLE, uiElement.isScrollable()); 15879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.LONG_CLICKABLE, uiElement.isLongClickable()); 15979884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.PASSWORD, uiElement.isPassword()); 1605c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin if (uiElement.hasSelection()) { 1615c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin element.setAttribute(Attribute.SELECTION_START.getName(), 1625c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin Integer.toString(uiElement.getSelectionStart())); 1635c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin element.setAttribute(Attribute.SELECTION_END.getName(), 1645c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin Integer.toString(uiElement.getSelectionEnd())); 1655c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin } 16679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin setAttribute(element, Attribute.SELECTED, uiElement.isSelected()); 16779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin element.setAttribute(Attribute.BOUNDS.getName(), uiElement.getBounds().toShortString()); 16879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 169d2abd0b28789a4a187343b0485e2b8e3fc9ef7acKevin Jin // If we're dumping for debugging, add extra information 170d2abd0b28789a4a187343b0485e2b8e3fc9ef7acKevin Jin if (!UiElement.VISIBLE.equals(predicate)) { 171d2abd0b28789a4a187343b0485e2b8e3fc9ef7acKevin Jin if (!uiElement.isVisible()) { 172d2abd0b28789a4a187343b0485e2b8e3fc9ef7acKevin Jin element.setAttribute(BaseUiElement.ATTRIB_NOT_VISIBLE, ""); 1735c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin } else if (!uiElement.getVisibleBounds().equals(uiElement.getBounds())) { 174d2abd0b28789a4a187343b0485e2b8e3fc9ef7acKevin Jin element.setAttribute(BaseUiElement.ATTRIB_VISIBLE_BOUNDS, uiElement.getVisibleBounds() 175d2abd0b28789a4a187343b0485e2b8e3fc9ef7acKevin Jin .toShortString()); 176d2abd0b28789a4a187343b0485e2b8e3fc9ef7acKevin Jin } 177d2abd0b28789a4a187343b0485e2b8e3fc9ef7acKevin Jin } 178d2abd0b28789a4a187343b0485e2b8e3fc9ef7acKevin Jin 17974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin for (BaseUiElement<?, ?> child : uiElement.getChildren(predicate)) { 180c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin element.appendChild(getDomNode(child, predicate)); 18179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 18279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin return element; 18379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 18479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin 18579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin private static void setAttribute(Element element, Attribute attr, String value) { 18679884ac460f8d357c28091ec3f42fe369964b809Kevin Jin if (value != null) { 18779884ac460f8d357c28091ec3f42fe369964b809Kevin Jin element.setAttribute(attr.getName(), value); 18879884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 189c134b29628280a680fdf2696480166de56074b1eKevin Jin } 1908e4d4bb6a6c2b2e4a470c0804833de2a92f154d5Kevin Jin 19179884ac460f8d357c28091ec3f42fe369964b809Kevin Jin // add attribute only if it's true 19279884ac460f8d357c28091ec3f42fe369964b809Kevin Jin private static void setAttribute(Element element, Attribute attr, boolean value) { 19379884ac460f8d357c28091ec3f42fe369964b809Kevin Jin if (value) { 19479884ac460f8d357c28091ec3f42fe369964b809Kevin Jin element.setAttribute(attr.getName(), ""); 19579884ac460f8d357c28091ec3f42fe369964b809Kevin Jin } 1968e4d4bb6a6c2b2e4a470c0804833de2a92f154d5Kevin Jin } 1976316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin 19874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin public static boolean dumpDom(String path, BaseUiElement<?, ?> uiElement) { 1996316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin BufferedOutputStream bos = null; 2006316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin try { 2016316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin bos = FileUtils.open(path); 2026316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin Transformer transformer = TransformerFactory.newInstance().newTransformer(); 2036316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 20417342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin // find() filters invisible UiElements, but this is for debugging and 20517342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin // invisible UiElements may be of interest. 20617342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin clearData(); 20717342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin Element domNode = getDomNode(uiElement, null); 20817342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin transformer.transform(new DOMSource(domNode), new StreamResult(bos)); 2096316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin Logs.log(Log.INFO, "Wrote dom to " + path); 2106316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } catch (Exception e) { 2110b9344441daed36d371df59ca4735d1e0e008189Kevin Jin Logs.log(Log.ERROR, e, "Failed to transform node"); 2126316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin return false; 2136316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } finally { 214c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin // We built DOM with invisible UiElements. Don't use it for find()! 215c39b04e0e5d3962153cd860d1430857fe625da90Kevin Jin clearData(); 2166316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin if (bos != null) { 2176316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin try { 2186316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin bos.close(); 2196316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } catch (Exception e) { 2206316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin // ignore 2216316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } 2226316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } 2236316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } 2246316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin return true; 2256316362de61fca700d7d5a455ad5c0ac9717c365Kevin Jin } 226c134b29628280a680fdf2696480166de56074b1eKevin Jin} 227