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