• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
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.ide.eclipse.adt.internal.sdk;
18 
19 import com.android.ide.common.resources.configuration.FolderConfiguration;
20 import com.android.ide.eclipse.adt.AdtPlugin;
21 import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice.DeviceConfig;
22 import com.android.prefs.AndroidLocation;
23 import com.android.prefs.AndroidLocation.AndroidLocationException;
24 import com.android.sdklib.SdkConstants;
25 
26 import org.w3c.dom.Document;
27 import org.w3c.dom.Element;
28 import org.xml.sax.ErrorHandler;
29 import org.xml.sax.InputSource;
30 import org.xml.sax.SAXException;
31 import org.xml.sax.SAXParseException;
32 
33 import java.io.File;
34 import java.io.FileInputStream;
35 import java.io.FileNotFoundException;
36 import java.io.FileReader;
37 import java.io.IOException;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.List;
41 
42 import javax.xml.parsers.DocumentBuilder;
43 import javax.xml.parsers.DocumentBuilderFactory;
44 import javax.xml.parsers.ParserConfigurationException;
45 import javax.xml.parsers.SAXParser;
46 import javax.xml.parsers.SAXParserFactory;
47 import javax.xml.transform.Result;
48 import javax.xml.transform.Source;
49 import javax.xml.transform.Transformer;
50 import javax.xml.transform.TransformerFactory;
51 import javax.xml.transform.dom.DOMSource;
52 import javax.xml.transform.stream.StreamResult;
53 import javax.xml.transform.stream.StreamSource;
54 import javax.xml.validation.Validator;
55 
56 /**
57  * Manages the layout devices.
58  * They can come from 3 sources: built-in, add-ons, user.
59  */
60 public class LayoutDeviceManager {
61 
62     /**
63      * A SAX error handler that captures the errors and warnings.
64      * This allows us to capture *all* errors and just not get an exception on the first one.
65      */
66     private static class CaptureErrorHandler implements ErrorHandler {
67 
68         private final String mSourceLocation;
69 
70         private boolean mFoundError = false;
71 
CaptureErrorHandler(String sourceLocation)72         CaptureErrorHandler(String sourceLocation) {
73             mSourceLocation = sourceLocation;
74         }
75 
foundError()76         public boolean foundError() {
77             return mFoundError;
78         }
79 
80         /**
81          * @throws SAXException
82          */
error(SAXParseException ex)83         public void error(SAXParseException ex) throws SAXException {
84             mFoundError = true;
85             AdtPlugin.log(ex, "Error validating %1$s", mSourceLocation);
86         }
87 
88         /**
89          * @throws SAXException
90          */
fatalError(SAXParseException ex)91         public void fatalError(SAXParseException ex) throws SAXException {
92             mFoundError = true;
93             AdtPlugin.log(ex, "Error validating %1$s", mSourceLocation);
94         }
95 
96         /**
97          * @throws SAXException
98          */
warning(SAXParseException ex)99         public void warning(SAXParseException ex) throws SAXException {
100             // ignore those for now.
101         }
102     }
103 
104     private final SAXParserFactory mParserFactory;
105 
106     private List<LayoutDevice> mDefaultLayoutDevices =
107         new ArrayList<LayoutDevice>();
108     private List<LayoutDevice> mAddOnLayoutDevices =
109         new ArrayList<LayoutDevice>();
110     private final List<LayoutDevice> mUserLayoutDevices =
111         new ArrayList<LayoutDevice>();
112     private List<LayoutDevice> mLayoutDevices;
113 
LayoutDeviceManager()114     LayoutDeviceManager() {
115         mParserFactory = SAXParserFactory.newInstance();
116         mParserFactory.setNamespaceAware(true);
117     }
118 
getCombinedList()119     public List<LayoutDevice> getCombinedList() {
120         return mLayoutDevices;
121     }
122 
getDefaultLayoutDevices()123     public List<LayoutDevice> getDefaultLayoutDevices() {
124         return mDefaultLayoutDevices;
125     }
126 
getAddOnLayoutDevice()127     public List<LayoutDevice> getAddOnLayoutDevice() {
128         return mAddOnLayoutDevices;
129     }
130 
getUserLayoutDevices()131     public List<LayoutDevice> getUserLayoutDevices() {
132         return mUserLayoutDevices;
133     }
134 
getUserLayoutDevice(String name)135     public LayoutDevice getUserLayoutDevice(String name) {
136         for (LayoutDevice d : mUserLayoutDevices) {
137             if (d.getName().equals(name)) {
138                 return d;
139             }
140         }
141 
142         return null;
143     }
144 
addUserDevice(String name, float xdpi, float ydpi)145     public LayoutDevice addUserDevice(String name, float xdpi, float ydpi) {
146         LayoutDevice d = new LayoutDevice(name);
147         d.setXDpi(xdpi);
148         d.setYDpi(ydpi);
149         mUserLayoutDevices.add(d);
150         combineLayoutDevices();
151 
152         return d;
153     }
154 
removeUserDevice(LayoutDevice device)155     public void removeUserDevice(LayoutDevice device) {
156         if (mUserLayoutDevices.remove(device)) {
157             combineLayoutDevices();
158         }
159     }
160 
161     /**
162      * Replaces a device with a new one with new name and/or x/y dpi, and return the new device.
163      * If the name and dpi values are identical the given device is returned an nothing is done
164      * @param device the {@link LayoutDevice} to replace
165      * @param newName the new name.
166      * @param newXDpi the new X dpi value
167      * @param newYDpi the new Y dpi value.
168      * @return the new LayoutDevice
169      */
replaceUserDevice(LayoutDevice device, String newName, float newXDpi, float newYDpi)170     public LayoutDevice replaceUserDevice(LayoutDevice device, String newName,
171             float newXDpi, float newYDpi) {
172         if (device.getName().equals(newName) && device.getXDpi() == newXDpi &&
173                 device.getYDpi() == newYDpi) {
174             return device;
175         }
176 
177         // else create a new device
178         LayoutDevice newDevice = new LayoutDevice(newName);
179         newDevice.setXDpi(newXDpi);
180         newDevice.setYDpi(newYDpi);
181 
182         // and get the Folderconfiguration
183         List<DeviceConfig> configs = device.getConfigs();
184         newDevice.addConfigs(configs);
185 
186         // replace the old device with the new
187         mUserLayoutDevices.remove(device);
188         mUserLayoutDevices.add(newDevice);
189         combineLayoutDevices();
190 
191         return newDevice;
192     }
193 
194     /**
195      * Adds or replaces a configuration in a given {@link LayoutDevice}.
196      * @param device the device to modify
197      * @param configName the configuration name to add or replace
198      * @param config the configuration to set
199      */
addUserConfiguration(LayoutDevice device, String configName, FolderConfiguration config)200     public void addUserConfiguration(LayoutDevice device, String configName,
201             FolderConfiguration config) {
202         // check that the device does belong to the user list.
203         // the main goal is to make sure that this does not belong to the default/addon list.
204         if (mUserLayoutDevices.contains(device)) {
205             device.addConfig(configName, config);
206         }
207     }
208 
209     /**
210      * Replaces a configuration in a given {@link LayoutDevice}.
211      * @param device the device to modify
212      * @param oldConfigName the name of the config to replace. If null, the new config is simply
213      * added.
214      * @param newConfigName the configuration name to add or replace
215      * @param config the configuration to set
216      */
replaceUserConfiguration(LayoutDevice device, String oldConfigName, String newConfigName, FolderConfiguration config)217     public void replaceUserConfiguration(LayoutDevice device, String oldConfigName,
218             String newConfigName, FolderConfiguration config) {
219         // check that the device does belong to the user list.
220         // the main goal is to make sure that this does not belong to the default/addon list.
221         if (mUserLayoutDevices.contains(device)) {
222             // if the old and new config name are different, remove the old one
223             if (oldConfigName != null && oldConfigName.equals(newConfigName) == false) {
224                 device.removeConfig(oldConfigName);
225             }
226 
227             // and then add the new one
228             device.addConfig(newConfigName, config);
229         }
230     }
231 
232     /**
233      * Removes a configuration from a given user {@link LayoutDevice}
234      * @param device the device to modify
235      * @param configName the name of the config to remove
236      */
removeUserConfiguration(LayoutDevice device, String configName)237     public void removeUserConfiguration(LayoutDevice device, String configName) {
238         // check that the device does belong to the user list.
239         // the main goal is to make sure that this does not belong to the default/addon list.
240         if (mUserLayoutDevices.contains(device)) {
241             device.removeConfig(configName);
242         }
243     }
244 
245     /**
246      * Saves the user-made {@link LayoutDevice}s to disk.
247      */
save()248     public void save() {
249         try {
250             String userFolder = AndroidLocation.getFolder();
251             File deviceXml = new File(userFolder, SdkConstants.FN_DEVICES_XML);
252             if (deviceXml.isDirectory() == false) {
253                 write(deviceXml, mUserLayoutDevices);
254             }
255         } catch (AndroidLocationException e) {
256             // no user folder? simply don't save the user layout device.
257             // we could display the error, but it's likely something else did before, as
258             // nothing will work w/o it.
259             AdtPlugin.log(e, "Unable to find user directory");
260         }
261     }
262 
263     /**
264      * Loads the default built-in and user created Layout Devices.
265      * @param sdkOsLocation location of the SDK.
266      */
loadDefaultAndUserDevices(String sdkOsLocation)267     void loadDefaultAndUserDevices(String sdkOsLocation) {
268         // load the default devices
269         loadDefaultLayoutDevices(sdkOsLocation);
270 
271         // load the user devices;
272         try {
273             String userFolder = AndroidLocation.getFolder();
274             File deviceXml = new File(userFolder, SdkConstants.FN_DEVICES_XML);
275             if (deviceXml.isFile()) {
276                 parseLayoutDevices(deviceXml, mUserLayoutDevices);
277             }
278         } catch (AndroidLocationException e) {
279             // no user folder? simply don't load the user layout device
280             AdtPlugin.log(e, "Unable to find user directory");
281         }
282     }
283 
parseAddOnLayoutDevice(File deviceXml)284     void parseAddOnLayoutDevice(File deviceXml) {
285         parseLayoutDevices(deviceXml, mAddOnLayoutDevices);
286     }
287 
sealAddonLayoutDevices()288     void sealAddonLayoutDevices() {
289         mAddOnLayoutDevices = Collections.unmodifiableList(mAddOnLayoutDevices);
290 
291         combineLayoutDevices();
292     }
293 
294     /**
295      * Does the actual parsing of a devices.xml file.
296      * @param deviceXml the {@link File} to load/parse. This must be an existing file.
297      * @param list the list in which to write the parsed {@link LayoutDevice}.
298      */
parseLayoutDevices(File deviceXml, List<LayoutDevice> list)299     private void parseLayoutDevices(File deviceXml, List<LayoutDevice> list) {
300         // first we validate the XML
301         try {
302             Source source = new StreamSource(new FileReader(deviceXml));
303 
304             CaptureErrorHandler errorHandler = new CaptureErrorHandler(deviceXml.getAbsolutePath());
305 
306             Validator validator = LayoutDevicesXsd.getValidator(errorHandler);
307             validator.validate(source);
308 
309             if (errorHandler.foundError() == false) {
310                 // do the actual parsing
311                 LayoutDeviceHandler handler = new LayoutDeviceHandler();
312 
313                 SAXParser parser = mParserFactory.newSAXParser();
314                 parser.parse(new InputSource(new FileInputStream(deviceXml)), handler);
315 
316                 // get the parsed devices
317                 list.addAll(handler.getDevices());
318             }
319         } catch (SAXException e) {
320             AdtPlugin.log(e, "Error parsing %1$s", deviceXml.getAbsoluteFile());
321         } catch (FileNotFoundException e) {
322             // this shouldn't happen as we check above.
323         } catch (IOException e) {
324             AdtPlugin.log(e, "Error reading %1$s", deviceXml.getAbsoluteFile());
325         } catch (ParserConfigurationException e) {
326             AdtPlugin.log(e, "Error parsing %1$s", deviceXml.getAbsoluteFile());
327         }
328     }
329 
330     /**
331      * Creates some built-it layout devices.
332      */
loadDefaultLayoutDevices(String sdkOsLocation)333     private void loadDefaultLayoutDevices(String sdkOsLocation) {
334         ArrayList<LayoutDevice> list = new ArrayList<LayoutDevice>();
335         File toolsFolder = new File(sdkOsLocation, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER);
336         if (toolsFolder.isDirectory()) {
337             File deviceXml = new File(toolsFolder, SdkConstants.FN_DEVICES_XML);
338             if (deviceXml.isFile()) {
339                 parseLayoutDevices(deviceXml, list);
340             }
341         }
342         mDefaultLayoutDevices = Collections.unmodifiableList(list);
343     }
344 
combineLayoutDevices()345     private void combineLayoutDevices() {
346         ArrayList<LayoutDevice> list = new ArrayList<LayoutDevice>();
347         list.addAll(mDefaultLayoutDevices);
348         list.addAll(mAddOnLayoutDevices);
349         list.addAll(mUserLayoutDevices);
350 
351         mLayoutDevices = Collections.unmodifiableList(list);
352     }
353 
354     /**
355      * Writes the given {@link LayoutDevice}s into the given file.
356      * @param deviceXml the file to write.
357      * @param deviceList the LayoutDevice to write into the file.
358      */
write(File deviceXml, List<LayoutDevice> deviceList)359     private void write(File deviceXml, List<LayoutDevice> deviceList) {
360         try {
361             // create a new document
362             DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
363             docFactory.setNamespaceAware(true);
364             DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
365             Document doc = docBuilder.newDocument();
366 
367             // create a base node
368             Element baseNode = doc.createElementNS(
369                     LayoutDevicesXsd.NS_LAYOUT_DEVICE_XSD,
370                     LayoutDevicesXsd.NODE_LAYOUT_DEVICES);
371             // create the prefix for the namespace
372             baseNode.setPrefix("d");
373             doc.appendChild(baseNode);
374 
375             // fill it with the layout devices.
376             for (LayoutDevice device : deviceList) {
377                 device.saveTo(doc, baseNode);
378             }
379 
380             // save the document to disk
381             // Prepare the DOM document for writing
382             Source source = new DOMSource(doc);
383 
384             // Prepare the output file
385             File file = new File(deviceXml.getAbsolutePath());
386             Result result = new StreamResult(file);
387 
388             // Write the DOM document to the file
389             Transformer xformer = TransformerFactory.newInstance().newTransformer();
390             xformer.transform(source, result);
391         } catch (Exception e) {
392             AdtPlugin.log(e, "Failed to write %s", deviceXml.getAbsolutePath());
393         }
394     }
395 }
396