Add save/load for the user-made Layout Devices. (do not merge)

Also added support for mcc/mnc in the schema.
This commit is contained in:
Xavier Ducrohet
2009-10-14 17:57:30 -07:00
parent 5e4efac4f7
commit af49663340
12 changed files with 368 additions and 77 deletions

View File

@@ -591,6 +591,9 @@ public class ConfigurationComposite extends Composite {
ConfigManagerDialog dialog = new ConfigManagerDialog(getShell()); ConfigManagerDialog dialog = new ConfigManagerDialog(getShell());
dialog.open(); dialog.open();
// save the user devices
Sdk.getCurrent().getLayoutDeviceManager().save();
// reload the combo with the new content. // reload the combo with the new content.
loadDevices(); loadDevices();
initUiWithDevices(); initUiWithDevices();

View File

@@ -33,7 +33,7 @@ public final class CountryCodeQualifier extends ResourceQualifier {
private final static Pattern sCountryCodePattern = Pattern.compile("^mcc(\\d{3})$");//$NON-NLS-1$ private final static Pattern sCountryCodePattern = Pattern.compile("^mcc(\\d{3})$");//$NON-NLS-1$
private int mCode = DEFAULT_CODE; private final int mCode;
public static final String NAME = "Mobile Country Code"; public static final String NAME = "Mobile Country Code";
@@ -56,8 +56,7 @@ public final class CountryCodeQualifier extends ResourceQualifier {
return null; return null;
} }
CountryCodeQualifier qualifier = new CountryCodeQualifier(); CountryCodeQualifier qualifier = new CountryCodeQualifier(code);
qualifier.mCode = code;
return qualifier; return qualifier;
} }
@@ -77,6 +76,14 @@ public final class CountryCodeQualifier extends ResourceQualifier {
return ""; //$NON-NLS-1$ return ""; //$NON-NLS-1$
} }
public CountryCodeQualifier() {
this(DEFAULT_CODE);
}
public CountryCodeQualifier(int code) {
mCode = code;
}
public int getCode() { public int getCode() {
return mCode; return mCode;
} }

View File

@@ -33,7 +33,7 @@ public final class NetworkCodeQualifier extends ResourceQualifier {
private final static Pattern sNetworkCodePattern = Pattern.compile("^mnc(\\d{1,3})$"); //$NON-NLS-1$ private final static Pattern sNetworkCodePattern = Pattern.compile("^mnc(\\d{1,3})$"); //$NON-NLS-1$
private int mCode = DEFAULT_CODE; private final int mCode;
public final static String NAME = "Mobile Network Code"; public final static String NAME = "Mobile Network Code";
@@ -56,8 +56,7 @@ public final class NetworkCodeQualifier extends ResourceQualifier {
return null; return null;
} }
NetworkCodeQualifier qualifier = new NetworkCodeQualifier(); NetworkCodeQualifier qualifier = new NetworkCodeQualifier(code);
qualifier.mCode = code;
return qualifier; return qualifier;
} }
@@ -77,6 +76,14 @@ public final class NetworkCodeQualifier extends ResourceQualifier {
return ""; //$NON-NLS-1$ return ""; //$NON-NLS-1$
} }
public NetworkCodeQualifier() {
this(DEFAULT_CODE);
}
public NetworkCodeQualifier(int code) {
mCode = code;
}
public int getCode() { public int getCode() {
return mCode; return mCode;
} }
@@ -115,8 +122,7 @@ public final class NetworkCodeQualifier extends ResourceQualifier {
return false; return false;
} }
NetworkCodeQualifier qualifier = new NetworkCodeQualifier(); NetworkCodeQualifier qualifier = new NetworkCodeQualifier(code);
qualifier.mCode = code;
config.setNetworkCodeQualifier(qualifier); config.setNetworkCodeQualifier(qualifier);
return true; return true;
} }

View File

