/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tools.klint.client.api; import static com.android.SdkConstants.ABSOLUTE_LAYOUT; import static com.android.SdkConstants.ABS_LIST_VIEW; import static com.android.SdkConstants.ABS_SEEK_BAR; import static com.android.SdkConstants.ABS_SPINNER; import static com.android.SdkConstants.ADAPTER_VIEW; import static com.android.SdkConstants.AUTO_COMPLETE_TEXT_VIEW; import static com.android.SdkConstants.BUTTON; import static com.android.SdkConstants.CHECKABLE; import static com.android.SdkConstants.CHECKED_TEXT_VIEW; import static com.android.SdkConstants.CHECK_BOX; import static com.android.SdkConstants.COMPOUND_BUTTON; import static com.android.SdkConstants.EDIT_TEXT; import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW; import static com.android.SdkConstants.FRAME_LAYOUT; import static com.android.SdkConstants.GALLERY; import static com.android.SdkConstants.GRID_VIEW; import static com.android.SdkConstants.HORIZONTAL_SCROLL_VIEW; import static com.android.SdkConstants.IMAGE_BUTTON; import static com.android.SdkConstants.IMAGE_VIEW; import static com.android.SdkConstants.LINEAR_LAYOUT; import static com.android.SdkConstants.LIST_VIEW; import static com.android.SdkConstants.MULTI_AUTO_COMPLETE_TEXT_VIEW; import static com.android.SdkConstants.PROGRESS_BAR; import static com.android.SdkConstants.RADIO_BUTTON; import static com.android.SdkConstants.RADIO_GROUP; import static com.android.SdkConstants.RELATIVE_LAYOUT; import static com.android.SdkConstants.SCROLL_VIEW; import static com.android.SdkConstants.SEEK_BAR; import static com.android.SdkConstants.SPINNER; import static com.android.SdkConstants.SURFACE_VIEW; import static com.android.SdkConstants.SWITCH; import static com.android.SdkConstants.TABLE_LAYOUT; import static com.android.SdkConstants.TABLE_ROW; import static com.android.SdkConstants.TAB_HOST; import static com.android.SdkConstants.TAB_WIDGET; import static com.android.SdkConstants.TEXT_VIEW; import static com.android.SdkConstants.TOGGLE_BUTTON; import static com.android.SdkConstants.VIEW; import static com.android.SdkConstants.VIEW_ANIMATOR; import static com.android.SdkConstants.VIEW_GROUP; import static com.android.SdkConstants.VIEW_PKG_PREFIX; import static com.android.SdkConstants.VIEW_STUB; import static com.android.SdkConstants.VIEW_SWITCHER; import static com.android.SdkConstants.WEB_VIEW; import static com.android.SdkConstants.WIDGET_PKG_PREFIX; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.google.common.annotations.Beta; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * Default simple implementation of an {@link SdkInfo} * <p> * <b>NOTE: This is not a public or final API; if you rely on this be prepared * to adjust your code for the next tools release.</b> */ @Beta class DefaultSdkInfo extends SdkInfo { @Override @Nullable public String getParentViewName(@NonNull String name) { name = getRawType(name); return PARENTS.get(name); } @Override @Nullable public String getParentViewClass(@NonNull String fqcn) { int index = fqcn.lastIndexOf('.'); if (index != -1) { fqcn = fqcn.substring(index + 1); } String parent = PARENTS.get(fqcn); if (parent == null) { return null; } // The map only stores class names internally; correct for full package paths if (parent.equals(VIEW) || parent.equals(VIEW_GROUP) || parent.equals(SURFACE_VIEW)) { return VIEW_PKG_PREFIX + parent; } else { return WIDGET_PKG_PREFIX + parent; } } @Override public boolean isSubViewOf(@NonNull String parentType, @NonNull String childType) { String parent = getRawType(parentType); String child = getRawType(childType); // Do analysis just on non-fqcn paths if (parent.indexOf('.') != -1) { parent = parent.substring(parent.lastIndexOf('.') + 1); } if (child.indexOf('.') != -1) { child = child.substring(child.lastIndexOf('.') + 1); } if (parent.equals(VIEW)) { return true; } while (!child.equals(VIEW)) { if (parent.equals(child)) { return true; } if (implementsInterface(child, parent)) { return true; } child = PARENTS.get(child); if (child == null) { // Unknown view - err on the side of caution return true; } } return false; } private static boolean implementsInterface(String className, String interfaceName) { return interfaceName.equals(INTERFACES.get(className)); } // Strip off type parameters, e.g. AdapterView<?> ⇒ AdapterView private static String getRawType(String type) { if (type != null) { int index = type.indexOf('<'); if (index != -1) { type = type.substring(0, index); } } return type; } @Override public boolean isLayout(@NonNull String tag) { // TODO: Read in widgets.txt from the platform install area to look up this information // dynamically instead! if (super.isLayout(tag)) { return true; } return LAYOUTS.contains(tag); } private static final int CLASS_COUNT = 59; private static final int LAYOUT_COUNT = 20; private static final Map<String,String> PARENTS = Maps.newHashMapWithExpectedSize(CLASS_COUNT); private static final Set<String> LAYOUTS = Sets.newHashSetWithExpectedSize(CLASS_COUNT); static { PARENTS.put(COMPOUND_BUTTON, BUTTON); PARENTS.put(ABS_SPINNER, ADAPTER_VIEW); PARENTS.put(ABS_LIST_VIEW, ADAPTER_VIEW); PARENTS.put(ABS_SEEK_BAR, ADAPTER_VIEW); PARENTS.put(ADAPTER_VIEW, VIEW_GROUP); PARENTS.put(VIEW_GROUP, VIEW); PARENTS.put(TEXT_VIEW, VIEW); PARENTS.put(CHECKED_TEXT_VIEW, TEXT_VIEW); PARENTS.put(RADIO_BUTTON, COMPOUND_BUTTON); PARENTS.put(SPINNER, ABS_SPINNER); PARENTS.put(IMAGE_BUTTON, IMAGE_VIEW); PARENTS.put(IMAGE_VIEW, VIEW); PARENTS.put(EDIT_TEXT, TEXT_VIEW); PARENTS.put(PROGRESS_BAR, VIEW); PARENTS.put(TOGGLE_BUTTON, COMPOUND_BUTTON); PARENTS.put(VIEW_STUB, VIEW); PARENTS.put(BUTTON, TEXT_VIEW); PARENTS.put(SEEK_BAR, ABS_SEEK_BAR); PARENTS.put(CHECK_BOX, COMPOUND_BUTTON); PARENTS.put(SWITCH, COMPOUND_BUTTON); PARENTS.put(GALLERY, ABS_SPINNER); PARENTS.put(SURFACE_VIEW, VIEW); PARENTS.put(ABSOLUTE_LAYOUT, VIEW_GROUP); PARENTS.put(LINEAR_LAYOUT, VIEW_GROUP); PARENTS.put(RELATIVE_LAYOUT, VIEW_GROUP); PARENTS.put(LIST_VIEW, ABS_LIST_VIEW); PARENTS.put(VIEW_SWITCHER, VIEW_ANIMATOR); PARENTS.put(FRAME_LAYOUT, VIEW_GROUP); PARENTS.put(HORIZONTAL_SCROLL_VIEW, FRAME_LAYOUT); PARENTS.put(VIEW_ANIMATOR, FRAME_LAYOUT); PARENTS.put(TAB_HOST, FRAME_LAYOUT); PARENTS.put(TABLE_ROW, LINEAR_LAYOUT); PARENTS.put(RADIO_GROUP, LINEAR_LAYOUT); PARENTS.put(TAB_WIDGET, LINEAR_LAYOUT); PARENTS.put(EXPANDABLE_LIST_VIEW, LIST_VIEW); PARENTS.put(TABLE_LAYOUT, LINEAR_LAYOUT); PARENTS.put(SCROLL_VIEW, FRAME_LAYOUT); PARENTS.put(GRID_VIEW, ABS_LIST_VIEW); PARENTS.put(WEB_VIEW, ABSOLUTE_LAYOUT); PARENTS.put(AUTO_COMPLETE_TEXT_VIEW, EDIT_TEXT); PARENTS.put(MULTI_AUTO_COMPLETE_TEXT_VIEW, AUTO_COMPLETE_TEXT_VIEW); PARENTS.put(CHECKED_TEXT_VIEW, TEXT_VIEW); PARENTS.put("MediaController", FRAME_LAYOUT); //$NON-NLS-1$ PARENTS.put("SlidingDrawer", VIEW_GROUP); //$NON-NLS-1$ PARENTS.put("DialerFilter", RELATIVE_LAYOUT); //$NON-NLS-1$ PARENTS.put("DigitalClock", TEXT_VIEW); //$NON-NLS-1$ PARENTS.put("Chronometer", TEXT_VIEW); //$NON-NLS-1$ PARENTS.put("ImageSwitcher", VIEW_SWITCHER); //$NON-NLS-1$ PARENTS.put("TextSwitcher", VIEW_SWITCHER); //$NON-NLS-1$ PARENTS.put("AnalogClock", VIEW); //$NON-NLS-1$ PARENTS.put("TwoLineListItem", RELATIVE_LAYOUT); //$NON-NLS-1$ PARENTS.put("ZoomControls", LINEAR_LAYOUT); //$NON-NLS-1$ PARENTS.put("DatePicker", FRAME_LAYOUT); //$NON-NLS-1$ PARENTS.put("TimePicker", FRAME_LAYOUT); //$NON-NLS-1$ PARENTS.put("VideoView", SURFACE_VIEW); //$NON-NLS-1$ PARENTS.put("ZoomButton", IMAGE_BUTTON); //$NON-NLS-1$ PARENTS.put("RatingBar", ABS_SEEK_BAR); //$NON-NLS-1$ PARENTS.put("ViewFlipper", VIEW_ANIMATOR); //$NON-NLS-1$ PARENTS.put("NumberPicker", LINEAR_LAYOUT); //$NON-NLS-1$ assert PARENTS.size() <= CLASS_COUNT : PARENTS.size(); /* // Check that all widgets lead to the root view if (LintUtils.assertionsEnabled()) { for (String key : PARENTS.keySet()) { String parent = PARENTS.get(key); if (!parent.equals(VIEW)) { String grandParent = PARENTS.get(parent); assert grandParent != null : parent; } } } */ LAYOUTS.add(TAB_HOST); LAYOUTS.add(HORIZONTAL_SCROLL_VIEW); LAYOUTS.add(VIEW_SWITCHER); LAYOUTS.add(TAB_WIDGET); LAYOUTS.add(VIEW_ANIMATOR); LAYOUTS.add(SCROLL_VIEW); LAYOUTS.add(GRID_VIEW); LAYOUTS.add(TABLE_ROW); LAYOUTS.add(RADIO_GROUP); LAYOUTS.add(LIST_VIEW); LAYOUTS.add(EXPANDABLE_LIST_VIEW); LAYOUTS.add("MediaController"); //$NON-NLS-1$ LAYOUTS.add("DialerFilter"); //$NON-NLS-1$ LAYOUTS.add("ViewFlipper"); //$NON-NLS-1$ LAYOUTS.add("SlidingDrawer"); //$NON-NLS-1$ LAYOUTS.add("StackView"); //$NON-NLS-1$ LAYOUTS.add("SearchView"); //$NON-NLS-1$ LAYOUTS.add("TextSwitcher"); //$NON-NLS-1$ LAYOUTS.add("AdapterViewFlipper"); //$NON-NLS-1$ LAYOUTS.add("ImageSwitcher"); //$NON-NLS-1$ assert LAYOUTS.size() <= LAYOUT_COUNT : LAYOUTS.size(); } // Currently using a map; this should really be a list, but using a map until we actually // start adding more than one item @NonNull private static final Map<String, String> INTERFACES = new HashMap<String, String>(2); static { INTERFACES.put(CHECKED_TEXT_VIEW, CHECKABLE); INTERFACES.put(COMPOUND_BUTTON, CHECKABLE); } }