1 /* 2 * Copyright (C) 2008 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.editors.layout.descriptors; 18 19 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; 20 import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS; 21 import static com.android.ide.common.layout.LayoutConstants.ATTR_NAME; 22 import static com.android.ide.common.layout.LayoutConstants.ATTR_TAG; 23 import static com.android.ide.common.layout.LayoutConstants.FQCN_GESTURE_OVERLAY_VIEW; 24 25 import com.android.ide.common.api.IAttributeInfo.Format; 26 import com.android.ide.common.resources.platform.AttributeInfo; 27 import com.android.ide.common.resources.platform.DeclareStyleableInfo; 28 import com.android.ide.common.resources.platform.ViewClassInfo; 29 import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo; 30 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; 31 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; 32 import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; 33 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; 34 import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; 35 import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor; 36 import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor; 37 import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.ClassAttributeDescriptor; 38 import com.android.sdklib.IAndroidTarget; 39 import com.android.sdklib.SdkConstants; 40 41 import java.util.ArrayList; 42 import java.util.Collection; 43 import java.util.Collections; 44 import java.util.HashMap; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Map.Entry; 48 49 50 /** 51 * Complete description of the layout structure. 52 */ 53 public final class LayoutDescriptors implements IDescriptorProvider { 54 55 /** 56 * The XML name of the special {@code <include>} layout tag. 57 * A synthetic element with that name is created as part of the view descriptors list 58 * returned by {@link #getViewDescriptors()}. 59 */ 60 public static final String VIEW_INCLUDE = "include"; //$NON-NLS-1$ 61 62 /** 63 * The XML name of the special {@code <merge>} layout tag. 64 * A synthetic element with that name is created as part of the view descriptors list 65 * returned by {@link #getViewDescriptors()}. 66 */ 67 public static final String VIEW_MERGE = "merge"; //$NON-NLS-1$ 68 69 /** 70 * The XML name of the special {@code <fragment>} layout tag. 71 * A synthetic element with that name is created as part of the view descriptors list 72 * returned by {@link #getViewDescriptors()}. 73 */ 74 public static final String VIEW_FRAGMENT = "fragment"; //$NON-NLS-1$ 75 76 /** 77 * The XML name of the special {@code <view>} layout tag. This is used to add generic 78 * views with a class attribute to specify the view. 79 * <p> 80 * TODO: We should add a synthetic descriptor for this, similar to our descriptors for 81 * include, merge and requestFocus. 82 */ 83 public static final String VIEW_VIEWTAG = "view"; //$NON-NLS-1$ 84 85 /** 86 * The XML name of the special {@code <requestFocus>} layout tag. 87 * A synthetic element with that name is created as part of the view descriptors list 88 * returned by {@link #getViewDescriptors()}. 89 */ 90 public static final String REQUEST_FOCUS = "requestFocus";//$NON-NLS-1$ 91 92 /** 93 * The attribute name of the include tag's url naming the resource to be inserted 94 * <p> 95 * <b>NOTE</b>: The layout attribute is NOT in the Android namespace! 96 */ 97 public static final String ATTR_LAYOUT = "layout"; //$NON-NLS-1$ 98 99 // Public attributes names, attributes descriptors and elements descriptors 100 public static final String ID_ATTR = "id"; //$NON-NLS-1$ 101 102 /** The document descriptor. Contains all layouts and views linked together. */ 103 private DocumentDescriptor mRootDescriptor = 104 new DocumentDescriptor("layout_doc", null); //$NON-NLS-1$ 105 106 /** The list of all known ViewLayout descriptors. */ 107 private List<ViewElementDescriptor> mLayoutDescriptors = Collections.emptyList(); 108 109 /** Read-Only list of View Descriptors. */ 110 private List<ViewElementDescriptor> mROLayoutDescriptors; 111 112 /** The list of all known View (not ViewLayout) descriptors. */ 113 private List<ViewElementDescriptor> mViewDescriptors = Collections.emptyList(); 114 115 /** Read-Only list of View Descriptors. */ 116 private List<ViewElementDescriptor> mROViewDescriptors; 117 118 /** The descriptor matching android.view.View. */ 119 private ViewElementDescriptor mBaseViewDescriptor; 120 121 /** Map from view full class name to view descriptor */ 122 private Map<String, ViewElementDescriptor> mFqcnToDescriptor = 123 // As of 3.1 there are 58 items in this map 124 new HashMap<String, ViewElementDescriptor>(80); 125 126 /** Returns the document descriptor. Contains all layouts and views linked together. */ 127 @Override 128 public DocumentDescriptor getDescriptor() { 129 return mRootDescriptor; 130 } 131 132 /** Returns the read-only list of all known ViewLayout descriptors. */ 133 public List<ViewElementDescriptor> getLayoutDescriptors() { 134 return mROLayoutDescriptors; 135 } 136 137 /** Returns the read-only list of all known View (not ViewLayout) descriptors. */ 138 public List<ViewElementDescriptor> getViewDescriptors() { 139 return mROViewDescriptors; 140 } 141 142 @Override 143 public ElementDescriptor[] getRootElementDescriptors() { 144 return mRootDescriptor.getChildren(); 145 } 146 147 /** 148 * Returns the descriptor matching android.view.View, which is guaranteed 149 * to be a {@link ViewElementDescriptor}. 150 */ 151 public ViewElementDescriptor getBaseViewDescriptor() { 152 if (mBaseViewDescriptor == null) { 153 mBaseViewDescriptor = findDescriptorByClass(SdkConstants.CLASS_VIEW); 154 } 155 return mBaseViewDescriptor; 156 } 157 158 /** 159 * Updates the document descriptor. 160 * <p/> 161 * It first computes the new children of the descriptor and then update them 162 * all at once. 163 * <p/> 164 * TODO: differentiate groups from views in the tree UI? => rely on icons 165 * <p/> 166 * 167 * @param views The list of views in the framework. 168 * @param layouts The list of layouts in the framework. 169 * @param styleMap A map from style names to style information provided by the SDK 170 * @param target The android target being initialized 171 */ 172 public synchronized void updateDescriptors(ViewClassInfo[] views, ViewClassInfo[] layouts, 173 Map<String, DeclareStyleableInfo> styleMap, IAndroidTarget target) { 174 175 // This map links every ViewClassInfo to the ElementDescriptor we created. 176 // It is filled by convertView() and used later to fix the super-class hierarchy. 177 HashMap<ViewClassInfo, ViewElementDescriptor> infoDescMap = 178 new HashMap<ViewClassInfo, ViewElementDescriptor>(); 179 180 ArrayList<ViewElementDescriptor> newViews = new ArrayList<ViewElementDescriptor>(40); 181 if (views != null) { 182 for (ViewClassInfo info : views) { 183 ViewElementDescriptor desc = convertView(info, infoDescMap); 184 newViews.add(desc); 185 mFqcnToDescriptor.put(desc.getFullClassName(), desc); 186 } 187 } 188 189 // Create <include> as a synthetic regular view. 190 // Note: ViewStub is already described by attrs.xml 191 insertInclude(newViews); 192 193 List<ViewElementDescriptor> newLayouts = new ArrayList<ViewElementDescriptor>(30); 194 if (layouts != null) { 195 for (ViewClassInfo info : layouts) { 196 ViewElementDescriptor desc = convertView(info, infoDescMap); 197 newLayouts.add(desc); 198 mFqcnToDescriptor.put(desc.getFullClassName(), desc); 199 } 200 } 201 202 // Find View and inherit all its layout attributes 203 AttributeDescriptor[] frameLayoutAttrs = findViewLayoutAttributes( 204 SdkConstants.CLASS_FRAMELAYOUT); 205 206 if (target.getVersion().getApiLevel() >= 4) { 207 ViewElementDescriptor fragmentTag = createFragment(frameLayoutAttrs, styleMap); 208 newViews.add(fragmentTag); 209 } 210 211 List<ElementDescriptor> newDescriptors = new ArrayList<ElementDescriptor>(80); 212 newDescriptors.addAll(newLayouts); 213 newDescriptors.addAll(newViews); 214 215 // Link all layouts to everything else here.. recursively 216 for (ViewElementDescriptor layoutDesc : newLayouts) { 217 layoutDesc.setChildren(newDescriptors); 218 } 219 220 // The gesture overlay descriptor is really a layout but not included in the layouts list 221 // so handle it specially 222 ViewElementDescriptor gestureView = findDescriptorByClass(FQCN_GESTURE_OVERLAY_VIEW); 223 if (gestureView != null) { 224 gestureView.setChildren(newDescriptors); 225 // Inherit layout attributes from FrameLayout 226 gestureView.setLayoutAttributes(frameLayoutAttrs); 227 } 228 229 fixSuperClasses(infoDescMap); 230 231 ViewElementDescriptor requestFocus = createRequestFocus(); 232 newViews.add(requestFocus); 233 newDescriptors.add(requestFocus); 234 235 // The <merge> tag can only be a root tag, so it is added at the end. 236 // It gets everything else as children but it is not made a child itself. 237 ViewElementDescriptor mergeTag = createMerge(frameLayoutAttrs); 238 mergeTag.setChildren(newDescriptors); // mergeTag makes a copy of the list 239 newDescriptors.add(mergeTag); 240 newLayouts.add(mergeTag); 241 242 // Sort palette contents 243 Collections.sort(newViews); 244 Collections.sort(newLayouts); 245 246 mViewDescriptors = newViews; 247 mLayoutDescriptors = newLayouts; 248 mRootDescriptor.setChildren(newDescriptors); 249 250 mBaseViewDescriptor = null; 251 mROLayoutDescriptors = Collections.unmodifiableList(mLayoutDescriptors); 252 mROViewDescriptors = Collections.unmodifiableList(mViewDescriptors); 253 } 254 255 /** 256 * Creates an element descriptor from a given {@link ViewClassInfo}. 257 * 258 * @param info The {@link ViewClassInfo} to convert into a new {@link ViewElementDescriptor}. 259 * @param infoDescMap This map links every ViewClassInfo to the ElementDescriptor it created. 260 * It is filled by here and used later to fix the super-class hierarchy. 261 */ 262 private ViewElementDescriptor convertView( 263 ViewClassInfo info, 264 HashMap<ViewClassInfo, ViewElementDescriptor> infoDescMap) { 265 String xmlName = info.getShortClassName(); 266 String uiName = xmlName; 267 String fqcn = info.getFullClassName(); 268 if (ViewElementDescriptor.viewNeedsPackage(fqcn)) { 269 xmlName = fqcn; 270 } 271 String tooltip = info.getJavaDoc(); 272 273 // Average is around 90, max (in 3.2) is 145 274 ArrayList<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>(120); 275 276 // All views and groups have an implicit "style" attribute which is a reference. 277 AttributeInfo styleInfo = new AttributeInfo( 278 "style", //$NON-NLS-1$ xmlLocalName 279 Format.REFERENCE_SET); 280 styleInfo.setJavaDoc("A reference to a custom style"); //tooltip 281 DescriptorsUtils.appendAttribute(attributes, 282 "style", //$NON-NLS-1$ 283 null, //nsUri 284 styleInfo, 285 false, //required 286 null); // overrides 287 styleInfo.setDefinedBy(SdkConstants.CLASS_VIEW); 288 289 // Process all View attributes 290 DescriptorsUtils.appendAttributes(attributes, 291 null, // elementName 292 SdkConstants.NS_RESOURCES, 293 info.getAttributes(), 294 null, // requiredAttributes 295 null /* overrides */); 296 297 List<String> attributeSources = new ArrayList<String>(); 298 if (info.getAttributes() != null && info.getAttributes().length > 0) { 299 attributeSources.add(fqcn); 300 } 301 302 for (ViewClassInfo link = info.getSuperClass(); 303 link != null; 304 link = link.getSuperClass()) { 305 AttributeInfo[] attrList = link.getAttributes(); 306 if (attrList.length > 0) { 307 attributeSources.add(link.getFullClassName()); 308 DescriptorsUtils.appendAttributes(attributes, 309 null, // elementName 310 SdkConstants.NS_RESOURCES, 311 attrList, 312 null, // requiredAttributes 313 null /* overrides */); 314 } 315 } 316 317 // Process all LayoutParams attributes 318 ArrayList<AttributeDescriptor> layoutAttributes = new ArrayList<AttributeDescriptor>(); 319 LayoutParamsInfo layoutParams = info.getLayoutData(); 320 321 for(; layoutParams != null; layoutParams = layoutParams.getSuperClass()) { 322 for (AttributeInfo attrInfo : layoutParams.getAttributes()) { 323 if (DescriptorsUtils.containsAttribute(layoutAttributes, 324 SdkConstants.NS_RESOURCES, attrInfo)) { 325 continue; 326 } 327 DescriptorsUtils.appendAttribute(layoutAttributes, 328 null, // elementName 329 SdkConstants.NS_RESOURCES, 330 attrInfo, 331 false, // required 332 null /* overrides */); 333 } 334 } 335 336 ViewElementDescriptor desc = new ViewElementDescriptor( 337 xmlName, 338 uiName, 339 fqcn, 340 tooltip, 341 null, // sdk_url 342 attributes.toArray(new AttributeDescriptor[attributes.size()]), 343 layoutAttributes.toArray(new AttributeDescriptor[layoutAttributes.size()]), 344 null, // children 345 false /* mandatory */); 346 desc.setAttributeSources(Collections.unmodifiableList(attributeSources)); 347 infoDescMap.put(info, desc); 348 return desc; 349 } 350 351 /** 352 * Creates a new <include> descriptor and adds it to the list of view descriptors. 353 * 354 * @param knownViews A list of view descriptors being populated. Also used to find the 355 * View descriptor and extract its layout attributes. 356 */ 357 private void insertInclude(List<ViewElementDescriptor> knownViews) { 358 String xmlName = VIEW_INCLUDE; 359 360 // Create the include custom attributes 361 ArrayList<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>(); 362 363 // Note that the "layout" attribute does NOT have the Android namespace 364 DescriptorsUtils.appendAttribute(attributes, 365 null, //elementXmlName 366 null, //nsUri 367 new AttributeInfo( 368 ATTR_LAYOUT, 369 Format.REFERENCE_SET ), 370 true, //required 371 null); //overrides 372 373 DescriptorsUtils.appendAttribute(attributes, 374 null, //elementXmlName 375 SdkConstants.NS_RESOURCES, //nsUri 376 new AttributeInfo( 377 "id", //$NON-NLS-1$ 378 Format.REFERENCE_SET ), 379 true, //required 380 null); //overrides 381 382 // Find View and inherit all its layout attributes 383 AttributeDescriptor[] viewLayoutAttribs = findViewLayoutAttributes( 384 SdkConstants.CLASS_VIEW); 385 386 // Create the include descriptor 387 ViewElementDescriptor desc = new ViewElementDescriptor(xmlName, 388 xmlName, // ui_name 389 VIEW_INCLUDE, // "class name"; the GLE only treats this as an element tag 390 "Lets you statically include XML layouts inside other XML layouts.", // tooltip 391 null, // sdk_url 392 attributes.toArray(new AttributeDescriptor[attributes.size()]), 393 viewLayoutAttribs, // layout attributes 394 null, // children 395 false /* mandatory */); 396 397 knownViews.add(desc); 398 } 399 400 /** 401 * Creates and returns a new {@code <merge>} descriptor. 402 * @param viewLayoutAttribs The layout attributes to use for the new descriptor 403 */ 404 private ViewElementDescriptor createMerge(AttributeDescriptor[] viewLayoutAttribs) { 405 String xmlName = VIEW_MERGE; 406 407 // Create the include descriptor 408 ViewElementDescriptor desc = new ViewElementDescriptor(xmlName, 409 xmlName, // ui_name 410 VIEW_MERGE, // "class name"; the GLE only treats this as an element tag 411 "A root tag useful for XML layouts inflated using a ViewStub.", // tooltip 412 null, // sdk_url 413 null, // attributes 414 viewLayoutAttribs, // layout attributes 415 null, // children 416 false /* mandatory */); 417 418 return desc; 419 } 420 421 /** 422 * Creates and returns a new {@code <fragment>} descriptor. 423 * @param viewLayoutAttribs The layout attributes to use for the new descriptor 424 * @param styleMap The style map provided by the SDK 425 */ 426 private ViewElementDescriptor createFragment(AttributeDescriptor[] viewLayoutAttribs, 427 Map<String, DeclareStyleableInfo> styleMap) { 428 String xmlName = VIEW_FRAGMENT; 429 final ViewElementDescriptor descriptor; 430 431 // First try to create the descriptor from metadata in attrs.xml: 432 DeclareStyleableInfo style = styleMap.get("Fragment"); //$NON-NLS-1$ 433 String fragmentTooltip = 434 "A Fragment is a piece of an application's user interface or behavior that " 435 + "can be placed in an Activity"; 436 String sdkUrl = "http://developer.android.com/guide/topics/fundamentals/fragments.html"; 437 TextAttributeDescriptor classAttribute = new ClassAttributeDescriptor( 438 // Should accept both CLASS_V4_FRAGMENT and CLASS_FRAGMENT 439 null /*superClassName*/, 440 ATTR_CLASS, null /* namespace */, 441 new AttributeInfo(ATTR_CLASS, Format.STRING_SET), 442 true /*mandatory*/) 443 .setTooltip("Supply the name of the fragment class to instantiate"); 444 445 if (style != null) { 446 descriptor = new ViewElementDescriptor( 447 VIEW_FRAGMENT, VIEW_FRAGMENT, VIEW_FRAGMENT, 448 fragmentTooltip, // tooltip 449 sdkUrl, //, 450 null /* attributes */, 451 viewLayoutAttribs, // layout attributes 452 null /*childrenElements*/, 453 false /*mandatory*/); 454 ArrayList<AttributeDescriptor> descs = new ArrayList<AttributeDescriptor>(); 455 // The class attribute is not included in the attrs.xml 456 descs.add(classAttribute); 457 DescriptorsUtils.appendAttributes(descs, 458 null, // elementName 459 SdkConstants.NS_RESOURCES, 460 style.getAttributes(), 461 null, // requiredAttributes 462 null); // overrides 463 //descriptor.setTooltip(style.getJavaDoc()); 464 descriptor.setAttributes(descs.toArray(new AttributeDescriptor[descs.size()])); 465 } else { 466 // The above will only work on API 11 and up. However, fragments are *also* available 467 // on older platforms, via the fragment support library, so add in a manual 468 // entry if necessary. 469 descriptor = new ViewElementDescriptor(xmlName, 470 xmlName, // ui_name 471 xmlName, // "class name"; the GLE only treats this as an element tag 472 fragmentTooltip, 473 sdkUrl, 474 new AttributeDescriptor[] { 475 new ClassAttributeDescriptor( 476 null /*superClassName*/, 477 ATTR_NAME, ANDROID_URI, 478 new AttributeInfo(ATTR_NAME, Format.STRING_SET), 479 true /*mandatory*/) 480 .setTooltip("Supply the name of the fragment class to instantiate"), 481 classAttribute, 482 new ClassAttributeDescriptor( 483 null /*superClassName*/, 484 ATTR_TAG, ANDROID_URI, 485 new AttributeInfo(ATTR_TAG, Format.STRING_SET), 486 true /*mandatory*/) 487 .setTooltip("Supply a tag for the top-level view containing a String"), 488 }, // attributes 489 viewLayoutAttribs, // layout attributes 490 null, // children 491 false /* mandatory */); 492 } 493 494 return descriptor; 495 } 496 497 /** 498 * Creates and returns a new {@code <requestFocus>} descriptor. 499 */ 500 private ViewElementDescriptor createRequestFocus() { 501 String xmlName = REQUEST_FOCUS; 502 503 // Create the include descriptor 504 return new ViewElementDescriptor( 505 xmlName, // xml_name 506 xmlName, // ui_name 507 xmlName, // "class name"; the GLE only treats this as an element tag 508 "Requests focus for the parent element or one of its descendants", // tooltip 509 null, // sdk_url 510 null, // attributes 511 null, // layout attributes 512 null, // children 513 false /* mandatory */); 514 } 515 516 /** 517 * Finds the descriptor and retrieves all its layout attributes. 518 */ 519 private AttributeDescriptor[] findViewLayoutAttributes( 520 String viewFqcn) { 521 ViewElementDescriptor viewDesc = findDescriptorByClass(viewFqcn); 522 if (viewDesc != null) { 523 return viewDesc.getLayoutAttributes(); 524 } 525 526 return null; 527 } 528 529 /** 530 * Set the super-class of each {@link ViewElementDescriptor} by using the super-class 531 * information available in the {@link ViewClassInfo}. 532 */ 533 private void fixSuperClasses(Map<ViewClassInfo, ViewElementDescriptor> infoDescMap) { 534 535 for (Entry<ViewClassInfo, ViewElementDescriptor> entry : infoDescMap.entrySet()) { 536 ViewClassInfo info = entry.getKey(); 537 ViewElementDescriptor desc = entry.getValue(); 538 539 ViewClassInfo sup = info.getSuperClass(); 540 if (sup != null) { 541 ViewElementDescriptor supDesc = infoDescMap.get(sup); 542 while (supDesc == null && sup != null) { 543 // We don't have a descriptor for the super-class. That means the class is 544 // probably abstract, so we just need to walk up the super-class chain till 545 // we find one we have. All views derive from android.view.View so we should 546 // surely find that eventually. 547 sup = sup.getSuperClass(); 548 if (sup != null) { 549 supDesc = infoDescMap.get(sup); 550 } 551 } 552 if (supDesc != null) { 553 desc.setSuperClass(supDesc); 554 } 555 } 556 } 557 } 558 559 /** 560 * Returns the {@link ViewElementDescriptor} with the given fully qualified class 561 * name, or null if not found. This is a quick map lookup. 562 * 563 * @param fqcn the fully qualified class name 564 * @return the corresponding {@link ViewElementDescriptor} or null 565 */ 566 public ViewElementDescriptor findDescriptorByClass(String fqcn) { 567 return mFqcnToDescriptor.get(fqcn); 568 } 569 570 /** 571 * Returns the {@link ViewElementDescriptor} with the given XML tag name, 572 * which usually does not include the package (depending on the 573 * value of {@link ViewElementDescriptor#viewNeedsPackage(String)}). 574 * 575 * @param tag the XML tag name 576 * @return the corresponding {@link ViewElementDescriptor} or null 577 */ 578 public ViewElementDescriptor findDescriptorByTag(String tag) { 579 // TODO: Consider whether we need to add a direct map lookup for this as well. 580 // Currently not done since this is not frequently needed (only needed for 581 // exploded rendering which was already performing list iteration.) 582 for (ViewElementDescriptor descriptor : mLayoutDescriptors) { 583 if (tag.equals(descriptor.getXmlLocalName())) { 584 return descriptor; 585 } 586 } 587 588 return null; 589 } 590 591 /** 592 * Returns a collection of all the view class names, including layouts 593 * 594 * @return a collection of all the view class names, never null 595 */ 596 public Collection<String> getAllViewClassNames() { 597 return mFqcnToDescriptor.keySet(); 598 } 599 } 600