@@ -16,11 +16,26 @@
package com.android.ide.eclipse.adt.internal.sdk; package com.android.ide.eclipse.adt.internal.sdk;
import com.android.ide.eclipse.adt.internal.resources.configurations.CountryCodeQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
import com.android.ide.eclipse.adt.internal.resources.configurations.KeyboardStateQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationMethodQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.NetworkCodeQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenDimensionQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenRatioQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenSizeQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.TextInputMethodQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.TouchScreenQualifier;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
/** /**
* Class representing a layout device. * Class representing a layout device.
@@ -53,6 +68,138 @@ public class LayoutDevice {
mName = name; mName = name;
} }
/**
* Saves the Layout Device into a document under a given node
* @param doc the document.
* @param parentNode the parent node.
*/
void saveTo(Document doc, Element parentNode) {
// create the device node
Element deviceNode = createNode(doc, parentNode, LayoutDevicesXsd.NODE_DEVICE);
// create the name attribute (no namespace on this one).
deviceNode.setAttribute(LayoutDevicesXsd.ATTR_NAME, mName);
// create a default with the x/y dpi
Element defaultNode = createNode(doc, deviceNode, LayoutDevicesXsd.NODE_DEFAULT);
if (Float.isNaN(mXDpi) == false) {
Element xdpiNode = createNode(doc, defaultNode, LayoutDevicesXsd.NODE_XDPI);
xdpiNode.setTextContent(Float.toString(mXDpi));
}
if (Float.isNaN(mYDpi) == false) {
Element xdpiNode = createNode(doc, defaultNode, LayoutDevicesXsd.NODE_YDPI);
xdpiNode.setTextContent(Float.toString(mYDpi));
}
// then save all the configs.
for (Entry<String, FolderConfiguration> entry : mEditMap.entrySet()) {
saveConfigTo(doc, deviceNode, entry.getKey(), entry.getValue());
}
}
/**
* Creates and returns a new NS-enabled node.
* @param doc the {@link Document}
* @param parentNode the parent node. The new node is appended to this one as a child.
* @param name the name of the node.
* @return the newly created node.
*/
private Element createNode(Document doc, Element parentNode, String name) {
Element newNode = doc.createElementNS(
LayoutDevicesXsd.NS_LAYOUT_DEVICE_XSD, name);
newNode.setPrefix(doc.lookupPrefix(LayoutDevicesXsd.NS_LAYOUT_DEVICE_XSD));
parentNode.appendChild(newNode);
return newNode;
}
/**
* Saves a {@link FolderConfiguration} in a {@link Document}.
* @param doc the Document in which to save
* @param parent the parent node
* @param configName the name of the config
* @param config the config to save
*/
private void saveConfigTo(Document doc, Element parent, String configName,
FolderConfiguration config) {
Element configNode = createNode(doc, parent, LayoutDevicesXsd.NODE_CONFIG);
// create the name attribute (no namespace on this one).
configNode.setAttribute(LayoutDevicesXsd.ATTR_NAME, configName);
// now do the qualifiers
CountryCodeQualifier ccq = config.getCountryCodeQualifier();
if (ccq != null) {
Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_COUNTRY_CODE);
node.setTextContent(Integer.toString(ccq.getCode()));
}
NetworkCodeQualifier ncq = config.getNetworkCodeQualifier();
if (ncq != null) {
Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_NETWORK_CODE);
node.setTextContent(Integer.toString(ncq.getCode()));
}
ScreenSizeQualifier ssq = config.getScreenSizeQualifier();
if (ssq != null) {
Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_SCREEN_SIZE);
node.setTextContent(ssq.getFolderSegment(null));
}
ScreenRatioQualifier srq = config.getScreenRatioQualifier();
if (srq != null) {
Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_SCREEN_RATIO);
node.setTextContent(srq.getFolderSegment(null));
}
ScreenOrientationQualifier soq = config.getScreenOrientationQualifier();
if (soq != null) {
Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_SCREEN_ORIENTATION);
node.setTextContent(soq.getFolderSegment(null));
}
PixelDensityQualifier pdq = config.getPixelDensityQualifier();
if (pdq != null) {
Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_PIXEL_DENSITY);
node.setTextContent(pdq.getFolderSegment(null));
}
TouchScreenQualifier ttq = config.getTouchTypeQualifier();
if (ttq != null) {
Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_TOUCH_TYPE);
node.setTextContent(ttq.getFolderSegment(null));
}
KeyboardStateQualifier ksq = config.getKeyboardStateQualifier();
if (ksq != null) {
Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_KEYBOARD_STATE);
node.setTextContent(ksq.getFolderSegment(null));
}
TextInputMethodQualifier timq = config.getTextInputMethodQualifier();
if (timq != null) {
Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_TEXT_INPUT_METHOD);
node.setTextContent(timq.getFolderSegment(null));
}
NavigationMethodQualifier nmq = config.getNavigationMethodQualifier();
if (nmq != null) {
Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_NAV_METHOD);
node.setTextContent(nmq.getFolderSegment(null));
}
ScreenDimensionQualifier sdq = config.getScreenDimensionQualifier();
if (sdq != null) {
Element sizeNode = createNode(doc, configNode, LayoutDevicesXsd.NODE_SCREEN_DIMENSION);
Element node = createNode(doc, sizeNode, LayoutDevicesXsd.NODE_SIZE);
node.setTextContent(Integer.toString(sdq.getValue1()));
node = createNode(doc, sizeNode, LayoutDevicesXsd.NODE_SIZE);
node.setTextContent(Integer.toString(sdq.getValue2()));
}
}
void addConfig(String name, FolderConfiguration config) { void addConfig(String name, FolderConfiguration config) {
mEditMap.put(name, config); mEditMap.put(name, config);
_seal(); _seal();

View File

@@ -16,9 +16,11 @@
package com.android.ide.eclipse.adt.internal.sdk; package com.android.ide.eclipse.adt.internal.sdk;
import com.android.ide.eclipse.adt.internal.resources.configurations.CountryCodeQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
import com.android.ide.eclipse.adt.internal.resources.configurations.KeyboardStateQualifier; import com.android.ide.eclipse.adt.internal.resources.configurations.KeyboardStateQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationMethodQualifier; import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationMethodQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.NetworkCodeQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier; import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenDimensionQualifier; import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenDimensionQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier; import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier;
@@ -44,7 +46,7 @@ import java.util.List;
/** /**
* {@link DefaultHandler} implementation to parse Layout Device XML file. * {@link DefaultHandler} implementation to parse Layout Device XML file.
* @see LayoutConfigsXsd * @see LayoutDevicesXsd
* @see Layout-configs.xsd * @see Layout-configs.xsd
*/ */
class LayoutDeviceHandler extends DefaultHandler { class LayoutDeviceHandler extends DefaultHandler {
@@ -74,17 +76,17 @@ class LayoutDeviceHandler extends DefaultHandler {
@Override @Override
public void startElement(String uri, String localName, String name, Attributes attributes) public void startElement(String uri, String localName, String name, Attributes attributes)
throws SAXException { throws SAXException {
if (LayoutConfigsXsd.NODE_DEVICE.equals(localName)) { if (LayoutDevicesXsd.NODE_DEVICE.equals(localName)) {
// get the deviceName, will not be null since we validated the XML. // get the deviceName, will not be null since we validated the XML.
String deviceName = attributes.getValue("", LayoutConfigsXsd.ATTR_NAME); String deviceName = attributes.getValue("", LayoutDevicesXsd.ATTR_NAME);
// create a device and add it to the list // create a device and add it to the list
mCurrentDevice = new LayoutDevice(deviceName); mCurrentDevice = new LayoutDevice(deviceName);
mDevices.add(mCurrentDevice); mDevices.add(mCurrentDevice);
} else if (LayoutConfigsXsd.NODE_DEFAULT.equals(localName)) { } else if (LayoutDevicesXsd.NODE_DEFAULT.equals(localName)) {
// create a new default config // create a new default config
mDefaultConfig = mCurrentConfig = new FolderConfiguration(); mDefaultConfig = mCurrentConfig = new FolderConfiguration();
} else if (LayoutConfigsXsd.NODE_CONFIG.equals(localName)) { } else if (LayoutDevicesXsd.NODE_CONFIG.equals(localName)) {
// create a new config // create a new config
mCurrentConfig = new FolderConfiguration(); mCurrentConfig = new FolderConfiguration();
@@ -94,11 +96,11 @@ class LayoutDeviceHandler extends DefaultHandler {
} }
// get the name of the config // get the name of the config
String deviceName = attributes.getValue("", LayoutConfigsXsd.ATTR_NAME); String deviceName = attributes.getValue("", LayoutDevicesXsd.ATTR_NAME);
// give it to the current device. // give it to the current device.
mCurrentDevice.addConfig(deviceName, mCurrentConfig); mCurrentDevice.addConfig(deviceName, mCurrentConfig);
} else if (LayoutConfigsXsd.NODE_SCREEN_DIMENSION.equals(localName)) { } else if (LayoutDevicesXsd.NODE_SCREEN_DIMENSION.equals(localName)) {
mSize1 = mSize2 = null; mSize1 = mSize2 = null;
} }
@@ -112,53 +114,61 @@ class LayoutDeviceHandler extends DefaultHandler {
@Override @Override
public void endElement(String uri, String localName, String name) throws SAXException { public void endElement(String uri, String localName, String name) throws SAXException {
if (LayoutConfigsXsd.NODE_DEVICE.equals(localName)) { if (LayoutDevicesXsd.NODE_DEVICE.equals(localName)) {
mCurrentDevice = null; mCurrentDevice = null;
mDefaultConfig = null; mDefaultConfig = null;
} else if (LayoutConfigsXsd.NODE_CONFIG.equals(localName)) { } else if (LayoutDevicesXsd.NODE_CONFIG.equals(localName)) {
mCurrentConfig = null; mCurrentConfig = null;
} else if (LayoutConfigsXsd.NODE_SCREEN_SIZE.equals(localName)) { } else if (LayoutDevicesXsd.NODE_COUNTRY_CODE.equals(localName)) {
CountryCodeQualifier ccq = new CountryCodeQualifier(
Integer.parseInt(mStringAccumulator.toString()));
mCurrentConfig.setCountryCodeQualifier(ccq);
} else if (LayoutDevicesXsd.NODE_NETWORK_CODE.equals(localName)) {
NetworkCodeQualifier ncq = new NetworkCodeQualifier(
Integer.parseInt(mStringAccumulator.toString()));
mCurrentConfig.setNetworkCodeQualifier(ncq);
} else if (LayoutDevicesXsd.NODE_SCREEN_SIZE.equals(localName)) {
ScreenSizeQualifier ssq = new ScreenSizeQualifier( ScreenSizeQualifier ssq = new ScreenSizeQualifier(
ScreenSize.getEnum(mStringAccumulator.toString())); ScreenSize.getEnum(mStringAccumulator.toString()));
mCurrentConfig.setScreenSizeQualifier(ssq); mCurrentConfig.setScreenSizeQualifier(ssq);
} else if (LayoutConfigsXsd.NODE_SCREEN_RATIO.equals(localName)) { } else if (LayoutDevicesXsd.NODE_SCREEN_RATIO.equals(localName)) {
ScreenRatioQualifier srq = new ScreenRatioQualifier( ScreenRatioQualifier srq = new ScreenRatioQualifier(
ScreenRatio.getEnum(mStringAccumulator.toString())); ScreenRatio.getEnum(mStringAccumulator.toString()));
mCurrentConfig.setScreenRatioQualifier(srq); mCurrentConfig.setScreenRatioQualifier(srq);
} else if (LayoutConfigsXsd.NODE_SCREEN_ORIENTATION.equals(localName)) { } else if (LayoutDevicesXsd.NODE_SCREEN_ORIENTATION.equals(localName)) {
ScreenOrientationQualifier soq = new ScreenOrientationQualifier( ScreenOrientationQualifier soq = new ScreenOrientationQualifier(
ScreenOrientation.getEnum(mStringAccumulator.toString())); ScreenOrientation.getEnum(mStringAccumulator.toString()));
mCurrentConfig.setScreenOrientationQualifier(soq); mCurrentConfig.setScreenOrientationQualifier(soq);
} else if (LayoutConfigsXsd.NODE_PIXEL_DENSITY.equals(localName)) { } else if (LayoutDevicesXsd.NODE_PIXEL_DENSITY.equals(localName)) {
PixelDensityQualifier pdq = new PixelDensityQualifier( PixelDensityQualifier pdq = new PixelDensityQualifier(
Density.getEnum(mStringAccumulator.toString())); Density.getEnum(mStringAccumulator.toString()));
mCurrentConfig.setPixelDensityQualifier(pdq); mCurrentConfig.setPixelDensityQualifier(pdq);
} else if (LayoutConfigsXsd.NODE_TOUCH_TYPE.equals(localName)) { } else if (LayoutDevicesXsd.NODE_TOUCH_TYPE.equals(localName)) {
TouchScreenQualifier tsq = new TouchScreenQualifier( TouchScreenQualifier tsq = new TouchScreenQualifier(
TouchScreenType.getEnum(mStringAccumulator.toString())); TouchScreenType.getEnum(mStringAccumulator.toString()));
mCurrentConfig.setTouchTypeQualifier(tsq); mCurrentConfig.setTouchTypeQualifier(tsq);
} else if (LayoutConfigsXsd.NODE_KEYBOARD_STATE.equals(localName)) { } else if (LayoutDevicesXsd.NODE_KEYBOARD_STATE.equals(localName)) {
KeyboardStateQualifier ksq = new KeyboardStateQualifier( KeyboardStateQualifier ksq = new KeyboardStateQualifier(
KeyboardState.getEnum(mStringAccumulator.toString())); KeyboardState.getEnum(mStringAccumulator.toString()));
mCurrentConfig.setKeyboardStateQualifier(ksq); mCurrentConfig.setKeyboardStateQualifier(ksq);
} else if (LayoutConfigsXsd.NODE_TEXT_INPUT_METHOD.equals(localName)) { } else if (LayoutDevicesXsd.NODE_TEXT_INPUT_METHOD.equals(localName)) {
TextInputMethodQualifier timq = new TextInputMethodQualifier( TextInputMethodQualifier timq = new TextInputMethodQualifier(
TextInputMethod.getEnum(mStringAccumulator.toString())); TextInputMethod.getEnum(mStringAccumulator.toString()));
mCurrentConfig.setTextInputMethodQualifier(timq); mCurrentConfig.setTextInputMethodQualifier(timq);
} else if (LayoutConfigsXsd.NODE_NAV_METHOD.equals(localName)) { } else if (LayoutDevicesXsd.NODE_NAV_METHOD.equals(localName)) {
NavigationMethodQualifier nmq = new NavigationMethodQualifier( NavigationMethodQualifier nmq = new NavigationMethodQualifier(
NavigationMethod.getEnum(mStringAccumulator.toString())); NavigationMethod.getEnum(mStringAccumulator.toString()));
mCurrentConfig.setNavigationMethodQualifier(nmq); mCurrentConfig.setNavigationMethodQualifier(nmq);
} else if (LayoutConfigsXsd.NODE_SCREEN_DIMENSION.equals(localName)) { } else if (LayoutDevicesXsd.NODE_SCREEN_DIMENSION.equals(localName)) {
ScreenDimensionQualifier qual = ScreenDimensionQualifier.getQualifier(mSize1, mSize2); ScreenDimensionQualifier qual = ScreenDimensionQualifier.getQualifier(mSize1, mSize2);
if (qual != null) { if (qual != null) {
mCurrentConfig.setScreenDimensionQualifier(qual); mCurrentConfig.setScreenDimensionQualifier(qual);
} }
} else if (LayoutConfigsXsd.NODE_XDPI.equals(localName)) { } else if (LayoutDevicesXsd.NODE_XDPI.equals(localName)) {
mCurrentDevice.setXDpi(Float.parseFloat(mStringAccumulator.toString())); mCurrentDevice.setXDpi(Float.parseFloat(mStringAccumulator.toString()));
} else if (LayoutConfigsXsd.NODE_YDPI.equals(localName)) { } else if (LayoutDevicesXsd.NODE_YDPI.equals(localName)) {
mCurrentDevice.setYDpi(Float.parseFloat(mStringAccumulator.toString())); mCurrentDevice.setYDpi(Float.parseFloat(mStringAccumulator.toString()));
} else if (LayoutConfigsXsd.NODE_SIZE.equals(localName)) { } else if (LayoutDevicesXsd.NODE_SIZE.equals(localName)) {
if (mSize1 == null) { if (mSize1 == null) {
mSize1 = mStringAccumulator.toString(); mSize1 = mStringAccumulator.toString();
} else if (mSize2 == null) { } else if (mSize2 == null) {

View File

@@ -18,8 +18,12 @@ package com.android.ide.eclipse.adt.internal.sdk;
import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkConstants;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.ErrorHandler; import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
@@ -35,10 +39,17 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Result;
import javax.xml.transform.Source; import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource; import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Validator; import javax.xml.validation.Validator;
@@ -232,11 +243,43 @@ public class LayoutDeviceManager {
} }
} }
void load(String sdkOsLocation) { /**
* Saves the user-made {@link LayoutDevice}s to disk.
*/
public void save() {
try {
String userFolder = AndroidLocation.getFolder();
File deviceXml = new File(userFolder, SdkConstants.FN_DEVICES_XML);
if (deviceXml.isDirectory() == false) {
write(deviceXml, mUserLayoutDevices);
}
} catch (AndroidLocationException e) {
// no user folder? simply don't save the user layout device.
// we could display the error, but it's likely something else did before, as
// nothing will work w/o it.
AdtPlugin.log(e, "Unable to find user directory");
}
}
/**
* Loads the default built-in and user created Layout Devices.
* @param sdkOsLocation location of the SDK.
*/
void loadDefaultAndUserDevices(String sdkOsLocation) {
// load the default devices // load the default devices
loadDefaultLayoutDevices(sdkOsLocation); loadDefaultLayoutDevices(sdkOsLocation);
// load the user devices; // load the user devices;
try {
String userFolder = AndroidLocation.getFolder();
File deviceXml = new File(userFolder, SdkConstants.FN_DEVICES_XML);
if (deviceXml.isFile()) {
parseLayoutDevices(deviceXml, mUserLayoutDevices);
}
} catch (AndroidLocationException e) {
// no user folder? simply don't load the user layout device
AdtPlugin.log(e, "Unable to find user directory");
}
} }
void parseAddOnLayoutDevice(File deviceXml) { void parseAddOnLayoutDevice(File deviceXml) {
@@ -251,6 +294,8 @@ public class LayoutDeviceManager {
/** /**
* Does the actual parsing of a devices.xml file. * Does the actual parsing of a devices.xml file.
* @param deviceXml the {@link File} to load/parse. This must be an existing file.
* @param list the list in which to write the parsed {@link LayoutDevice}.
*/ */
private void parseLayoutDevices(File deviceXml, List<LayoutDevice> list) { private void parseLayoutDevices(File deviceXml, List<LayoutDevice> list) {
// first we validate the XML // first we validate the XML
@@ -259,7 +304,7 @@ public class LayoutDeviceManager {
CaptureErrorHandler errorHandler = new CaptureErrorHandler(deviceXml.getAbsolutePath()); CaptureErrorHandler errorHandler = new CaptureErrorHandler(deviceXml.getAbsolutePath());
Validator validator = LayoutConfigsXsd.getValidator(errorHandler); Validator validator = LayoutDevicesXsd.getValidator(errorHandler);
validator.validate(source); validator.validate(source);
if (errorHandler.foundError() == false) { if (errorHandler.foundError() == false) {
@@ -307,4 +352,45 @@ public class LayoutDeviceManager {
mLayoutDevices = Collections.unmodifiableList(list); mLayoutDevices = Collections.unmodifiableList(list);
} }
/**
* Writes the given {@link LayoutDevice}s into the given file.
* @param deviceXml the file to write.
* @param deviceList the LayoutDevice to write into the file.
*/
private void write(File deviceXml, List<LayoutDevice> deviceList) {
try {
// create a new document
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setNamespaceAware(true);
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.newDocument();
// create a base node
Element baseNode = doc.createElementNS(
LayoutDevicesXsd.NS_LAYOUT_DEVICE_XSD,
LayoutDevicesXsd.NODE_LAYOUT_DEVICES);
// create the prefix for the namespace
baseNode.setPrefix("d");
doc.appendChild(baseNode);
// fill it with the layout devices.
for (LayoutDevice device : deviceList) {
device.saveTo(doc, baseNode);
}
// save the document to disk
// Prepare the DOM document for writing
Source source = new DOMSource(doc);
// Prepare the output file
File file = new File(deviceXml.getAbsolutePath());
Result result = new StreamResult(file);
// Write the DOM document to the file
Transformer xformer = TransformerFactory.newInstance().newTransformer();
xformer.transform(source, result);
} catch (Exception e) {
AdtPlugin.log(e, "Failed to write %s", deviceXml.getAbsolutePath());
}
}
} }

View File

@@ -31,14 +31,14 @@ import javax.xml.validation.Validator;
/** /**
* Public constants for the layout device description XML Schema. * Public constants for the layout device description XML Schema.
*/ */
public class LayoutConfigsXsd { public class LayoutDevicesXsd {
/** The XML namespace of the layout-configs XML. */ /** The XML namespace of the layout-configs XML. */
public static final String NS_LAYOUT_CONFIG_XSD = public static final String NS_LAYOUT_DEVICE_XSD =
"http://schemas.android.com/sdk/android/layout-configs/1"; //$NON-NLS-1$ "http://schemas.android.com/sdk/android/layout-devices/1"; //$NON-NLS-1$
/** /**
* The "layout-configs" element is the root element of this schema. * The "layout-devices" element is the root element of this schema.
* *
* It must contain one or more "device" elements that each define the configurations * It must contain one or more "device" elements that each define the configurations
* available for a given device. * available for a given device.
@@ -46,7 +46,7 @@ public class LayoutConfigsXsd {
* These definitions are used in the Graphical Layout Editor in the * These definitions are used in the Graphical Layout Editor in the
* Android Development Tools (ADT) plugin for Eclipse. * Android Development Tools (ADT) plugin for Eclipse.
*/ */
public static final String NODE_LAYOUT_CONFIGS = "layout-configs"; //$NON-NLS-1$ public static final String NODE_LAYOUT_DEVICES = "layout-devices"; //$NON-NLS-1$
/** /**
* A device element must contain at most one "default" element followed * A device element must contain at most one "default" element followed
@@ -76,6 +76,10 @@ public class LayoutConfigsXsd {
public static final String NODE_CONFIG = "config"; //$NON-NLS-1$ public static final String NODE_CONFIG = "config"; //$NON-NLS-1$
public static final String NODE_COUNTRY_CODE = "country-code"; //$NON-NLS-1$
public static final String NODE_NETWORK_CODE = "network-code"; //$NON-NLS-1$
public static final String NODE_SCREEN_SIZE = "screen-size"; //$NON-NLS-1$ public static final String NODE_SCREEN_SIZE = "screen-size"; //$NON-NLS-1$
public static final String NODE_SCREEN_RATIO = "screen-ratio"; //$NON-NLS-1$ public static final String NODE_SCREEN_RATIO = "screen-ratio"; //$NON-NLS-1$
@@ -111,7 +115,7 @@ public class LayoutConfigsXsd {
* Helper to get an input stream of the layout config XML schema. * Helper to get an input stream of the layout config XML schema.
*/ */
public static InputStream getXsdStream() { public static InputStream getXsdStream() {
return LayoutConfigsXsd.class.getResourceAsStream("layout-configs.xsd"); //$NON-NLS-1$ return LayoutDevicesXsd.class.getResourceAsStream("layout-devices.xsd"); //$NON-NLS-1$
} }
/** Helper method that returns a {@link Validator} for our XSD */ /** Helper method that returns a {@link Validator} for our XSD */

View File

@@ -440,7 +440,7 @@ public class Sdk implements IProjectListener {
SdkConstants.OS_SDK_DOCS_FOLDER); SdkConstants.OS_SDK_DOCS_FOLDER);
// load the built-in and user layout devices // load the built-in and user layout devices
mLayoutDeviceManager.load(mManager.getLocation()); mLayoutDeviceManager.loadDefaultAndUserDevices(mManager.getLocation());
// and the ones from the add-on // and the ones from the add-on
loadLayoutDevices(); loadLayoutDevices();
} }

View File

@@ -15,17 +15,17 @@
* limitations under the License. * limitations under the License.
--> -->
<xsd:schema <xsd:schema
targetNamespace="http://schemas.android.com/sdk/android/layout-configs/1" targetNamespace="http://schemas.android.com/sdk/android/layout-devices/1"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:c="http://schemas.android.com/sdk/android/layout-configs/1" xmlns:c="http://schemas.android.com/sdk/android/layout-devices/1"
elementFormDefault="qualified" elementFormDefault="qualified"
attributeFormDefault="unqualified" attributeFormDefault="unqualified"
version="1"> version="1">
<xsd:element name="layout-configs"> <xsd:element name="layout-devices">
<xsd:annotation> <xsd:annotation>
<xsd:documentation xml:lang="en"> <xsd:documentation xml:lang="en">
The "layout-configs" element is the root element of this schema. The "layout-devices" element is the root element of this schema.
It must contain one or more "device" elements that each define the configurations It must contain one or more "device" elements that each define the configurations
available for a given device. available for a given device.
@@ -37,7 +37,7 @@
<xsd:complexType> <xsd:complexType>
<xsd:sequence> <xsd:sequence>
<!-- layout-configs defines a sequence of 1..n device elements. --> <!-- layout-devices defines a sequence of 1..n device elements. -->
<xsd:element name="device" minOccurs="1" maxOccurs="unbounded"> <xsd:element name="device" minOccurs="1" maxOccurs="unbounded">
<xsd:annotation> <xsd:annotation>
@@ -92,6 +92,34 @@
<xsd:all> <xsd:all>
<!-- parametersType says that 0..1 of each of these elements must be declared. --> <!-- parametersType says that 0..1 of each of these elements must be declared. -->
<xsd:element name="country-code" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies the configuration is for a particular Mobile Country Code.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:float">
<xsd:minInclusive value="100" />
<xsd:maxInclusive value="999" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="network-code" minOccurs="0">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Specifies the configuration is for a particular Mobile Network Code.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:float">
<xsd:minExclusive value="0" />
<xsd:maxExclusive value="1000" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="screen-size" minOccurs="0"> <xsd:element name="screen-size" minOccurs="0">
<xsd:annotation> <xsd:annotation>
<xsd:documentation xml:lang="en"> <xsd:documentation xml:lang="en">

View File

@@ -30,9 +30,9 @@ import javax.xml.validation.Validator;
import junit.framework.TestCase; import junit.framework.TestCase;
/** /**
* Tests local validation of a Layout-Configs sample XMLs using an XML Schema validator. * Tests local validation of a Layout-Devices sample XMLs using an XML Schema validator.
*/ */
public class TestLayoutConfisXsd extends TestCase { public class TestLayoutDevicesXsd extends TestCase {
@Override @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
@@ -117,7 +117,7 @@ public class TestLayoutConfisXsd extends TestCase {
Source source = new StreamSource(new StringReader(document)); Source source = new StreamSource(new StringReader(document));
// don't capture the validator errors, we want it to fail and catch the exception // don't capture the validator errors, we want it to fail and catch the exception
Validator validator = LayoutConfigsXsd.getValidator(null); Validator validator = LayoutDevicesXsd.getValidator(null);
try { try {
validator.validate(source); validator.validate(source);
} catch (SAXParseException e) { } catch (SAXParseException e) {
@@ -133,7 +133,7 @@ public class TestLayoutConfisXsd extends TestCase {
Source source = new StreamSource(new StringReader(document)); Source source = new StreamSource(new StringReader(document));
CaptureErrorHandler handler = new CaptureErrorHandler(); CaptureErrorHandler handler = new CaptureErrorHandler();
Validator validator = LayoutConfigsXsd.getValidator(null); Validator validator = LayoutDevicesXsd.getValidator(null);
validator.validate(source); validator.validate(source);
handler.verify(); handler.verify();
} }
@@ -144,11 +144,11 @@ public class TestLayoutConfisXsd extends TestCase {
public void testValidateLocalRepositoryFile() throws Exception { public void testValidateLocalRepositoryFile() throws Exception {
InputStream xmlStream = InputStream xmlStream =
TestLayoutConfisXsd.class.getResourceAsStream("config_sample.xml"); TestLayoutDevicesXsd.class.getResourceAsStream("config_sample.xml");
Source source = new StreamSource(xmlStream); Source source = new StreamSource(xmlStream);
CaptureErrorHandler handler = new CaptureErrorHandler(); CaptureErrorHandler handler = new CaptureErrorHandler();
Validator validator = LayoutConfigsXsd.getValidator(handler); Validator validator = LayoutDevicesXsd.getValidator(handler);
validator.validate(source); validator.validate(source);
handler.verify(); handler.verify();
} }
@@ -168,9 +168,9 @@ public class TestLayoutConfisXsd extends TestCase {
checkFailure( checkFailure(
// document // document
"<?xml version=\"1.0\"?>" + "<?xml version=\"1.0\"?>" +
"<d:layout-configs xmlns:d=\"http://schemas.android.com/sdk/android/layout-configs/1\" >" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" +
"<d:unknown />" + "<d:unknown />" +
"</d:layout-configs>", "</d:layout-devices>",
// expected failure // expected failure
"cvc-complex-type.2.4.a: Invalid content was found.*"); "cvc-complex-type.2.4.a: Invalid content was found.*");
@@ -181,9 +181,9 @@ public class TestLayoutConfisXsd extends TestCase {
checkFailure( checkFailure(
// document // document
"<?xml version=\"1.0\"?>" + "<?xml version=\"1.0\"?>" +
"<d:layout-configs xmlns:d=\"http://schemas.android.com/sdk/android/layout-configs/1\" >" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" +
"<d:device />" + "<d:device />" +
"</d:layout-configs>", "</d:layout-devices>",
// expected failure // expected failure
"cvc-complex-type.4: Attribute 'name' must appear on element 'd:device'."); "cvc-complex-type.4: Attribute 'name' must appear on element 'd:device'.");
@@ -194,10 +194,10 @@ public class TestLayoutConfisXsd extends TestCase {
checkFailure( checkFailure(
// document // document
"<?xml version=\"1.0\"?>" + "<?xml version=\"1.0\"?>" +
"<d:layout-configs xmlns:d=\"http://schemas.android.com/sdk/android/layout-configs/1\" />", "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" />",
// expected failure // expected failure
"cvc-complex-type.2.4.b: The content of element 'd:layout-configs' is not complete.*"); "cvc-complex-type.2.4.b: The content of element 'd:layout-devices' is not complete.*");
} }
/** A document with an empty device element is not valid. */ /** A document with an empty device element is not valid. */
@@ -205,9 +205,9 @@ public class TestLayoutConfisXsd extends TestCase {
checkFailure( checkFailure(
// document // document
"<?xml version=\"1.0\"?>" + "<?xml version=\"1.0\"?>" +
"<d:layout-configs xmlns:d=\"http://schemas.android.com/sdk/android/layout-configs/1\" >" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" +
"<d:device name=\"foo\"/>" + "<d:device name=\"foo\"/>" +
"</d:layout-configs>", "</d:layout-devices>",
// expected failure // expected failure
"cvc-complex-type.2.4.b: The content of element 'd:device' is not complete.*"); "cvc-complex-type.2.4.b: The content of element 'd:device' is not complete.*");
@@ -218,12 +218,12 @@ public class TestLayoutConfisXsd extends TestCase {
checkFailure( checkFailure(
// document // document
"<?xml version=\"1.0\"?>" + "<?xml version=\"1.0\"?>" +
"<d:layout-configs xmlns:d=\"http://schemas.android.com/sdk/android/layout-configs/1\" >" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" +
"<d:device name=\"foo\">" + "<d:device name=\"foo\">" +
" <d:default />" + " <d:default />" +
" <d:default />" + " <d:default />" +
"</d:device>" + "</d:device>" +
"</d:layout-configs>", "</d:layout-devices>",
// expected failure // expected failure
"cvc-complex-type.2.4.a: Invalid content was found starting with element 'd:default'.*"); "cvc-complex-type.2.4.a: Invalid content was found starting with element 'd:default'.*");
@@ -234,12 +234,12 @@ public class TestLayoutConfisXsd extends TestCase {
checkFailure( checkFailure(
// document // document
"<?xml version=\"1.0\"?>" + "<?xml version=\"1.0\"?>" +
"<d:layout-configs xmlns:d=\"http://schemas.android.com/sdk/android/layout-configs/1\" >" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" +
"<d:device name=\"foo\">" + "<d:device name=\"foo\">" +
" <d:config name=\"must-be-after-default\" />" + " <d:config name=\"must-be-after-default\" />" +
" <d:default />" + " <d:default />" +
"</d:device>" + "</d:device>" +
"</d:layout-configs>", "</d:layout-devices>",
// expected failure // expected failure
"cvc-complex-type.2.4.a: Invalid content was found starting with element 'd:default'.*"); "cvc-complex-type.2.4.a: Invalid content was found starting with element 'd:default'.*");
@@ -250,13 +250,13 @@ public class TestLayoutConfisXsd extends TestCase {
checkFailure( checkFailure(
// document // document
"<?xml version=\"1.0\"?>" + "<?xml version=\"1.0\"?>" +
"<d:layout-configs xmlns:d=\"http://schemas.android.com/sdk/android/layout-configs/1\" >" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" +
"<d:device name=\"foo\">" + "<d:device name=\"foo\">" +
" <d:default>" + " <d:default>" +
" <d:screen-dimension> <d:size>0</d:size> <d:size>1</d:size> </d:screen-dimension>" + " <d:screen-dimension> <d:size>0</d:size> <d:size>1</d:size> </d:screen-dimension>" +
" </d:default>" + " </d:default>" +
"</d:device>" + "</d:device>" +
"</d:layout-configs>", "</d:layout-devices>",
// expected failure // expected failure
"cvc-minInclusive-valid: Value '0' is not facet-valid with respect to minInclusive '1'.*"); "cvc-minInclusive-valid: Value '0' is not facet-valid with respect to minInclusive '1'.*");
@@ -267,13 +267,13 @@ public class TestLayoutConfisXsd extends TestCase {
checkFailure( checkFailure(
// document // document
"<?xml version=\"1.0\"?>" + "<?xml version=\"1.0\"?>" +
"<d:layout-configs xmlns:d=\"http://schemas.android.com/sdk/android/layout-configs/1\" >" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" +
"<d:device name=\"foo\">" + "<d:device name=\"foo\">" +
" <d:default>" + " <d:default>" +
" <d:screen-dimension> <d:size>-5</d:size> <d:size>1</d:size> </d:screen-dimension>" + " <d:screen-dimension> <d:size>-5</d:size> <d:size>1</d:size> </d:screen-dimension>" +
" </d:default>" + " </d:default>" +
"</d:device>" + "</d:device>" +
"</d:layout-configs>", "</d:layout-devices>",
// expected failure // expected failure
"cvc-minInclusive-valid: Value '-5' is not facet-valid with respect to minInclusive '1'.*"); "cvc-minInclusive-valid: Value '-5' is not facet-valid with respect to minInclusive '1'.*");
@@ -284,13 +284,13 @@ public class TestLayoutConfisXsd extends TestCase {
checkFailure( checkFailure(
// document // document
"<?xml version=\"1.0\"?>" + "<?xml version=\"1.0\"?>" +
"<d:layout-configs xmlns:d=\"http://schemas.android.com/sdk/android/layout-configs/1\" >" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" +
"<d:device name=\"foo\">" + "<d:device name=\"foo\">" +
" <d:default>" + " <d:default>" +
" <d:xdpi>0</d:xdpi>" + " <d:xdpi>0</d:xdpi>" +
" </d:default>" + " </d:default>" +
"</d:device>" + "</d:device>" +
"</d:layout-configs>", "</d:layout-devices>",
// expected failure // expected failure
"cvc-minExclusive-valid: Value '0' is not facet-valid with respect to minExclusive '0.0E1'.*"); "cvc-minExclusive-valid: Value '0' is not facet-valid with respect to minExclusive '0.0E1'.*");
@@ -302,13 +302,13 @@ public class TestLayoutConfisXsd extends TestCase {
checkFailure( checkFailure(
// document // document
"<?xml version=\"1.0\"?>" + "<?xml version=\"1.0\"?>" +
"<d:layout-configs xmlns:d=\"http://schemas.android.com/sdk/android/layout-configs/1\" >" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" +
"<d:device name=\"foo\">" + "<d:device name=\"foo\">" +
" <d:default>" + " <d:default>" +
" <d:xdpi>-3.1415926538</d:xdpi>" + " <d:xdpi>-3.1415926538</d:xdpi>" +
" </d:default>" + " </d:default>" +
"</d:device>" + "</d:device>" +
"</d:layout-configs>", "</d:layout-devices>",
// expected failure // expected failure
"cvc-minExclusive-valid: Value '-3.1415926538' is not facet-valid with respect to minExclusive '0.0E1'.*"); "cvc-minExclusive-valid: Value '-3.1415926538' is not facet-valid with respect to minExclusive '0.0E1'.*");
@@ -319,13 +319,13 @@ public class TestLayoutConfisXsd extends TestCase {
checkSuccess( checkSuccess(
// document // document
"<?xml version=\"1.0\"?>" + "<?xml version=\"1.0\"?>" +
"<d:layout-configs xmlns:d=\"http://schemas.android.com/sdk/android/layout-configs/1\" >" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" +
"<d:device name=\"foo\">" + "<d:device name=\"foo\">" +
" <d:config name='foo'>" + " <d:config name='foo'>" +
" <d:screen-ratio> \n long \r </d:screen-ratio>" + " <d:screen-ratio> \n long \r </d:screen-ratio>" +
" </d:config>" + " </d:config>" +
"</d:device>" + "</d:device>" +
"</d:layout-configs>"); "</d:layout-devices>");
} }
} }

View File

@@ -14,9 +14,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
--> -->
<d:layout-configs <d:layout-devices
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:d="http://schemas.android.com/sdk/android/layout-configs/1"> xmlns:d="http://schemas.android.com/sdk/android/layout-devices/1">
<d:device name="MyDevice"> <!-- 1..n --> <d:device name="MyDevice"> <!-- 1..n -->
<d:default> <!-- 0..1 --> <d:default> <!-- 0..1 -->
@@ -134,4 +134,4 @@
</d:config> </d:config>
</d:device> </d:device>
</d:layout-configs> </d:layout-devices>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<d:layout-configs <d:layout-devices
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:d="http://schemas.android.com/sdk/android/layout-configs/1"> xmlns:d="http://schemas.android.com/sdk/android/layout-devices/1">
<d:device name="ADP1"> <d:device name="ADP1">
<d:default> <d:default>
@@ -59,4 +59,4 @@
<d:screen-orientation>land</d:screen-orientation> <d:screen-orientation>land</d:screen-orientation>
</d:config> </d:config>
</d:device> </d:device>
</d:layout-configs> </d:layout-devices>