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.CountryCodeQualifier; 20 import com.android.ide.common.resources.configuration.DensityQualifier; 21 import com.android.ide.common.resources.configuration.FolderConfiguration; 22 import com.android.ide.common.resources.configuration.KeyboardStateQualifier; 23 import com.android.ide.common.resources.configuration.NavigationMethodQualifier; 24 import com.android.ide.common.resources.configuration.NavigationStateQualifier; 25 import com.android.ide.common.resources.configuration.NetworkCodeQualifier; 26 import com.android.ide.common.resources.configuration.ScreenDimensionQualifier; 27 import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; 28 import com.android.ide.common.resources.configuration.ScreenRatioQualifier; 29 import com.android.ide.common.resources.configuration.ScreenSizeQualifier; 30 import com.android.ide.common.resources.configuration.TextInputMethodQualifier; 31 import com.android.ide.common.resources.configuration.TouchScreenQualifier; 32 33 import org.w3c.dom.Document; 34 import org.w3c.dom.Element; 35 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.List; 39 40 /** 41 * Class representing a layout device. 42 * 43 * A Layout device is a collection of {@link FolderConfiguration} that can be used to render Android 44 * layout files. 45 * 46 * It also contains a single xdpi/ydpi that is independent of the {@link FolderConfiguration}. 47 * 48 * If the device is meant to represent a true device, then most of the FolderConfigurations' content 49 * should be identical, with only a few qualifiers (orientation, keyboard state) that would differ. 50 * However it is simpler to reuse the FolderConfiguration class (with the non changing qualifiers 51 * duplicated in each configuration) as it's what's being used by the rendering library. 52 * 53 * To create, edit and delete LayoutDevice objects, see {@link LayoutDeviceManager}. 54 * The class is not technically immutable but behaves as such outside of its package. 55 */ 56 public class LayoutDevice { 57 58 private final String mName; 59 60 /** 61 * Wrapper around a {@link FolderConfiguration}. 62 * <p/>This adds a name, accessible through {@link #getName()}. 63 * <p/>The folder config can be accessed through {@link #getConfig()}. 64 * 65 */ 66 public final static class DeviceConfig { 67 private final String mName; 68 private final FolderConfiguration mConfig; 69 70 DeviceConfig(String name, FolderConfiguration config) { 71 mName = name; 72 mConfig = config; 73 } 74 75 public String getName() { 76 return mName; 77 } 78 79 public FolderConfiguration getConfig() { 80 return mConfig; 81 } 82 } 83 84 /** editable list of the config */ 85 private final ArrayList<DeviceConfig> mConfigs = new ArrayList<DeviceConfig>(); 86 /** Read-only list */ 87 private List<DeviceConfig> mROList; 88 89 private float mXDpi = Float.NaN; 90 private float mYDpi = Float.NaN; 91 92 LayoutDevice(String name) { 93 mName = name; 94 } 95 96 /** 97 * Saves the Layout Device into a document under a given node 98 * @param doc the document. 99 * @param parentNode the parent node. 100 */ 101 void saveTo(Document doc, Element parentNode) { 102 // create the device node 103 Element deviceNode = createNode(doc, parentNode, LayoutDevicesXsd.NODE_DEVICE); 104 105 // create the name attribute (no namespace on this one). 106 deviceNode.setAttribute(LayoutDevicesXsd.ATTR_NAME, mName); 107 108 // create a default with the x/y dpi 109 Element defaultNode = createNode(doc, deviceNode, LayoutDevicesXsd.NODE_DEFAULT); 110 if (Float.isNaN(mXDpi) == false) { 111 Element xdpiNode = createNode(doc, defaultNode, LayoutDevicesXsd.NODE_XDPI); 112 xdpiNode.setTextContent(Float.toString(mXDpi)); 113 } 114 if (Float.isNaN(mYDpi) == false) { 115 Element xdpiNode = createNode(doc, defaultNode, LayoutDevicesXsd.NODE_YDPI); 116 xdpiNode.setTextContent(Float.toString(mYDpi)); 117 } 118 119 // then save all the configs. 120 synchronized (mConfigs) { 121 for (DeviceConfig config : mConfigs) { 122 saveConfigTo(doc, deviceNode, config.getName(), config.getConfig()); 123 } 124 } 125 } 126 127 /** 128 * Creates and returns a new NS-enabled node. 129 * @param doc the {@link Document} 130 * @param parentNode the parent node. The new node is appended to this one as a child. 131 * @param name the name of the node. 132 * @return the newly created node. 133 */ 134 private Element createNode(Document doc, Element parentNode, String name) { 135 Element newNode = doc.createElementNS( 136 LayoutDevicesXsd.NS_LAYOUT_DEVICE_XSD, name); 137 newNode.setPrefix(doc.lookupPrefix(LayoutDevicesXsd.NS_LAYOUT_DEVICE_XSD)); 138 parentNode.appendChild(newNode); 139 140 return newNode; 141 } 142 143 /** 144 * Saves a {@link FolderConfiguration} in a {@link Document}. 145 * @param doc the Document in which to save 146 * @param parent the parent node 147 * @param configName the name of the config 148 * @param config the config to save 149 */ 150 private void saveConfigTo(Document doc, Element parent, String configName, 151 FolderConfiguration config) { 152 Element configNode = createNode(doc, parent, LayoutDevicesXsd.NODE_CONFIG); 153 154 // create the name attribute (no namespace on this one). 155 configNode.setAttribute(LayoutDevicesXsd.ATTR_NAME, configName); 156 157 // now do the qualifiers 158 CountryCodeQualifier ccq = config.getCountryCodeQualifier(); 159 if (ccq != null) { 160 Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_COUNTRY_CODE); 161 node.setTextContent(Integer.toString(ccq.getCode())); 162 } 163 164 NetworkCodeQualifier ncq = config.getNetworkCodeQualifier(); 165 if (ncq != null) { 166 Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_NETWORK_CODE); 167 node.setTextContent(Integer.toString(ncq.getCode())); 168 } 169 170 ScreenSizeQualifier slsq = config.getScreenSizeQualifier(); 171 if (slsq != null) { 172 Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_SCREEN_SIZE); 173 node.setTextContent(slsq.getFolderSegment()); 174 } 175 176 ScreenRatioQualifier srq = config.getScreenRatioQualifier(); 177 if (srq != null) { 178 Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_SCREEN_RATIO); 179 node.setTextContent(srq.getFolderSegment()); 180 } 181 182 ScreenOrientationQualifier soq = config.getScreenOrientationQualifier(); 183 if (soq != null) { 184 Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_SCREEN_ORIENTATION); 185 node.setTextContent(soq.getFolderSegment()); 186 } 187 188 DensityQualifier dq = config.getDensityQualifier(); 189 if (dq != null) { 190 Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_PIXEL_DENSITY); 191 node.setTextContent(dq.getFolderSegment()); 192 } 193 194 TouchScreenQualifier ttq = config.getTouchTypeQualifier(); 195 if (ttq != null) { 196 Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_TOUCH_TYPE); 197 node.setTextContent(ttq.getFolderSegment()); 198 } 199 200 KeyboardStateQualifier ksq = config.getKeyboardStateQualifier(); 201 if (ksq != null) { 202 Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_KEYBOARD_STATE); 203 node.setTextContent(ksq.getFolderSegment()); 204 } 205 206 TextInputMethodQualifier timq = config.getTextInputMethodQualifier(); 207 if (timq != null) { 208 Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_TEXT_INPUT_METHOD); 209 node.setTextContent(timq.getFolderSegment()); 210 } 211 212 NavigationStateQualifier nsq = config.getNavigationStateQualifier(); 213 if (nsq != null) { 214 Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_NAV_STATE); 215 node.setTextContent(nsq.getFolderSegment()); 216 } 217 218 NavigationMethodQualifier nmq = config.getNavigationMethodQualifier(); 219 if (nmq != null) { 220 Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_NAV_METHOD); 221 node.setTextContent(nmq.getFolderSegment()); 222 } 223 224 ScreenDimensionQualifier sdq = config.getScreenDimensionQualifier(); 225 if (sdq != null) { 226 Element sizeNode = createNode(doc, configNode, LayoutDevicesXsd.NODE_SCREEN_DIMENSION); 227 228 Element node = createNode(doc, sizeNode, LayoutDevicesXsd.NODE_SIZE); 229 node.setTextContent(Integer.toString(sdq.getValue1())); 230 231 node = createNode(doc, sizeNode, LayoutDevicesXsd.NODE_SIZE); 232 node.setTextContent(Integer.toString(sdq.getValue2())); 233 } 234 } 235 236 /** 237 * Adds config to the LayoutDevice. 238 * <p/>This ensures that no two configurations have the same. If a config already exists 239 * with the same name, the new config replaces it. 240 * 241 * @param name the name of the config. 242 * @param config the config. 243 */ 244 void addConfig(String name, FolderConfiguration config) { 245 synchronized (mConfigs) { 246 doAddConfig(name, config); 247 seal(); 248 } 249 } 250 251 /** 252 * Adds a list of config to the LayoutDevice 253 * <p/>This ensures that no two configurations have the same. If a config already exists 254 * with the same name, the new config replaces it. 255 256 * @param configs the configs to add. 257 */ 258 void addConfigs(List<DeviceConfig> configs) { 259 synchronized (mConfigs) { 260 // add the configs manually one by one, to check for no duplicate. 261 for (DeviceConfig config : configs) { 262 String name = config.getName(); 263 264 for (DeviceConfig c : mConfigs) { 265 if (c.getName().equals(name)) { 266 mConfigs.remove(c); 267 break; 268 } 269 } 270 271 mConfigs.add(config); 272 } 273 274 seal(); 275 } 276 } 277 278 /** 279 * Removes a config by its name. 280 * @param name the name of the config to remove. 281 */ 282 void removeConfig(String name) { 283 synchronized (mConfigs) { 284 for (DeviceConfig config : mConfigs) { 285 if (config.getName().equals(name)) { 286 mConfigs.remove(config); 287 seal(); 288 return; 289 } 290 } 291 } 292 } 293 294 /** 295 * Adds config to the LayoutDevice. This is to be used to add plenty of 296 * configurations. It must be followed by {@link #_seal()}. 297 * <p/>This ensures that no two configurations have the same. If a config already exists 298 * with the same name, the new config replaces it. 299 * <p/><strong>This must be called inside a <code>synchronized(mConfigs)</code> block.</strong> 300 * 301 * @param name the name of the config 302 * @param config the config. 303 */ 304 private void doAddConfig(String name, FolderConfiguration config) { 305 // remove config that would have the same name to ensure no duplicate 306 for (DeviceConfig c : mConfigs) { 307 if (c.getName().equals(name)) { 308 mConfigs.remove(c); 309 break; 310 } 311 } 312 mConfigs.add(new DeviceConfig(name, config)); 313 } 314 315 /** 316 * Seals the layout device by setting up {@link #mROList}. 317 * <p/><strong>This must be called inside a <code>synchronized(mConfigs)</code> block.</strong> 318 */ 319 private void seal() { 320 mROList = Collections.unmodifiableList(mConfigs); 321 } 322 323 void setXDpi(float xdpi) { 324 mXDpi = xdpi; 325 } 326 327 void setYDpi(float ydpi) { 328 mYDpi = ydpi; 329 } 330 331 public String getName() { 332 return mName; 333 } 334 335 /** 336 * Returns an unmodifiable list of all the {@link DeviceConfig}. 337 */ 338 public List<DeviceConfig> getConfigs() { 339 synchronized (mConfigs) { 340 return mROList; 341 } 342 } 343 344 /** 345 * Returns a {@link DeviceConfig} by its name. 346 */ 347 public DeviceConfig getDeviceConfigByName(String name) { 348 synchronized (mConfigs) { 349 for (DeviceConfig config : mConfigs) { 350 if (config.getName().equals(name)) { 351 return config; 352 } 353 } 354 } 355 356 return null; 357 } 358 359 /** 360 * Returns a {@link FolderConfiguration} by its name. 361 */ 362 public FolderConfiguration getFolderConfigByName(String name) { 363 synchronized (mConfigs) { 364 for (DeviceConfig config : mConfigs) { 365 if (config.getName().equals(name)) { 366 return config.getConfig(); 367 } 368 } 369 } 370 371 return null; 372 } 373 374 /** 375 * Returns the dpi of the Device screen in X. 376 * @return the dpi of screen or {@link Float#NaN} if it's not set. 377 */ 378 public float getXDpi() { 379 return mXDpi; 380 } 381 382 /** 383 * Returns the dpi of the Device screen in Y. 384 * @return the dpi of screen or {@link Float#NaN} if it's not set. 385 */ 386 public float getYDpi() { 387 return mYDpi; 388 } 389 } 390