diff options
Diffstat (limited to 'eclipse')
43 files changed, 1352 insertions, 684 deletions
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt index aa985c7..dca9b23 100644 --- a/eclipse/dictionary.txt +++ b/eclipse/dictionary.txt @@ -199,6 +199,7 @@ preloaded preloads primordial printf +pristine programmatic programmatically proguard diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/GridLayout.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/GridLayout.png Binary files differindex 1aa4165..ca64e8c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/icons/GridLayout.png +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/GridLayout.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/Space.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/Space.png Binary files differindex 58afbe4..28b2553 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/icons/Space.png +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/Space.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java index b9c2290..47f3a58 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java @@ -49,7 +49,6 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_TO_RIGHT import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_X; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_Y; -import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; import static com.android.ide.common.layout.LayoutConstants.VALUE_FILL_PARENT; import static com.android.ide.common.layout.LayoutConstants.VALUE_MATCH_PARENT; import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT; @@ -518,30 +517,11 @@ public class BaseLayoutRule extends BaseViewRule { protected static void addAttributes(INode newNode, IDragElement oldElement, Map<String, Pair<String, String>> idMap, AttributeFilter filter) { - // A little trick here: when creating new UI widgets by dropping them - // from the palette, we assign them a new id and then set the text - // attribute to that id, so for example a Button will have - // android:text="@+id/Button01". - // Here we detect if such an id is being remapped to a new id and if - // there's a text attribute with exactly the same id name, we update it - // too. - String oldText = null; - String oldId = null; - String newId = null; - for (IDragAttribute attr : oldElement.getAttributes()) { String uri = attr.getUri(); String name = attr.getName(); String value = attr.getValue(); - if (uri.equals(ANDROID_URI)) { - if (name.equals(ATTR_ID)) { - oldId = value; - } else if (name.equals(ATTR_TEXT)) { - oldText = value; - } - } - IAttributeInfo attrInfo = newNode.getAttributeInfo(uri, name); if (attrInfo != null) { Format[] formats = attrInfo.getFormats(); @@ -557,17 +537,8 @@ public class BaseLayoutRule extends BaseViewRule { } if (value != null && value.length() > 0) { newNode.setAttribute(uri, name, value); - - if (uri.equals(ANDROID_URI) && name.equals(ATTR_ID) && - oldId != null && !oldId.equals(value)) { - newId = value; - } } } - - if (newId != null && oldText != null && oldText.equals(oldId)) { - newNode.setAttribute(ANDROID_URI, ATTR_TEXT, newId); - } } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java index 4fda13d..f8f0a8c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java @@ -21,7 +21,9 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_COLUMN; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_GRAVITY; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW; import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION; +import static com.android.ide.common.layout.LayoutConstants.FQCN_GRID_LAYOUT; import static com.android.ide.common.layout.LayoutConstants.FQCN_SPACE; +import static com.android.ide.common.layout.LayoutConstants.FQCN_SPACE_V7; import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_FILL; import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_FILL_HORIZONTAL; import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_FILL_VERTICAL; @@ -150,11 +152,12 @@ public class GridLayoutRule extends BaseLayoutRule { final List<? extends INode> children) { super.addLayoutActions(actions, parentNode, children); + String namespace = getNamespace(parentNode); Choices orientationAction = RuleAction.createChoices( ACTION_ORIENTATION, "Orientation", //$NON-NLS-1$ new PropertyCallback(Collections.singletonList(parentNode), - "Change LinearLayout Orientation", ANDROID_URI, ATTR_ORIENTATION), Arrays + "Change LinearLayout Orientation", namespace, ATTR_ORIENTATION), Arrays .<String> asList("Set Horizontal Orientation", "Set Vertical Orientation"), Arrays.<URL> asList(ICON_HORIZONTAL, ICON_VERTICAL), Arrays.<String> asList( "horizontal", "vertical"), getCurrentOrientation(parentNode), @@ -256,8 +259,8 @@ public class GridLayoutRule extends BaseLayoutRule { * Returns the orientation attribute value currently used by the node (even if not * defined, in which case the default horizontal value is returned) */ - private static String getCurrentOrientation(final INode node) { - String orientation = node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION); + private String getCurrentOrientation(final INode node) { + String orientation = node.getStringAttr(getNamespace(node), ATTR_ORIENTATION); if (orientation == null || orientation.length() == 0) { orientation = VALUE_HORIZONTAL; } @@ -332,11 +335,28 @@ public class GridLayoutRule extends BaseLayoutRule { FillPreference fill = metadata.getFillPreference(); String gravity = computeDefaultGravity(fill); if (gravity != null) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_GRAVITY, gravity); + node.setAttribute(getNamespace(parent), ATTR_LAYOUT_GRAVITY, gravity); } } /** + * Returns the namespace URI to use for GridLayout-specific attributes, such + * as columnCount, layout_column, layout_column_span, layout_gravity etc. + * + * @param layout the GridLayout instance to look up the namespace for + * @return the namespace, never null + */ + public String getNamespace(INode layout) { + String namespace = ANDROID_URI; + + if (!layout.getFqcn().equals(FQCN_GRID_LAYOUT)) { + namespace = mRulesEngine.getAppNameSpace(); + } + + return namespace; + } + + /** * Computes the default gravity to be used for a widget of the given fill * preference when added to a grid layout * @@ -374,7 +394,8 @@ public class GridLayoutRule extends BaseLayoutRule { GridModel grid = new GridModel(mRulesEngine, parent, null); for (INode child : deleted) { // We don't care about deletion of spacers - if (child.getFqcn().equals(FQCN_SPACE)) { + String fqcn = child.getFqcn(); + if (fqcn.equals(FQCN_SPACE) || fqcn.equals(FQCN_SPACE_V7)) { continue; } grid.markDeleted(child); @@ -430,8 +451,9 @@ public class GridLayoutRule extends BaseLayoutRule { Pair<Integer, Integer> spans = computeResizeSpans(state); int rowSpan = spans.getFirst(); int columnSpan = spans.getSecond(); - GridModel.setColumnSpanAttribute(node, columnSpan); - GridModel.setRowSpanAttribute(node, rowSpan); + GridModel grid = getGrid(state); + grid.setColumnSpanAttribute(node, columnSpan); + grid.setRowSpanAttribute(node, rowSpan); } } @@ -561,7 +583,8 @@ public class GridLayoutRule extends BaseLayoutRule { for (IDragElement element : elements) { // Skip <Space> elements and only insert the real elements being // copied - if (elements.length > 1 && FQCN_SPACE.equals(element.getFqcn())) { + if (elements.length > 1 && (FQCN_SPACE.equals(element.getFqcn()) + || FQCN_SPACE_V7.equals(element.getFqcn()))) { continue; } @@ -571,8 +594,10 @@ public class GridLayoutRule extends BaseLayoutRule { // Ensure that we reset any potential row/column attributes from a different // grid layout being copied from - newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, null); - newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, null); + GridDropHandler handler = (GridDropHandler) feedback.userData; + GridModel grid = handler.getGrid(); + grid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN, null); + grid.setGridAttribute(newChild, ATTR_LAYOUT_ROW, null); // TODO: Set columnSpans to avoid making these widgets completely // break the layout diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java index 1b9f815..3129f4d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java @@ -196,6 +196,7 @@ public class LayoutConstants { /** The fully qualified class name of a RelativeLayout view */ public static final String FQCN_GRID_LAYOUT = "android.widget.GridLayout"; //$NON-NLS-1$ + public static final String FQCN_GRID_LAYOUT_V7 = "android.support.v7.widget.GridLayout"; //$NON-NLS-1$ /** The fully qualified class name of a FrameLayout view */ public static final String FQCN_FRAME_LAYOUT = "android.widget.FrameLayout"; //$NON-NLS-1$ @@ -247,6 +248,7 @@ public class LayoutConstants { /** The fully qualified class name of a Space */ public static final String FQCN_SPACE = "android.widget.Space"; //$NON-NLS-1$ + public static final String FQCN_SPACE_V7 = "android.support.v7.widget.Space"; //$NON-NLS-1$ /** The fully qualified class name of a TextView view */ public static final String FQCN_TEXT_VIEW = "android.widget.TextView"; //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java index 0c7ed9f..74c1b59 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java @@ -19,14 +19,12 @@ import static com.android.ide.common.layout.GridLayoutRule.GRID_SIZE; import static com.android.ide.common.layout.GridLayoutRule.MARGIN_SIZE; import static com.android.ide.common.layout.GridLayoutRule.MAX_CELL_DIFFERENCE; import static com.android.ide.common.layout.GridLayoutRule.SHORT_GAP_DP; -import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; import static com.android.ide.common.layout.LayoutConstants.ATTR_COLUMN_COUNT; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_COLUMN; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_COLUMN_SPAN; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_GRAVITY; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW_SPAN; -import static com.android.ide.common.layout.LayoutConstants.VALUE_1; import static com.android.ide.common.layout.LayoutConstants.VALUE_BOTTOM; import static com.android.ide.common.layout.LayoutConstants.VALUE_CENTER_HORIZONTAL; import static com.android.ide.common.layout.LayoutConstants.VALUE_RIGHT; @@ -478,20 +476,18 @@ public class GridDropHandler { // bottom half! - int columnX = mGrid.getColumnX(column); - int rowY = mGrid.getRowY(row); + //int columnX = mGrid.getColumnX(column); + //int rowY = mGrid.getRowY(row); - targetNode.setAttribute(ANDROID_URI, ATTR_COLUMN_COUNT, VALUE_1); - //targetNode.setAttribute(ANDROID_URI, ATTR_COLUMN_COUNT, "3"); + mGrid.setGridAttribute(targetNode, ATTR_COLUMN_COUNT, 2); + //mGrid.setGridAttribute(targetNode, ATTR_COLUMN_COUNT, 3); //INode scr0 = addSpacer(targetNode, -1, 0, 0, 1, 1); //INode sc1 = addSpacer(targetNode, -1, 0, 1, 0, 0); //INode sc2 = addSpacer(targetNode, -1, 0, 2, 1, 0); //INode sr1 = addSpacer(targetNode, -1, 1, 0, 0, 0); //INode sr2 = addSpacer(targetNode, -1, 2, 0, 0, 1); - //sc1.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN_WEIGHT, VALUE_1); - //sr1.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW_WEIGHT, VALUE_1); - //sc1.setAttribute(ANDROID_URI, ATTR_LAYOUT_GRAVITY, VALUE_FILL_HORIZONTAL); - //sr1.setAttribute(ANDROID_URI, ATTR_LAYOUT_GRAVITY, VALUE_FILL_VERTICAL); + //mGrid.setGridAttribute(sc1, ATTR_LAYOUT_GRAVITY, VALUE_FILL_HORIZONTAL); + //mGrid.setGridAttribute(sr1, ATTR_LAYOUT_GRAVITY, VALUE_FILL_VERTICAL); // //mGrid.loadFromXml(); //column = mGrid.getColumn(columnX); @@ -655,33 +651,33 @@ public class GridDropHandler { // Set the cell position of the new widget if (mColumnMatch.type == SegmentType.RIGHT) { - newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_GRAVITY, VALUE_RIGHT); + mGrid.setGridAttribute(newChild, ATTR_LAYOUT_GRAVITY, VALUE_RIGHT); } else if (mColumnMatch.type == SegmentType.CENTER_HORIZONTAL) { - newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_GRAVITY, VALUE_CENTER_HORIZONTAL); + mGrid.setGridAttribute(newChild, ATTR_LAYOUT_GRAVITY, VALUE_CENTER_HORIZONTAL); } - newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, Integer.toString(column)); + mGrid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN, column); if (mRowMatch.type == SegmentType.BOTTOM) { String value = VALUE_BOTTOM; if (mColumnMatch.type == SegmentType.RIGHT) { value = value + '|' + VALUE_RIGHT; + } else if (mColumnMatch.type == SegmentType.CENTER_HORIZONTAL) { + value = value + '|' + VALUE_CENTER_HORIZONTAL; } - newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_GRAVITY, value); + mGrid.setGridAttribute(newChild, ATTR_LAYOUT_GRAVITY, value); } - newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, Integer.toString(row)); + mGrid.setGridAttribute(newChild, ATTR_LAYOUT_ROW, row); // Apply spans to ensure that the widget can fit without pushing columns if (columnSpan > 1) { - newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN_SPAN, - Integer.toString(columnSpan)); + mGrid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN_SPAN, columnSpan); } if (rowSpan > 1) { - newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW_SPAN, Integer.toString(rowSpan)); + mGrid.setGridAttribute(newChild, ATTR_LAYOUT_ROW_SPAN, rowSpan); } // Ensure that we don't store columnCount=0 if (mGrid.actualColumnCount == 0) { - mGrid.layout.setAttribute(ANDROID_URI, ATTR_COLUMN_COUNT, - Integer.toString(Math.max(1, column + 1))); + mGrid.setGridAttribute(mGrid.layout, ATTR_COLUMN_COUNT, Math.max(1, column + 1)); } return newChild; @@ -708,10 +704,8 @@ public class GridDropHandler { mGrid.addRow(mRowMatch.cellIndex, newChild, UNDEFINED, false, UNDEFINED, UNDEFINED); } - newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, - Integer.toString(mColumnMatch.cellIndex)); - newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, - Integer.toString(mRowMatch.cellIndex)); + mGrid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN, mColumnMatch.cellIndex); + mGrid.setGridAttribute(newChild, ATTR_LAYOUT_ROW, mRowMatch.cellIndex); return newChild; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java index 1e4d5cd..9ef247e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java @@ -31,8 +31,12 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW_SPAN import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION; import static com.android.ide.common.layout.LayoutConstants.ATTR_ROW_COUNT; +import static com.android.ide.common.layout.LayoutConstants.FQCN_GRID_LAYOUT; import static com.android.ide.common.layout.LayoutConstants.FQCN_SPACE; +import static com.android.ide.common.layout.LayoutConstants.FQCN_SPACE_V7; +import static com.android.ide.common.layout.LayoutConstants.GRID_LAYOUT; import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.SPACE; import static com.android.ide.common.layout.LayoutConstants.VALUE_BOTTOM; import static com.android.ide.common.layout.LayoutConstants.VALUE_CENTER_VERTICAL; import static com.android.ide.common.layout.LayoutConstants.VALUE_N_DP; @@ -147,6 +151,9 @@ public class GridModel { */ private Object mViewObject; + /** The namespace to use for attributes */ + private String mNamespace; + /** * Constructs a {@link GridModel} for the given layout * @@ -250,7 +257,7 @@ public class GridModel { if (baseline != -1) { // Even views that do have baselines do not count towards a row // baseline if they have a vertical gravity - String gravity = view.node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_GRAVITY); + String gravity = getGridAttribute(view.node, ATTR_LAYOUT_GRAVITY); if (gravity == null || !(gravity.contains(VALUE_TOP) || gravity.contains(VALUE_BOTTOM) @@ -275,13 +282,61 @@ public class GridModel { } // Also fix the columnCount - if (layout.getStringAttr(ANDROID_URI, ATTR_COLUMN_COUNT) != null && + if (getGridAttribute(layout, ATTR_COLUMN_COUNT) != null && declaredColumnCount > actualColumnCount) { - layout.setAttribute(ANDROID_URI, ATTR_COLUMN_COUNT, - Integer.toString(actualColumnCount)); + setGridAttribute(layout, ATTR_COLUMN_COUNT, actualColumnCount); } } + /** + * Sets the given GridLayout attribute (rowCount, layout_row, etc) to the + * given value. This automatically handles using the right XML namespace + * based on whether the GridLayout is the android.widget.GridLayout, or the + * support library GridLayout, and whether it's in a library project or not + * etc. + * + * @param node the node to apply the attribute to + * @param name the local name of the attribute + * @param value the integer value to set the attribute to + */ + public void setGridAttribute(INode node, String name, int value) { + setGridAttribute(node, name, Integer.toString(value)); + } + + /** + * Sets the given GridLayout attribute (rowCount, layout_row, etc) to the + * given value. This automatically handles using the right XML namespace + * based on whether the GridLayout is the android.widget.GridLayout, or the + * support library GridLayout, and whether it's in a library project or not + * etc. + * + * @param node the node to apply the attribute to + * @param name the local name of the attribute + * @param value the string value to set the attribute to, or null to clear + * it + */ + public void setGridAttribute(INode node, String name, String value) { + node.setAttribute(getNamespace(), name, value); + } + + /** + * Returns the namespace URI to use for GridLayout-specific attributes, such + * as columnCount, layout_column, layout_column_span, layout_gravity etc. + * + * @return the namespace, never null + */ + public String getNamespace() { + if (mNamespace == null) { + mNamespace = ANDROID_URI; + + if (!layout.getFqcn().equals(FQCN_GRID_LAYOUT)) { + mNamespace = mRulesEngine.getAppNameSpace(); + } + } + + return mNamespace; + } + /** Removes the given flag from a flag attribute value and returns the result */ static String removeFlag(String flag, String value) { if (value.equals(flag)) { @@ -313,10 +368,10 @@ public class GridModel { void loadFromXml() { INode[] children = layout.getChildren(); - declaredRowCount = getInt(layout, ATTR_ROW_COUNT, UNDEFINED); - declaredColumnCount = getInt(layout, ATTR_COLUMN_COUNT, UNDEFINED); + declaredRowCount = getGridAttribute(layout, ATTR_ROW_COUNT, UNDEFINED); + declaredColumnCount = getGridAttribute(layout, ATTR_COLUMN_COUNT, UNDEFINED); // Horizontal is the default, so if no value is specified it is horizontal. - vertical = VALUE_VERTICAL.equals(layout.getStringAttr(ANDROID_URI, ATTR_ORIENTATION)); + vertical = VALUE_VERTICAL.equals(getGridAttribute(layout, ATTR_ORIENTATION)); mChildViews = new ArrayList<ViewData>(children.length); int index = 0; @@ -627,7 +682,7 @@ public class GridModel { } // The bounds should be in ascending order now - if (GridLayoutRule.sDebugGridLayout) { + if (false && GridLayoutRule.sDebugGridLayout) { for (int i = 1; i < actualRowCount; i++) { assert mTop[i + 1] >= mTop[i]; } @@ -756,8 +811,7 @@ public class GridModel { // Insert a new column if (declaredColumnCount != UNDEFINED) { declaredColumnCount++; - layout.setAttribute(ANDROID_URI, ATTR_COLUMN_COUNT, - Integer.toString(declaredColumnCount)); + setGridAttribute(layout, ATTR_COLUMN_COUNT, declaredColumnCount); } boolean isLastColumn = true; @@ -796,12 +850,10 @@ public class GridModel { // the new row number, but we use the spacer to assign the row // some height. if (view.column == newColumn) { - view.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, - Integer.toString(view.column + 1)); + setGridAttribute(view.node, ATTR_LAYOUT_COLUMN, view.column + 1); } // else: endColumn == newColumn: handled below - } else if (view.node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN) != null) { - view.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, - Integer.toString(view.column + 1)); + } else if (getGridAttribute(view.node, ATTR_LAYOUT_COLUMN) != null) { + setGridAttribute(view.node, ATTR_LAYOUT_COLUMN, view.column + 1); } } else if (endColumn > newColumn) { setColumnSpanAttribute(view.node, view.columnSpan + 1); @@ -820,8 +872,7 @@ public class GridModel { if (isLastColumn) { for (ViewData view : mChildViews) { if (view.column == 0 && view.row > 0) { - view.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, - Integer.toString(view.row)); + setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); } } if (split) { @@ -867,8 +918,7 @@ public class GridModel { // TODO: Do this under a write lock? / editXml lock? if (declaredColumnCount != UNDEFINED) { declaredColumnCount--; - layout.setAttribute(ANDROID_URI, ATTR_COLUMN_COUNT, - Integer.toString(declaredColumnCount)); + setGridAttribute(layout, ATTR_COLUMN_COUNT, declaredColumnCount); } // Remove any elements that begin in the deleted columns... @@ -894,9 +944,8 @@ public class GridModel { // Subtract column span to skip this item setColumnSpanAttribute(view.node, view.columnSpan - 1); } else if (view.column > removedColumn) { - if (view.node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN) != null) { - view.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, - Integer.toString(view.column - 1)); + if (getGridAttribute(view.node, ATTR_LAYOUT_COLUMN) != null) { + setGridAttribute(view.node, ATTR_LAYOUT_COLUMN, view.column - 1); } } } @@ -948,8 +997,7 @@ public class GridModel { if (declaredRowCount != UNDEFINED) { declaredRowCount++; - layout.setAttribute(ANDROID_URI, ATTR_ROW_COUNT, - Integer.toString(declaredRowCount)); + setGridAttribute(layout, ATTR_ROW_COUNT, declaredRowCount); } boolean added = false; for (ViewData view : mChildViews) { @@ -961,8 +1009,7 @@ public class GridModel { int index = getChildIndex(layout.getChildren(), view.node); assert view.index == index; // TODO: Get rid of getter if (declaredColumnCount != UNDEFINED && !split) { - layout.setAttribute(ANDROID_URI, ATTR_COLUMN_COUNT, - Integer.toString(declaredColumnCount)); + setGridAttribute(layout, ATTR_COLUMN_COUNT, declaredColumnCount); } newView = addSpacer(layout, index, split ? newRow - 1 : UNDEFINED, @@ -975,13 +1022,11 @@ public class GridModel { // This means we don't really need the spacer above to imply // the new row number, but we use the spacer to assign the row // some height. - view.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, - Integer.toString(view.row + 1)); + setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row + 1); added = true; - } else if (view.node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_ROW) != null) { - view.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, - Integer.toString(view.row + 1)); + } else if (getGridAttribute(view.node, ATTR_LAYOUT_ROW) != null) { + setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row + 1); } } else { int endRow = view.row + view.rowSpan; @@ -1003,12 +1048,12 @@ public class GridModel { rowHeightDp != UNDEFINED ? rowHeightDp : DEFAULT_CELL_HEIGHT); } if (declaredColumnCount != UNDEFINED && !split) { - newView.setAttribute(ANDROID_URI, ATTR_COLUMN_COUNT, - Integer.toString(declaredColumnCount)); + setGridAttribute(layout, ATTR_COLUMN_COUNT, declaredColumnCount); } if (split) { - newView.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, Integer.toString(newRow - 1)); - newView.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, Integer.toString(column)); + setGridAttribute(newView, ATTR_LAYOUT_ROW, newRow - 1); + setGridAttribute(newView, ATTR_LAYOUT_COLUMN, column); + } } @@ -1046,8 +1091,7 @@ public class GridModel { // the declared row range! if (declaredRowCount != UNDEFINED) { declaredRowCount--; - layout.setAttribute(ANDROID_URI, ATTR_ROW_COUNT, - Integer.toString(declaredRowCount)); + setGridAttribute(layout, ATTR_ROW_COUNT, declaredRowCount); } // Remove any elements that begin in the deleted rows... @@ -1062,9 +1106,8 @@ public class GridModel { // positions for other cells layout.removeChild(view.node); } else if (view.row > removedRow) { - if (view.node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_ROW) != null) { - view.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, - Integer.toString(view.row - 1)); + if (getGridAttribute(view.node, ATTR_LAYOUT_ROW) != null) { + setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row - 1); } } else if (view.row < removedRow && view.row + view.rowSpan > removedRow) { @@ -1422,8 +1465,7 @@ public class GridModel { if (insertMarginColumn) { declaredColumnCount++; } - layout.setAttribute(ANDROID_URI, ATTR_COLUMN_COUNT, - Integer.toString(declaredColumnCount)); + setGridAttribute(layout, ATTR_COLUMN_COUNT, declaredColumnCount); } // Are we inserting a new last column in the grid? That requires some special handling... @@ -1441,9 +1483,8 @@ public class GridModel { if (isLastColumn) { for (ViewData view : mChildViews) { if (view.column == 0 && view.row > 0) { - if (view.node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_ROW) == null) { - view.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, - Integer.toString(view.row)); + if (getGridAttribute(view.node, ATTR_LAYOUT_ROW) == null) { + setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); } } } @@ -1483,9 +1524,8 @@ public class GridModel { // where necessary, e.g. only on the FIRST view on this row following the // skipped column! - //if (node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN) != null) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, - Integer.toString(column + (insertMarginColumn ? 2 : 1))); + //if (getGridAttribute(node, ATTR_LAYOUT_COLUMN) != null) { + setGridAttribute(node, ATTR_LAYOUT_COLUMN, column + (insertMarginColumn ? 2 : 1)); //} } else if (!view.isSpacer()) { int endColumn = column + view.columnSpan; @@ -1508,8 +1548,8 @@ public class GridModel { if (remaining > 0) { prevColumnSpacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, String.format(VALUE_N_DP, remaining)); - prevColumnSpacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, - Integer.toString(insertMarginColumn ? newColumn + 1 : newColumn)); + setGridAttribute(prevColumnSpacer.node, ATTR_LAYOUT_COLUMN, + insertMarginColumn ? newColumn + 1 : newColumn); } } @@ -1541,8 +1581,7 @@ public class GridModel { if (insertMarginRow) { declaredRowCount++; } - layout.setAttribute(ANDROID_URI, ATTR_ROW_COUNT, - Integer.toString(declaredRowCount)); + setGridAttribute(layout, ATTR_ROW_COUNT, declaredRowCount); } // Find the spacer which marks this row, and if found, mark it as a split @@ -1563,9 +1602,8 @@ public class GridModel { INode node = view.node; int row = view.row; if (row > newRow || (row == newRow && view.node.getBounds().y2() > y)) { - //if (node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_ROW) != null) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, - Integer.toString(row + (insertMarginRow ? 2 : 1))); + //if (getGridAttribute(node, ATTR_LAYOUT_ROW) != null) { + setGridAttribute(node, ATTR_LAYOUT_ROW, row + (insertMarginRow ? 2 : 1)); //} } else if (!view.isSpacer()) { int endRow = row + view.rowSpan; @@ -1588,8 +1626,8 @@ public class GridModel { if (remaining > 0) { prevRowSpacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, String.format(VALUE_N_DP, remaining)); - prevRowSpacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, - Integer.toString(insertMarginRow ? newRow + 1 : newRow)); + setGridAttribute(prevRowSpacer.node, ATTR_LAYOUT_ROW, + insertMarginRow ? newRow + 1 : newRow); } } @@ -1604,7 +1642,7 @@ public class GridModel { * Data about a view in a table; this is not the same as a cell because multiple views * can share a single cell, and a view can span many cells. */ - static class ViewData { + class ViewData { public final INode node; public final int index; public int row; @@ -1617,23 +1655,20 @@ public class GridModel { node = n; this.index = index; - column = getInt(n, ATTR_LAYOUT_COLUMN, UNDEFINED); - columnSpan = getInt(n, ATTR_LAYOUT_COLUMN_SPAN, 1); - row = getInt(n, ATTR_LAYOUT_ROW, UNDEFINED); - rowSpan = getInt(n, ATTR_LAYOUT_ROW_SPAN, 1); - gravity = GravityHelper.getGravity(n.getStringAttr(ANDROID_URI, ATTR_LAYOUT_GRAVITY), - 0); + column = getGridAttribute(n, ATTR_LAYOUT_COLUMN, UNDEFINED); + columnSpan = getGridAttribute(n, ATTR_LAYOUT_COLUMN_SPAN, 1); + row = getGridAttribute(n, ATTR_LAYOUT_ROW, UNDEFINED); + rowSpan = getGridAttribute(n, ATTR_LAYOUT_ROW_SPAN, 1); + gravity = GravityHelper.getGravity(getGridAttribute(n, ATTR_LAYOUT_GRAVITY), 0); } /** Applies the column and row fields into the XML model */ void applyPositionAttributes() { - if (node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN) == null) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, - Integer.toString(column)); + if (getGridAttribute(node, ATTR_LAYOUT_COLUMN) == null) { + setGridAttribute(node, ATTR_LAYOUT_COLUMN, column); } - if (node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_ROW) == null) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, - Integer.toString(row)); + if (getGridAttribute(node, ATTR_LAYOUT_ROW) == null) { + setGridAttribute(node, ATTR_LAYOUT_ROW, row); } } @@ -1653,7 +1688,8 @@ public class GridModel { /** Returns true if this {@link ViewData} represents a spacer */ boolean isSpacer() { - return FQCN_SPACE.equals(node.getFqcn()); + String fqcn = node.getFqcn(); + return FQCN_SPACE.equals(fqcn) || FQCN_SPACE_V7.equals(fqcn); } /** @@ -1690,9 +1726,8 @@ public class GridModel { * @param node the target node * @param span the new column span */ - public static void setColumnSpanAttribute(INode node, int span) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN_SPAN, - span > 1 ? Integer.toString(span) : null); + public void setColumnSpanAttribute(INode node, int span) { + setGridAttribute(node, ATTR_LAYOUT_COLUMN_SPAN, span > 1 ? Integer.toString(span) : null); } /** @@ -1702,9 +1737,8 @@ public class GridModel { * @param node the target node * @param span the new row span */ - public static void setRowSpanAttribute(INode node, int span) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW_SPAN, - span > 1 ? Integer.toString(span) : null); + public void setRowSpanAttribute(INode node, int span) { + setGridAttribute(node, ATTR_LAYOUT_ROW_SPAN, span > 1 ? Integer.toString(span) : null); } /** Returns the index of the given target node in the given child node array */ @@ -1768,7 +1802,8 @@ public class GridModel { for (ViewData spacer : rowSpacers.values()) { layout.removeChild(spacer.node); } - layout.setAttribute(ANDROID_URI, ATTR_COLUMN_COUNT, Integer.toString(2)); + setGridAttribute(layout, ATTR_COLUMN_COUNT, 2); + return; } @@ -1792,8 +1827,7 @@ public class GridModel { nextSpacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, String.format(VALUE_N_DP, combinedSizeDp)); // Also move the spacer into this column - nextSpacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, - Integer.toString(column)); + setGridAttribute(nextSpacer.node, ATTR_LAYOUT_COLUMN, column); columnSpacers.put(column, nextSpacer); } else { continue; @@ -1815,8 +1849,7 @@ public class GridModel { if (view.column >= column) { if (view.column > 0) { view.column--; - view.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, - Integer.toString(view.column)); + setGridAttribute(view.node, ATTR_LAYOUT_COLUMN, view.column); } } else if (!view.isSpacer()) { int endColumn = view.column + view.columnSpan; @@ -1843,8 +1876,7 @@ public class GridModel { int combinedSizeDp = nextSizeDp + rowHeightDp; nextSpacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, String.format(VALUE_N_DP, combinedSizeDp)); - nextSpacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, - Integer.toString(row)); + setGridAttribute(nextSpacer.node, ATTR_LAYOUT_ROW, row); rowSpacers.put(row, nextSpacer); } else { continue; @@ -1865,8 +1897,7 @@ public class GridModel { if (view.row >= row) { if (view.row > 0) { view.row--; - view.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, - Integer.toString(view.row)); + setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); } } else if (!view.isSpacer()) { int endRow = view.row + view.rowSpan; @@ -1937,19 +1968,27 @@ public class GridModel { * @param heightDp the height in device independent pixels to assign to the spacer * @return the newly added spacer */ - static INode addSpacer(INode parent, int index, int row, int column, + INode addSpacer(INode parent, int index, int row, int column, int widthDp, int heightDp) { INode spacer; + + String tag = FQCN_SPACE; + String gridLayout = parent.getFqcn(); + if (!gridLayout.equals(GRID_LAYOUT) && gridLayout.length() > GRID_LAYOUT.length()) { + String pkg = gridLayout.substring(0, gridLayout.length() - GRID_LAYOUT.length()); + tag = pkg + SPACE; + } if (index != -1) { - spacer = parent.insertChildAt(FQCN_SPACE, index); + spacer = parent.insertChildAt(tag, index); } else { - spacer = parent.appendChild(FQCN_SPACE); + spacer = parent.appendChild(tag); } + if (row != UNDEFINED) { - spacer.setAttribute(ANDROID_URI, ATTR_LAYOUT_ROW, Integer.toString(row)); + setGridAttribute(spacer, ATTR_LAYOUT_ROW, row); } if (column != UNDEFINED) { - spacer.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN, Integer.toString(column)); + setGridAttribute(spacer, ATTR_LAYOUT_COLUMN, column); } if (widthDp > 0) { spacer.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, @@ -1985,41 +2024,34 @@ public class GridModel { } /** - * Returns the integer value of the given attribute, or the given defaultValue if the - * attribute was not set. + * Returns the string value of the given attribute, or null if it does not + * exist. This only works for attributes that are GridLayout specific, such + * as columnCount, layout_column, layout_row_span, etc. * * @param node the target node - * @param attribute the attribute name (which must be in the android: namespace) - * @param defaultValue the default value to use if the value is not set - * @return the attribute integer value + * @param name the attribute name (which must be in the android: namespace) + * @return the attribute value or null */ - private static int getInt(INode node, String attribute, int defaultValue) { - String valueString = node.getStringAttr(ANDROID_URI, attribute); - if (valueString != null) { - try { - return Integer.decode(valueString); - } catch (NumberFormatException nufe) { - // Ignore - error in user's XML - } - } - return defaultValue; + public String getGridAttribute(INode node, String name) { + return node.getStringAttr(getNamespace(), name); } /** - * Returns the float value of the given attribute, or the given defaultValue if the - * attribute was not set. + * Returns the integer value of the given attribute, or the given defaultValue if the + * attribute was not set. This only works for attributes that are GridLayout specific, + * such as columnCount, layout_column, layout_row_span, etc. * * @param node the target node * @param attribute the attribute name (which must be in the android: namespace) * @param defaultValue the default value to use if the value is not set - * @return the attribute float value + * @return the attribute integer value */ - private static float getFloat(INode node, String attribute, float defaultValue) { - String valueString = node.getStringAttr(ANDROID_URI, attribute); + private int getGridAttribute(INode node, String attribute, int defaultValue) { + String valueString = node.getStringAttr(getNamespace(), attribute); if (valueString != null) { try { - return Float.parseFloat(valueString); + return Integer.decode(valueString); } catch (NumberFormatException nufe) { // Ignore - error in user's XML } @@ -2029,24 +2061,6 @@ public class GridModel { } /** - * Returns the boolean value of the given attribute, or the given defaultValue if the - * attribute was not set. - * - * @param node the target node - * @param attribute the attribute name (which must be in the android: namespace) - * @param defaultValue the default value to use if the value is not set - * @return the attribute boolean value - */ - private static boolean getBoolean(INode node, String attribute, boolean defaultValue) { - String valueString = node.getStringAttr(ANDROID_URI, attribute); - if (valueString != null) { - return Boolean.valueOf(valueString); - } - - return defaultValue; - } - - /** * Returns the number of children views in the GridLayout * * @return the number of children views in the GridLayout diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java index 62b6804..7e0392a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java @@ -373,8 +373,12 @@ public class AdtUtils { * @param offset the offset to be checked * @return a list (possibly empty but never null) of matching markers */ - public static List<IMarker> findMarkersOnLine(String markerType, - IResource file, IDocument document, int offset) { + @NonNull + public static List<IMarker> findMarkersOnLine( + @NonNull String markerType, + @NonNull IResource file, + @NonNull IDocument document, + int offset) { List<IMarker> matchingMarkers = new ArrayList<IMarker>(2); try { IMarker[] markers = file.findMarkers(markerType, true, IResource.DEPTH_ZERO); @@ -413,6 +417,7 @@ public class AdtUtils { * * @return the available and open Android projects, never null */ + @NonNull public static IJavaProject[] getOpenAndroidProjects() { return BaseProjectHelper.getAndroidProjects(new IProjectFilter() { @Override @@ -423,6 +428,43 @@ public class AdtUtils { } /** + * Returns a unique project name, based on the given {@code base} file name + * possibly with a {@code conjunction} and a new number behind it to ensure + * that the project name is unique. For example, + * {@code getUniqueProjectName("project", "_")} will return + * {@code "project"} if that name does not already exist, and if it does, it + * will return {@code "project_2"}. + * + * @param base the base name to use, such as "foo" + * @param conjunction a string to insert between the base name and the + * number. + * @return a unique project name based on the given base and conjunction + */ + public static String getUniqueProjectName(String base, String conjunction) { + // We're using all workspace projects here rather than just open Android project + // via getOpenAndroidProjects because the name cannot conflict with non-Android + // or closed projects either + IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); + IProject[] projects = workspaceRoot.getProjects(); + + for (int i = 1; i < 1000; i++) { + String name = i == 1 ? base : base + conjunction + Integer.toString(i); + boolean found = false; + for (IProject project : projects) { + if (project.getName().equals(name)) { + found = true; + break; + } + } + if (!found) { + return name; + } + } + + return base; + } + + /** * Returns the name of the parent folder for the given editor input * * @param editorInput the editor input to check diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddCompatibilityJarAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddCompatibilityJarAction.java index b758b67..2428e60 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddCompatibilityJarAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddCompatibilityJarAction.java @@ -17,18 +17,28 @@ package com.android.ide.eclipse.adt.internal.actions; import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.AdtUtils; import com.android.ide.eclipse.adt.internal.project.ProjectHelper; import com.android.ide.eclipse.adt.internal.sdk.AdtConsoleSdkLog; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.sdklib.SdkConstants; +import com.android.sdklib.internal.project.ProjectProperties; +import com.android.sdklib.internal.project.ProjectProperties.PropertyType; +import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy; import com.android.sdklib.io.FileOp; import com.android.sdkuilib.internal.repository.sdkman2.AdtUpdateDialog; import com.android.util.Pair; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileStore; +import org.eclipse.core.filesystem.IFileSystem; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; @@ -117,12 +127,22 @@ public class AddCompatibilityJarAction implements IObjectActionDelegate { AdtPlugin.log(IStatus.ERROR, "JavaProject is null for %1$s", project); //$NON-NLS-1$ } + File jarPath = installSupport(); + if (jarPath != null) { + return addJar(javaProject, jarPath, waitForFinish); + } else { + return false; + } + } + + private static File installSupport() { + final Sdk sdk = Sdk.getCurrent(); if (sdk == null) { AdtPlugin.printErrorToConsole( AddCompatibilityJarAction.class.getSimpleName(), // tag "Error: Android SDK is not loaded yet."); //$NON-NLS-1$ - return false; + return null; } // TODO: For the generic action, check the library isn't in the project already. @@ -138,9 +158,12 @@ public class AddCompatibilityJarAction implements IObjectActionDelegate { Pair<Boolean, File> result = window.installExtraPackage( "android", "support"); //$NON-NLS-1$ //$NON-NLS-2$ + // TODO: Make sure the version is at the required level; we know we need at least one + // containing the v7 support + if (!result.getFirst().booleanValue()) { AdtPlugin.printErrorToConsole("Failed to install Android Compatibility library"); - return false; + return null; } // TODO these "v4" values needs to be dynamic, e.g. we could try to match @@ -153,11 +176,19 @@ public class AddCompatibilityJarAction implements IObjectActionDelegate { if (!jarPath.isFile()) { AdtPlugin.printErrorToConsole("Android Compatibility JAR not found:", jarPath.getAbsolutePath()); - return false; + return null; } - // Then run an Eclipse asynchronous job to update the project + return jarPath; + } + + private static boolean addJar( + final IJavaProject javaProject, + final File jarPath, + boolean waitForFinish) { + // Run an Eclipse asynchronous job to update the project + final IProject project = javaProject.getProject(); Job job = new Job("Add Compatibility Library to Project") { @Override protected IStatus run(IProgressMonitor monitor) { @@ -208,6 +239,212 @@ public class AddCompatibilityJarAction implements IObjectActionDelegate { return true; } + /** + * Similar to {@link #install}, but rather than copy a jar into the given + * project, it creates a new library project in the workspace for the + * compatibility library, and adds a library dependency on the newly + * installed library from the given project. + * + * @param project the project to add a dependency on the library to + * @param waitForFinish If true, block until the task has finished + * @return true if the installation was successful (or if + * <code>waitForFinish</code> is false, if the installation is + * likely to be successful - e.g. the user has at least agreed to + * all installation prompts.) + */ + public static boolean installLibrary(final IProject project, boolean waitForFinish) { + final IJavaProject javaProject = JavaCore.create(project); + if (javaProject != null) { + + File sdk = new File(Sdk.getCurrent().getSdkLocation()); + File supportPath = new File(sdk, + SdkConstants.FD_EXTRAS + File.separator + + "android" + File.separator //$NON-NLS-1$ + + "support"); //$NON-NLS-1$ + if (!supportPath.isDirectory()) { + File path = installSupport(); + if (path == null) { + return false; + } + assert path.equals(supportPath); + } + File libraryPath = new File(supportPath, + "v7" + File.separator //$NON-NLS-1$ + + "gridlayout"); //$NON-NLS-1$ + if (!libraryPath.isDirectory()) { + // Upgrade support package: it's out of date. The SDK manager will + // perform an upgrade to the latest version if the package is already installed. + File path = installSupport(); + if (path == null) { + return false; + } + assert path.equals(libraryPath) : path; + } + + // Create workspace copy of the project and add library dependency + IProject libraryProject = createLibraryProject(libraryPath, project, waitForFinish); + if (libraryProject != null) { + return addLibraryDependency(libraryProject, project, waitForFinish); + } + } + + return false; + } + + /** + * Creates a library project in the Eclipse workspace out of the grid layout project + * in the SDK tree. + * + * @param libraryPath the path to the directory tree containing the project contents + * @param project the project to copy the SDK target out of + * @param waitForFinish whether the operation should finish before this method returns + * @return a library project, or null if it fails for some reason + */ + private static IProject createLibraryProject( + final File libraryPath, + final IProject project, + boolean waitForFinish) { + + // Install a new library into the workspace. This is a copy rather than + // a reference to the compatibility library version such that modifications + // do not modify the pristine copy in the SDK install area. + + final IProject newProject; + try { + IProgressMonitor monitor = new NullProgressMonitor(); + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + + String name = AdtUtils.getUniqueProjectName( + "gridlayout_v7", "_"); //$NON-NLS-1$ //$NON-NLS-2$ + newProject = root.getProject(name); + newProject.create(monitor); + + // Copy in the files recursively + IFileSystem fileSystem = EFS.getLocalFileSystem(); + IFileStore sourceDir = fileSystem.getStore(libraryPath.toURI()); + IFileStore destDir = fileSystem.getStore(newProject.getLocationURI()); + sourceDir.copy(destDir, EFS.OVERWRITE, null); + + // Make sure the src folder exists + destDir.getChild("src").mkdir(0, null /*monitor*/); + + // Set the android platform to the same level as the calling project + ProjectState state = Sdk.getProjectState(project); + String target = state.getProperties().getProperty(ProjectProperties.PROPERTY_TARGET); + if (target != null && target.length() > 0) { + ProjectProperties properties = ProjectProperties.load(libraryPath.getPath(), + PropertyType.PROJECT); + ProjectPropertiesWorkingCopy copy = properties.makeWorkingCopy(); + copy.setProperty(ProjectProperties.PROPERTY_TARGET, target); + try { + copy.save(); + } catch (Exception e) { + AdtPlugin.log(e, null); + } + } + + newProject.open(monitor); + + return newProject; + } catch (CoreException e) { + AdtPlugin.log(e, null); + return null; + } + } + + /** + * Adds a library dependency on the given library into the given project. + * + * @param libraryProject the library project to depend on + * @param dependentProject the project to write the dependency into + * @param waitForFinish whether this method should wait for the job to + * finish + * @return true if the operation succeeded + */ + public static boolean addLibraryDependency( + final IProject libraryProject, + final IProject dependentProject, + boolean waitForFinish) { + + // Now add library dependency + + // Run an Eclipse asynchronous job to update the project + Job job = new Job("Add Compatibility Library Dependency to Project") { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + monitor.beginTask("Add library dependency to project build path", 3); + monitor.worked(1); + + // TODO: Add library project to the project.properties file! + ProjectState state = Sdk.getProjectState(dependentProject); + ProjectPropertiesWorkingCopy mPropertiesWorkingCopy = + state.getProperties().makeWorkingCopy(); + + // Get the highest version number of the libraries; there cannot be any + // gaps so we will assign the next library the next number + int nextVersion = 1; + for (String property : mPropertiesWorkingCopy.keySet()) { + if (property.startsWith(ProjectProperties.PROPERTY_LIB_REF)) { + String s = property.substring( + ProjectProperties.PROPERTY_LIB_REF.length()); + int version = Integer.parseInt(s); + if (version >= nextVersion) { + nextVersion = version + 1; + } + } + } + + IPath relativePath = libraryProject.getLocation().makeRelativeTo( + dependentProject.getLocation()); + + mPropertiesWorkingCopy.setProperty( + ProjectProperties.PROPERTY_LIB_REF + nextVersion, + relativePath.toString()); + try { + mPropertiesWorkingCopy.save(); + IResource projectProp = dependentProject.findMember( + SdkConstants.FN_PROJECT_PROPERTIES); + projectProp.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor()); + } catch (Exception e) { + String msg = String.format( + "Failed to save %1$s for project %2$s", + SdkConstants.FN_PROJECT_PROPERTIES, dependentProject.getName()); + AdtPlugin.log(e, msg); + } + + // Project fix-ups + Job fix = FixProjectAction.createFixProjectJob(libraryProject); + fix.schedule(); + fix.join(); + + monitor.worked(1); + + return Status.OK_STATUS; + } catch (Exception e) { + return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR, + "Failed", e); //$NON-NLS-1$ + } finally { + if (monitor != null) { + monitor.done(); + } + } + } + }; + job.schedule(); + + if (waitForFinish) { + try { + job.join(); + return job.getState() == IStatus.OK; + } catch (InterruptedException e) { + AdtPlugin.log(e, null); + } + } + + return true; + } + private static IResource copyJarIntoProject( IProject project, File jarPath, diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/FixProjectAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/FixProjectAction.java index c073022..254219f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/FixProjectAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/FixProjectAction.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.adt.internal.actions; +import com.android.annotations.NonNull; import com.android.ide.eclipse.adt.internal.project.AndroidNature; import com.android.ide.eclipse.adt.internal.project.ProjectHelper; @@ -82,7 +83,18 @@ public class FixProjectAction implements IObjectActionDelegate { } private void fixProject(final IProject project) { - new Job("Fix Project Properties") { + createFixProjectJob(project).schedule(); + } + + /** + * Creates a job to fix the project + * + * @param project the project to fix + * @return a job to perform the fix (not yet scheduled) + */ + @NonNull + public static Job createFixProjectJob(@NonNull final IProject project) { + return new Job("Fix Project Properties") { @Override protected IStatus run(IProgressMonitor monitor) { @@ -129,7 +141,7 @@ public class FixProjectAction implements IObjectActionDelegate { } } } - }.schedule(); + }; } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java index e915544..e8fbadb 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java @@ -21,8 +21,6 @@ import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AndroidPrintStream; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; -import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; -import com.android.ide.eclipse.adt.internal.project.ProjectHelper; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.IAndroidTarget; @@ -33,11 +31,9 @@ import com.android.sdklib.build.ApkBuilder.JarStatus; import com.android.sdklib.build.ApkBuilder.SigningInfo; import com.android.sdklib.build.ApkCreationException; import com.android.sdklib.build.DuplicateFileException; -import com.android.sdklib.build.IArchiveBuilder; import com.android.sdklib.build.SealedApkException; import com.android.sdklib.internal.build.DebugKeyProvider; import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException; -import com.android.sdklib.internal.build.SignedJarBuilder; import com.android.sdklib.util.GrabProcessOutput; import com.android.sdklib.util.GrabProcessOutput.IProcessOutput; import com.android.sdklib.util.GrabProcessOutput.Wait; @@ -46,9 +42,6 @@ import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceProxy; -import org.eclipse.core.resources.IResourceProxyVisitor; -import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; @@ -69,8 +62,12 @@ import java.io.PrintStream; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TreeMap; /** @@ -108,11 +105,14 @@ public class BuildHelper { private final boolean mVerbose; private final boolean mDebugMode; + private final Set<String> mCompiledCodePaths = new HashSet<String>(); + public static final boolean BENCHMARK_FLAG = false; public static long sStartOverallTime = 0; public static long sStartJavaCTime = 0; private final static int MILLION = 1000000; + private String mProguardFile; /** * An object able to put a marker on a resource. @@ -128,14 +128,18 @@ public class BuildHelper { * @param errStream * @param debugMode whether this is a debug build * @param verbose + * @throws CoreException */ public BuildHelper(IProject project, AndroidPrintStream outStream, - AndroidPrintStream errStream, boolean debugMode, boolean verbose) { + AndroidPrintStream errStream, boolean debugMode, boolean verbose, + ResourceMarker resMarker) throws CoreException { mProject = project; mOutStream = outStream; mErrStream = errStream; mDebugMode = debugMode; mVerbose = verbose; + + gatherPaths(resMarker); } public void updateCrunchCache() throws AaptExecException, AaptResultException { @@ -169,11 +173,6 @@ public class BuildHelper { } } - public static void writeResources(IArchiveBuilder builder, IJavaProject javaProject) - throws DuplicateFileException, ApkCreationException, SealedApkException, CoreException { - writeStandardResources(builder, javaProject, null); - } - /** * Packages the resources of the projet into a .ap_ file. * @param manifestFile the manifest of the project. @@ -287,9 +286,7 @@ public class BuildHelper { * @param intermediateApk The path to the temporary resource file. * @param dex The path to the dex file. * @param output The path to the final package file to create. - * @param javaProject the java project being compiled * @param libProjects an optional list of library projects (can be null) - * @param referencedJavaProjects referenced projects. * @return true if success, false otherwise. * @throws ApkCreationException * @throws AndroidLocationException @@ -299,8 +296,7 @@ public class BuildHelper { * @throws DuplicateFileException */ public void finalDebugPackage(String intermediateApk, String dex, String output, - final IJavaProject javaProject, List<IProject> libProjects, - List<IJavaProject> referencedJavaProjects, ResourceMarker resMarker) + List<IProject> libProjects, ResourceMarker resMarker) throws ApkCreationException, KeytoolException, AndroidLocationException, NativeLibInJarException, DuplicateFileException, CoreException { @@ -324,8 +320,7 @@ public class BuildHelper { // from the keystore, get the signing info SigningInfo info = ApkBuilder.getDebugKey(keystoreOsPath, mVerbose ? mOutStream : null); - finalPackage(intermediateApk, dex, output, javaProject, libProjects, - referencedJavaProjects, + finalPackage(intermediateApk, dex, output, libProjects, info != null ? info.key : null, info != null ? info.certificate : null, resMarker); } @@ -341,9 +336,7 @@ public class BuildHelper { * @param dex The path to the dex file. * @param output The path to the final package file to create. * @param debugSign whether the apk must be signed with the debug key. - * @param javaProject the java project being compiled * @param libProjects an optional list of library projects (can be null) - * @param referencedJavaProjects referenced projects. * @param abiFilter an optional filter. If not null, then only the matching ABI is included in * the final archive * @return true if success, false otherwise. @@ -353,9 +346,8 @@ public class BuildHelper { * @throws DuplicateFileException */ public void finalPackage(String intermediateApk, String dex, String output, - final IJavaProject javaProject, List<IProject> libProjects, - List<IJavaProject> referencedJavaProjects, PrivateKey key, - X509Certificate certificate, ResourceMarker resMarker) + List<IProject> libProjects, + PrivateKey key, X509Certificate certificate, ResourceMarker resMarker) throws NativeLibInJarException, ApkCreationException, DuplicateFileException, CoreException { @@ -365,19 +357,24 @@ public class BuildHelper { mVerbose ? mOutStream: null); apkBuilder.setDebugMode(mDebugMode); - // Now we write the standard resources from the project and the referenced projects. - writeStandardResources(apkBuilder, javaProject, referencedJavaProjects); + // either use the full compiled code paths or just the proguard file + // if present + Collection<String> pathsCollection = mCompiledCodePaths; + if (mProguardFile != null) { + pathsCollection = Collections.singletonList(mProguardFile); + mProguardFile = null; + } - // Now we write the standard resources from the external jars - for (String libraryOsPath : getExternalDependencies(resMarker)) { - File libFile = new File(libraryOsPath); - if (libFile.isFile()) { - JarStatus jarStatus = apkBuilder.addResourcesFromJar(new File(libraryOsPath)); + // Now we write the standard resources from all the output paths. + for (String path : pathsCollection) { + File file = new File(path); + if (file.isFile()) { + JarStatus jarStatus = apkBuilder.addResourcesFromJar(file); // check if we found native libraries in the external library. This // constitutes an error or warning depending on if they are in lib/ if (jarStatus.getNativeLibs().size() > 0) { - String libName = new File(libraryOsPath).getName(); + String libName = file.getName(); String msg = String.format( "Native libraries detected in '%1$s'. See console for more information.", @@ -418,11 +415,11 @@ public class BuildHelper { } } } - } else if (libFile.isDirectory()) { + } else if (file.isDirectory()) { // this is technically not a source folder (class folder instead) but since we // only care about Java resources (ie non class/java files) this will do the // same - apkBuilder.addSourceFolder(libFile); + apkBuilder.addSourceFolder(file); } } @@ -453,66 +450,15 @@ public class BuildHelper { } } - /** - * Return a list of the project output for compiled Java code. - * @return - * @throws CoreException - */ - public String[] getProjectJavaOutputs() throws CoreException { - IFolder outputFolder = BaseProjectHelper.getJavaOutputFolder(mProject); - - // get the list of referenced projects output to add - List<IProject> javaProjects = ProjectHelper.getReferencedProjects(mProject); - List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects(javaProjects); - - // get the project output, and since it's a new list object, just add the outputFolder - // of the project directly to it. - List<String> projectOutputs = getProjectJavaOutputs(referencedJavaProjects); - - projectOutputs.add(0, outputFolder.getLocation().toOSString()); - - return projectOutputs.toArray(new String[projectOutputs.size()]); + public void setProguardOutput(String proguardFile) { + mProguardFile = proguardFile; } - /** - * Returns an array for all the compiled code for the project. This can include the - * code compiled by Eclipse for the main project and dependencies (Java only projects), as well - * as external jars used by the project or its library. - * - * This array of paths is compatible with the input for dx and can be passed as is to - * {@link #executeDx(IJavaProject, String[], String)}. - * - * @param resMarker - * @return a array (never empty) containing paths to compiled code. - * @throws CoreException - */ - public String[] getCompiledCodePaths(boolean includeProjectOutputs, ResourceMarker resMarker) - throws CoreException { - - // get the list of libraries to include with the source code - String[] libraries = getExternalDependencies(resMarker); - - int startIndex = 0; - - String[] compiledPaths; - - if (includeProjectOutputs) { - String[] projectOutputs = getProjectJavaOutputs(); - - compiledPaths = new String[libraries.length + projectOutputs.length]; - - System.arraycopy(projectOutputs, 0, compiledPaths, 0, projectOutputs.length); - startIndex = projectOutputs.length; - } else { - compiledPaths = new String[libraries.length]; - } - - System.arraycopy(libraries, 0, compiledPaths, startIndex, libraries.length); - - return compiledPaths; + public Collection<String> getCompiledCodePaths() { + return mCompiledCodePaths; } - public void runProguard(List<File> proguardConfigs, File inputJar, String[] jarFiles, + public void runProguard(List<File> proguardConfigs, File inputJar, Collection<String> jarFiles, File obfuscatedJar, File logOutput) throws ProguardResultException, ProguardExecException, IOException { IAndroidTarget target = Sdk.getCurrent().getTarget(mProject); @@ -723,13 +669,14 @@ public class BuildHelper { /** * Execute the Dx tool for dalvik code conversion. * @param javaProject The java project - * @param inputPath the path to the main input of dex + * @param inputPaths the input paths for DX * @param osOutFilePath the path of the dex file to create. * * @throws CoreException * @throws DexException */ - public void executeDx(IJavaProject javaProject, String[] inputPaths, String osOutFilePath) + public void executeDx(IJavaProject javaProject, Collection<String> inputPaths, + String osOutFilePath) throws CoreException, DexException { // get the dex wrapper @@ -917,153 +864,94 @@ public class BuildHelper { } /** - * Writes the standard resources of a project and its referenced projects - * into a {@link SignedJarBuilder}. - * Standard resources are non java/aidl files placed in the java package folders. - * @param builder the archive builder. - * @param javaProject the javaProject object. - * @param referencedJavaProjects the java projects that this project references. - * @throws ApkCreationException if an error occurred - * @throws SealedApkException if the APK is already sealed. - * @throws DuplicateFileException if a file conflicts with another already added to the APK - * at the same location inside the APK archive. - * @throws CoreException - */ - private static void writeStandardResources(IArchiveBuilder builder, IJavaProject javaProject, - List<IJavaProject> referencedJavaProjects) - throws DuplicateFileException, ApkCreationException, SealedApkException, - CoreException { - IWorkspace ws = ResourcesPlugin.getWorkspace(); - IWorkspaceRoot wsRoot = ws.getRoot(); - - writeStandardProjectResources(builder, javaProject, wsRoot); - - if (referencedJavaProjects != null) { - for (IJavaProject referencedJavaProject : referencedJavaProjects) { - // only include output from non android referenced project - // (This is to handle the case of reference Android projects in the context of - // instrumentation projects that need to reference the projects to be tested). - if (referencedJavaProject.getProject().hasNature( - AdtConstants.NATURE_DEFAULT) == false) { - writeStandardProjectResources(builder, referencedJavaProject, wsRoot); - } - } - } - } - - /** - * Writes the standard resources of a {@link IJavaProject} into a {@link SignedJarBuilder}. - * Standard resources are non java/aidl files placed in the java package folders. - * @param jarBuilder the {@link ApkBuilder}. - * @param javaProject the javaProject object. - * @param wsRoot the {@link IWorkspaceRoot}. - * @throws ApkCreationException if an error occurred - * @throws SealedApkException if the APK is already sealed. - * @throws DuplicateFileException if a file conflicts with another already added to the APK - * at the same location inside the APK archive. + * Computes all the project output and dependencies that must go into building the apk. + * + * @param resMarker * @throws CoreException */ - private static void writeStandardProjectResources(IArchiveBuilder builder, - IJavaProject javaProject, IWorkspaceRoot wsRoot) - throws DuplicateFileException, ApkCreationException, SealedApkException, CoreException { - // get the source pathes - List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject); - - // loop on them and then recursively go through the content looking for matching files. - for (IPath sourcePath : sourceFolders) { - IResource sourceResource = wsRoot.findMember(sourcePath); - if (sourceResource != null && sourceResource.getType() == IResource.FOLDER) { - writeFolderResources(builder, javaProject, (IFolder) sourceResource); - } - } - } - - private static void writeFolderResources(IArchiveBuilder builder, - final IJavaProject javaProject, IFolder root) throws CoreException, - ApkCreationException, SealedApkException, DuplicateFileException { - final List<IPath> pathsToPackage = new ArrayList<IPath>(); - root.accept(new IResourceProxyVisitor() { - @Override - public boolean visit(IResourceProxy proxy) throws CoreException { - if (proxy.getType() == IResource.FOLDER) { - // If this folder isn't wanted, don't traverse into it. - return ApkBuilder.checkFolderForPackaging(proxy.getName()); - } - // If it's not a folder, it must be a file. We won't see any other resource type. - if (!ApkBuilder.checkFileForPackaging(proxy.getName())) { - return true; - } - IResource res = proxy.requestResource(); - if (!javaProject.isOnClasspath(res)) { - return true; - } - // Just record that we need to package this. Packaging here throws - // inappropriate checked exceptions. - IPath location = res.getLocation(); - pathsToPackage.add(location); - return true; - } - }, 0); - IPath rootLocation = root.getLocation(); - for (IPath path : pathsToPackage) { - IPath archivePath = path.makeRelativeTo(rootLocation); - builder.addFile(path.toFile(), archivePath.toString()); - } - } + private void gatherPaths(ResourceMarker resMarker) + throws CoreException { + IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); - /** - * Returns an array of external dependencies used the project. This can be paths to jar files - * or to source folders. - * - * @param resMarker if non null, used to put Resource marker on problem files. - * @return an array of OS-specific absolute file paths - */ - private final String[] getExternalDependencies(ResourceMarker resMarker) { - // get a java project from it + // get a java project for the project. IJavaProject javaProject = JavaCore.create(mProject); - IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); - ArrayList<String> oslibraryList = new ArrayList<String>(); + // get the output of the main project + IPath path = javaProject.getOutputLocation(); + IResource outputResource = wsRoot.findMember(path); + if (outputResource != null && outputResource.getType() == IResource.FOLDER) { + mCompiledCodePaths.add(outputResource.getLocation().toOSString()); + } // we could use IJavaProject.getResolvedClasspath directly, but we actually // want to see the containers themselves. IClasspathEntry[] classpaths = javaProject.readRawClasspath(); if (classpaths != null) { for (IClasspathEntry e : classpaths) { - // if this is a classpath variable reference, we resolve it. - if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { - e = JavaCore.getResolvedClasspathEntry(e); + // ignore non exported entries, unless it's the LIBRARIES container, + // in which case we always want it (there may be some older projects that + // have it as non exported). + if (e.isExported() || + (e.getEntryKind() == IClasspathEntry.CPE_CONTAINER && + e.getPath().toString().equals(AdtConstants.CONTAINER_LIBRARIES))) { + handleCPE(e, javaProject, wsRoot, resMarker); } + } + } + } - if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { - handleClasspathEntry(e, wsRoot, oslibraryList, resMarker); - } else if (e.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { - // get the container - try { - IClasspathContainer container = JavaCore.getClasspathContainer( - e.getPath(), javaProject); - // ignore the system and default_system types as they represent - // libraries that are part of the runtime. - if (container.getKind() == IClasspathContainer.K_APPLICATION) { - IClasspathEntry[] entries = container.getClasspathEntries(); - for (IClasspathEntry entry : entries) { - handleClasspathEntry(entry, wsRoot, oslibraryList, resMarker); - } - } - } catch (JavaModelException jme) { - // can't resolve the container? ignore it. - AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s", e.getPath()); + private void handleCPE(IClasspathEntry entry, IJavaProject javaProject, + IWorkspaceRoot wsRoot, ResourceMarker resMarker) { + + // if this is a classpath variable reference, we resolve it. + if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { + entry = JavaCore.getResolvedClasspathEntry(entry); + } + + if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) { + IProject refProject = wsRoot.getProject(entry.getPath().lastSegment()); + try { + // ignore if it's an Android project, or if it's not a Java Project + if (refProject.hasNature(JavaCore.NATURE_ID) && + refProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) { + IJavaProject refJavaProject = JavaCore.create(refProject); + + // get the output folder + IPath path = refJavaProject.getOutputLocation(); + IResource outputResource = wsRoot.findMember(path); + if (outputResource != null && outputResource.getType() == IResource.FOLDER) { + mCompiledCodePaths.add(outputResource.getLocation().toOSString()); } } + } catch (CoreException exception) { + // can't query the project nature? ignore } - } - return oslibraryList.toArray(new String[oslibraryList.size()]); + } else if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { + handleClasspathLibrary(entry, wsRoot, resMarker); + } else if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { + // get the container + try { + IClasspathContainer container = JavaCore.getClasspathContainer( + entry.getPath(), javaProject); + // ignore the system and default_system types as they represent + // libraries that are part of the runtime. + if (container.getKind() == IClasspathContainer.K_APPLICATION) { + IClasspathEntry[] entries = container.getClasspathEntries(); + for (IClasspathEntry cpe : entries) { + handleCPE(cpe, javaProject, wsRoot, resMarker); + } + } + } catch (JavaModelException jme) { + // can't resolve the container? ignore it. + AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s", entry.getPath()); + } + } } - private void handleClasspathEntry(IClasspathEntry e, IWorkspaceRoot wsRoot, - ArrayList<String> oslibraryList, ResourceMarker resMarker) { + private void handleClasspathLibrary(IClasspathEntry e, IWorkspaceRoot wsRoot, + ResourceMarker resMarker) { // get the IPath IPath path = e.getPath(); @@ -1077,7 +965,7 @@ public class BuildHelper { // case of a jar file (which could be relative to the workspace or a full path) if (resource != null && resource.exists() && resource.getType() == IResource.FILE) { - oslibraryList.add(resource.getLocation().toOSString()); + mCompiledCodePaths.add(resource.getLocation().toOSString()); } else { // if the jar path doesn't match a workspace resource, // then we get an OSString and check if this links to a valid file. @@ -1085,7 +973,7 @@ public class BuildHelper { File f = new File(osFullPath); if (f.isFile()) { - oslibraryList.add(osFullPath); + mCompiledCodePaths.add(osFullPath); } else { String message = String.format( Messages.Couldnt_Locate_s_Error, path); @@ -1102,7 +990,7 @@ public class BuildHelper { // this can be the case for a class folder. if (resource != null && resource.exists() && resource.getType() == IResource.FOLDER) { - oslibraryList.add(resource.getLocation().toOSString()); + mCompiledCodePaths.add(resource.getLocation().toOSString()); } else { // if the path doesn't match a workspace resource, // then we get an OSString and check if this links to a valid folder. @@ -1110,53 +998,13 @@ public class BuildHelper { File f = new File(osFullPath); if (f.isDirectory()) { - oslibraryList.add(osFullPath); + mCompiledCodePaths.add(osFullPath); } } } } /** - * Returns the list of the output folders for the specified {@link IJavaProject} objects, if - * they are Android projects. - * - * @param referencedJavaProjects the java projects. - * @return a new list object containing the output folder paths. - * @throws CoreException - */ - private List<String> getProjectJavaOutputs(List<IJavaProject> referencedJavaProjects) - throws CoreException { - ArrayList<String> list = new ArrayList<String>(); - - IWorkspace ws = ResourcesPlugin.getWorkspace(); - IWorkspaceRoot wsRoot = ws.getRoot(); - - for (IJavaProject javaProject : referencedJavaProjects) { - // only include output from non android referenced project - // (This is to handle the case of reference Android projects in the context of - // instrumentation projects that need to reference the projects to be tested). - if (javaProject.getProject().hasNature(AdtConstants.NATURE_DEFAULT) == false) { - // get the output folder - IPath path = null; - try { - path = javaProject.getOutputLocation(); - } catch (JavaModelException e) { - continue; - } - - IResource outputResource = wsRoot.findMember(path); - if (outputResource != null && outputResource.getType() == IResource.FOLDER) { - String outputOsPath = outputResource.getLocation().toOSString(); - - list.add(outputOsPath); - } - } - } - - return list; - } - - /** * Checks a {@link IFile} to make sure it should be packaged as standard resources. * @param file the IFile representing the file. * @return true if the file should be packaged as standard java resources. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/DexWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/DexWrapper.java index e03a150..015d230 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/DexWrapper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/DexWrapper.java @@ -30,6 +30,7 @@ import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import java.util.Collection; /** * Wrapper to access dx.jar through reflection. @@ -144,7 +145,7 @@ public final class DexWrapper { * @return the integer return code of com.android.dx.command.dexer.Main.run() * @throws CoreException */ - public synchronized int run(String osOutFilePath, String[] osFilenames, + public synchronized int run(String osOutFilePath, Collection<String> osFilenames, boolean verbose, PrintStream outStream, PrintStream errStream) throws CoreException { assert mRunMethod != null; @@ -171,7 +172,7 @@ public final class DexWrapper { // create the Arguments object. Object args = mArgConstructor.newInstance(); mArgOutName.set(args, osOutFilePath); - mArgFileNames.set(args, osFilenames); + mArgFileNames.set(args, osFilenames.toArray(new String[osFilenames.size()])); mArgJarOutput.set(args, false); mArgVerbose.set(args, verbose); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java index 6c4eca4..8be6863 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java @@ -38,6 +38,7 @@ import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.io.IFileWrapper; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.SdkConstants; +import com.android.sdklib.build.ApkBuilder; import com.android.sdklib.build.ApkCreationException; import com.android.sdklib.build.DuplicateFileException; import com.android.sdklib.build.IArchiveBuilder; @@ -68,6 +69,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.jar.Attributes; @@ -427,7 +429,8 @@ public class PostCompilerBuilder extends BaseBuilder { BuildHelper helper = new BuildHelper(project, mOutStream, mErrStream, true /*debugMode*/, - AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE); + AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE, + mResourceMarker); updateCrunchCache(project, helper); // refresh recursively bin/res folder @@ -542,7 +545,8 @@ public class PostCompilerBuilder extends BaseBuilder { BuildHelper helper = new BuildHelper(project, mOutStream, mErrStream, true /*debugMode*/, - AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE); + AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE, + mResourceMarker); // resource to the AndroidManifest.xml file IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML); @@ -637,8 +641,7 @@ public class PostCompilerBuilder extends BaseBuilder { System.out.println("\trunning dex!"); } try { - String[] dxInputPaths = helper.getCompiledCodePaths( - true /*includeProjectOutputs*/, mResourceMarker); + Collection<String> dxInputPaths = helper.getCompiledCodePaths(); helper.executeDx(javaProject, dxInputPaths, classesDexPath); } catch (DexException e) { @@ -677,8 +680,7 @@ public class PostCompilerBuilder extends BaseBuilder { } helper.finalDebugPackage( osAndroidBinPath + File.separator + AdtConstants.FN_RESOURCES_AP_, - classesDexPath, osFinalPackagePath, - javaProject, libProjects, referencedJavaProjects, mResourceMarker); + classesDexPath, osFinalPackagePath, libProjects, mResourceMarker); } catch (KeytoolException e) { String eMessage = e.getMessage(); @@ -920,8 +922,8 @@ public class PostCompilerBuilder extends BaseBuilder { // write the class files writeClassFilesIntoJar(jarBuilder, javaOutputFolder, javaOutputFolder); - // now write the standard Java resources - BuildHelper.writeResources(jarBuilder, JavaCore.create(project)); + // now write the standard Java resources from the output folder + ApkBuilder.addSourceFolder(jarBuilder, javaOutputFolder.getLocation().toFile()); saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex); } catch (Exception e) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java index 8234f25..7639338 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java @@ -99,7 +99,7 @@ public class PreCompilerBuilder extends BaseBuilder { private boolean mMustCreateBuildConfig = false; private boolean mLastBuildConfigMode; - private final List<SourceProcessor> mProcessors = new ArrayList<SourceProcessor>(); + private final List<SourceProcessor> mProcessors = new ArrayList<SourceProcessor>(2); /** cache of the java package defined in the manifest */ private String mManifestPackage; @@ -588,7 +588,7 @@ public class PreCompilerBuilder extends BaseBuilder { Messages.Removing_Generated_Classes); // remove all the derived resources from the 'gen' source folder. - if (mGenFolder != null) { + if (mGenFolder != null && mGenFolder.exists()) { // gen folder should not be derived, but previous version could set it to derived // so we make sure this isn't the case (or it'll get deleted by the clean) mGenFolder.setDerived(false, monitor); @@ -634,10 +634,11 @@ public class PreCompilerBuilder extends BaseBuilder { // load the source processors SourceProcessor aidlProcessor = new AidlProcessor(javaProject, mGenFolder); - mProcessors.add(aidlProcessor); SourceProcessor renderScriptProcessor = new RenderScriptProcessor(javaProject, mGenFolder); + mProcessors.add(aidlProcessor); mProcessors.add(renderScriptProcessor); + } catch (Throwable throwable) { AdtPlugin.log(throwable, "Failed to finish PrecompilerBuilder#startupOnInitialize()"); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java index dfdc740..ca13d38 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java @@ -17,6 +17,7 @@ package com.android.ide.eclipse.adt.internal.editors; +import com.android.annotations.NonNull; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.sdklib.SdkConstants; @@ -104,7 +105,7 @@ public class IconFactory { * Callers should not dispose it. * * @param osName The leaf name, without the extension, of an existing icon in the - * editor's "icons" directory. If it doesn't exists, a default icon will be + * editor's "icons" directory. If it doesn't exist, a default icon will be * generated automatically based on the name. * @param color The color of the text in the automatically generated icons, * one of COLOR_DEFAULT, COLOR_RED, COLOR_BLUE or COLOR_RED. @@ -172,6 +173,60 @@ public class IconFactory { } /** + * Returns an Image for a given icon name. + * <p/> + * Callers should not dispose it. + * + * @param osName The leaf name, without the extension, of an existing icon + * in the editor's "icons" directory. If it doesn't exist, the + * fallback will be used instead. + * @param fallback the fallback icon name to use if the primary icon does + * not exist. + * @return the icon, which should not be disposed by the caller + */ + public Image getIcon(String osName, String fallback) { + String key = osName; + Image icon = mIconMap.get(key); + if (icon == null && !mIconMap.containsKey(key)) { + ImageDescriptor id = getImageDescriptor(osName, fallback); + if (id != null) { + icon = id.createImage(); + } + // Note that we store null references in the icon map, to avoid looking them + // up every time. If it didn't exist once, it will not exist later. + mIconMap.put(key, icon); + } + return icon; + } + + /** + * Returns an icon of the given name, or if that image does not exist and icon + * of the given fallback name. + * + * @param key the icon name + * @param fallbackKey the fallback image to use if the primary key does not exist + * @return the image descriptor + */ + @NonNull + public ImageDescriptor getImageDescriptor(@NonNull String key, @NonNull String fallbackKey) { + ImageDescriptor id = mImageDescMap.get(key); + if (id == null && !mImageDescMap.containsKey(key)) { + id = AbstractUIPlugin.imageDescriptorFromPlugin( + AdtPlugin.PLUGIN_ID, + String.format("/icons/%1$s.png", key)); //$NON-NLS-1$ + if (id == null) { + id = getImageDescriptor(fallbackKey); + } + + // Place the fallback image for this key as well such that we don't keep trying + // to load the failed image + mImageDescMap.put(key, id); + } + + return id; + } + + /** * Returns the image indicated by the given URL * * @param url the url to the image resources diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlEditor.java index 5481456..96ce82b 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonXmlEditor.java @@ -136,10 +136,19 @@ public class CommonXmlEditor extends AndroidXmlEditor implements IShowEditorInpu ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file); ResourceFolderType type = resFolder == null ? null : resFolder.getType(); - for (IDelegateCreator creator : DELEGATES) { - mDelegate = creator.createForFile(this, type); - if (mDelegate != null) { - break; + if (type == null) { + // We lack any real resource information about that file. + // Let's take a guess using the actual path. + String folderName = AdtUtils.getParentFolderName(editorInput); + type = ResourceFolderType.getFolderType(folderName); + } + + if (type != null) { + for (IDelegateCreator creator : DELEGATES) { + mDelegate = creator.createForFile(this, type); + if (mDelegate != null) { + break; + } } } @@ -161,7 +170,7 @@ public class CommonXmlEditor extends AndroidXmlEditor implements IShowEditorInpu // and IProjects so for now just use a plain XML editor for project-less layout // files mDelegate = new OtherXmlEditorDelegate(this); - } else { + } else if (type != null) { for (IDelegateCreator creator : DELEGATES) { mDelegate = creator.createForFile(this, type); if (mDelegate != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java index 30c7687..f44faf4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java @@ -773,7 +773,9 @@ public final class DescriptorsUtils { || tag.equals(VIEW_FRAGMENT) || tag.equals(VIEW_INCLUDE) || tag.equals(VIEW_MERGE) - || tag.equals(SPACE)) { + || tag.equals(SPACE) + || tag.endsWith(SPACE) && tag.length() > SPACE.length() && + tag.charAt(tag.length() - SPACE.length()) == '.') { return false; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java index 90e46cf..5abbb0c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java @@ -319,7 +319,15 @@ public final class CustomViewDescriptorService { @Override public Image getGenericIcon() { - return IconFactory.getInstance().getIcon("customView"); //$NON-NLS-1$ + IconFactory iconFactory = IconFactory.getInstance(); + + int index = mXmlName.lastIndexOf('.'); + if (index != -1) { + return iconFactory.getIcon(mXmlName.substring(index + 1), + "customView"); //$NON-NLS-1$ + } + + return iconFactory.getIcon("customView"); //$NON-NLS-1$ } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java index 792194e..dd103c5 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java @@ -17,6 +17,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import static com.android.ide.common.layout.LayoutConstants.FQCN_SPACE; +import static com.android.ide.common.layout.LayoutConstants.FQCN_SPACE_V7; import static com.android.ide.common.layout.LayoutConstants.GESTURE_OVERLAY_VIEW; import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_MERGE; @@ -457,7 +458,7 @@ public class CanvasViewInfo implements IPropertySource { return false; } - return FQCN_SPACE.equals(mName); + return FQCN_SPACE.equals(mName) || FQCN_SPACE_V7.equals(mName); } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java index b8ab3fd..b5bcf29 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java @@ -285,8 +285,9 @@ public class ClipboardSupport { mCanvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel("Paste", new Runnable() { @Override public void run() { - mCanvas.getRulesEngine().callOnPaste(targetNode, target.getViewObject(), pasted); - targetNode.applyPendingChanges(); + RulesEngine engine = mCanvas.getRulesEngine(); + NodeProxy node = engine.callOnPaste(targetNode, target.getViewObject(), pasted); + node.applyPendingChanges(); } }); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java index 7c8cc81..b43cff9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java @@ -1658,6 +1658,7 @@ public class GraphicalEditorPart extends EditorPart for (String clazz : brokenClasses) { addText(mErrorLabel, "- "); + addText(mErrorLabel, clazz); addText(mErrorLabel, " ("); addActionLink(mErrorLabel, ActionLinkStyleRange.LINK_OPEN_CLASS, "Open Class", clazz); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java index 485d574..c5158af 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java @@ -18,18 +18,23 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS; +import static com.android.ide.common.layout.LayoutConstants.ATTR_COLUMN_COUNT; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_COLUMN; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_COLUMN_SPAN; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW_SPAN; import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ROW_COUNT; import static com.android.ide.common.layout.LayoutConstants.ATTR_SRC; import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; import static com.android.ide.common.layout.LayoutConstants.DRAWABLE_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.GRID_LAYOUT; import static com.android.ide.common.layout.LayoutConstants.LAYOUT_PREFIX; import static com.android.ide.common.layout.LayoutConstants.LINEAR_LAYOUT; import static com.android.ide.common.layout.LayoutConstants.VALUE_VERTICAL; import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_VIEWTAG; +import static com.android.tools.lint.detector.api.LintConstants.AUTO_URI; +import static com.android.tools.lint.detector.api.LintConstants.URI_PREFIX; import static org.eclipse.jface.viewers.StyledString.QUALIFIER_STYLER; import com.android.annotations.VisibleForTesting; @@ -46,10 +51,14 @@ import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDes import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference; import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; +import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; import com.android.ide.eclipse.adt.internal.editors.ui.ErrorImageComposite; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.util.Pair; +import org.eclipse.core.resources.IProject; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.ActionContributionItem; import org.eclipse.jface.action.IAction; @@ -58,6 +67,7 @@ import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; +import org.eclipse.jface.preference.JFacePreferences; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.IElementComparer; @@ -66,6 +76,7 @@ import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.ITreeSelection; import org.eclipse.jface.viewers.StyledCellLabelProvider; import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.StyledString.Styler; import org.eclipse.jface.viewers.TreePath; import org.eclipse.jface.viewers.TreeSelection; import org.eclipse.jface.viewers.TreeViewer; @@ -559,21 +570,59 @@ public class OutlinePage extends ContentOutlinePage Element e = (Element) xmlNode; // Temporary diagnostics code when developing GridLayout - if (GridLayoutRule.sDebugGridLayout && e.getParentNode() != null - && e.getParentNode().getNodeName() != null) { - if (e.getParentNode().getNodeName().equals("GridLayout")) { //$NON-NLS-1$ + if (GridLayoutRule.sDebugGridLayout) { + String namespace; + if (e.getParentNode().getNodeName().equals(GRID_LAYOUT)) { + namespace = ANDROID_URI; + } else { + IProject project = mGraphicalEditorPart.getProject(); + ProjectState projectState = Sdk.getProjectState(project); + if (projectState != null && projectState.isLibrary()) { + namespace = AUTO_URI; + } else { + ManifestInfo info = ManifestInfo.get(project); + namespace = URI_PREFIX + info.getPackage(); + } + } + + if (e.getNodeName() != null && e.getNodeName().endsWith(GRID_LAYOUT)) { + // Attach rowCount/columnCount info + String rowCount = e.getAttributeNS(namespace, ATTR_ROW_COUNT); + if (rowCount.length() == 0) { + rowCount = "?"; + } + String columnCount = e.getAttributeNS(namespace, ATTR_COLUMN_COUNT); + if (columnCount.length() == 0) { + columnCount = "?"; + } + + styledString.append(" - columnCount=", QUALIFIER_STYLER); + styledString.append(columnCount, QUALIFIER_STYLER); + styledString.append(", rowCount=", QUALIFIER_STYLER); + styledString.append(rowCount, QUALIFIER_STYLER); + } else if (e.getParentNode() != null + && e.getParentNode().getNodeName() != null + && e.getParentNode().getNodeName().endsWith(GRID_LAYOUT)) { // Attach row/column info - styledString.append(" - cell (", QUALIFIER_STYLER); - String row = e.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_ROW); + String row = e.getAttributeNS(namespace, ATTR_LAYOUT_ROW); if (row.length() == 0) { row = "?"; } - String column = e.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_COLUMN); + Styler colStyle = QUALIFIER_STYLER; + String column = e.getAttributeNS(namespace, ATTR_LAYOUT_COLUMN); if (column.length() == 0) { column = "?"; + } else { + String colCount = ((Element) e.getParentNode()).getAttributeNS( + namespace, ATTR_COLUMN_COUNT); + if (colCount.length() > 0 && Integer.parseInt(colCount) <= + Integer.parseInt(column)) { + colStyle = StyledString.createColorRegistryStyler( + JFacePreferences.ERROR_COLOR, null); + } } - String rowSpan = e.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_ROW_SPAN); - String columnSpan = e.getAttributeNS(ANDROID_URI, + String rowSpan = e.getAttributeNS(namespace, ATTR_LAYOUT_ROW_SPAN); + String columnSpan = e.getAttributeNS(namespace, ATTR_LAYOUT_COLUMN_SPAN); if (rowSpan.length() == 0) { rowSpan = "1"; @@ -582,10 +631,13 @@ public class OutlinePage extends ContentOutlinePage columnSpan = "1"; } + styledString.append(" - cell (row=", QUALIFIER_STYLER); styledString.append(row, QUALIFIER_STYLER); styledString.append(',', QUALIFIER_STYLER); - styledString.append(column, QUALIFIER_STYLER); - styledString.append("), span=(", QUALIFIER_STYLER); + styledString.append("col=", colStyle); + styledString.append(column, colStyle); + styledString.append(')', colStyle); + styledString.append(", span=(", QUALIFIER_STYLER); styledString.append(columnSpan, QUALIFIER_STYLER); styledString.append(',', QUALIFIER_STYLER); styledString.append(rowSpan, QUALIFIER_STYLER); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java index 495600f..e2c573a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import static com.android.ide.common.layout.LayoutConstants.FQCN_SPACE; +import static com.android.ide.common.layout.LayoutConstants.FQCN_SPACE_V7; import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.PIXEL_MARGIN; import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.PIXEL_RADIUS; @@ -874,7 +875,8 @@ public class SelectionManager implements ISelectionProvider { // Skip spacers - unless you're dropping just one continue; } - if (GridLayoutRule.sDebugGridLayout && viewInfo.getName().equals(FQCN_SPACE)) { + if (GridLayoutRule.sDebugGridLayout && (viewInfo.getName().equals(FQCN_SPACE) + || viewInfo.getName().equals(FQCN_SPACE_V7))) { // In debug mode they might not be marked as hidden but we never never // want to select these guys continue; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java index 9b186a1..7d21484 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java @@ -18,6 +18,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gre; import static com.android.sdklib.SdkConstants.CLASS_FRAGMENT; import static com.android.sdklib.SdkConstants.CLASS_V4_FRAGMENT; +import static com.android.tools.lint.detector.api.LintConstants.AUTO_URI; +import static com.android.tools.lint.detector.api.LintConstants.URI_PREFIX; import com.android.ide.common.api.IClientRulesEngine; import com.android.ide.common.api.INode; @@ -43,6 +45,7 @@ import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.internal.ui.MarginChooser; import com.android.ide.eclipse.adt.internal.ui.ReferenceChooserDialog; @@ -193,7 +196,9 @@ class ClientRulesEngine implements IClientRulesEngine { Sdk currentSdk = Sdk.getCurrent(); if (currentSdk != null) { IAndroidTarget target = currentSdk.getTarget(mRulesEngine.getEditor().getProject()); - return target.getVersion().getApiLevel(); + if (target != null) { + return target.getVersion().getApiLevel(); + } } return -1; @@ -353,6 +358,7 @@ class ClientRulesEngine implements IClientRulesEngine { // First check to make sure fragments are available, and if not, // warn the user. IAndroidTarget target = Sdk.getCurrent().getTarget(project); + // No, this should be using the min SDK instead! if (target.getVersion().getApiLevel() < 11 && oldFragmentType == null) { // Compatibility library must be present MessageDialog dialog = @@ -553,7 +559,14 @@ class ClientRulesEngine implements IClientRulesEngine { @Override public String getAppNameSpace() { - ManifestInfo info = ManifestInfo.get(mRulesEngine.getEditor().getProject()); - return info.getPackage(); + IProject project = mRulesEngine.getEditor().getProject(); + + ProjectState projectState = Sdk.getProjectState(project); + if (projectState != null && projectState.isLibrary()) { + return AUTO_URI; + } + + ManifestInfo info = ManifestInfo.get(project); + return URI_PREFIX + info.getPackage(); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java index a4306fa..ea464c1 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java @@ -37,6 +37,7 @@ import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElement import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.adt.internal.project.CompatibilityLibraryHelper; import org.eclipse.swt.graphics.Rectangle; import org.w3c.dom.NamedNodeMap; @@ -256,6 +257,13 @@ public class NodeProxy implements INode { private INode insertOrAppend(String viewFqcn, int index) { checkEditOK(); + AndroidXmlEditor editor = mNode.getEditor(); + if (editor != null) { + // Possibly replace the tag with a compatibility version if the + // minimum SDK requires it + viewFqcn = CompatibilityLibraryHelper.getTagFor(editor.getProject(), viewFqcn); + } + // Find the descriptor for this FQCN ViewElementDescriptor vd = getFqcnViewDescriptor(viewFqcn); if (vd == null) { @@ -277,14 +285,12 @@ public class NodeProxy implements INode { } } + // Set default attributes -- but only for new widgets (not when moving or copying) RulesEngine engine = null; - AndroidXmlEditor editor = mNode.getEditor(); LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(editor); if (delegate != null) { engine = delegate.getRulesEngine(); } - - // Set default attributes -- but only for new widgets (not when moving or copying) if (engine == null || engine.getInsertType().isCreate()) { // TODO: This should probably use IViewRule#getDefaultAttributes() at some point DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java index 9c3af1d..a8438d8 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java @@ -368,9 +368,30 @@ public class RulesEngine { * @param targetNode The first node selected. * @param targetView The view object for the target node, or null if not known * @param pastedElements The elements being pasted. + * @return the parent node the paste was applied into */ - public void callOnPaste(NodeProxy targetNode, Object targetView, + public NodeProxy callOnPaste(NodeProxy targetNode, Object targetView, SimpleElement[] pastedElements) { + + // Find a target which accepts children. If you for example select a button + // and attempt to paste, this will reselect the parent of the button as the paste + // target. (This is a loop rather than just checking the direct parent since + // we will soon ask each child whether they are *willing* to accept the new child. + // A ScrollView for example, which only accepts one child, might also say no + // and delegate to its parent in turn. + INode parent = targetNode; + while (parent instanceof NodeProxy) { + NodeProxy np = (NodeProxy) parent; + if (np.getNode() != null && np.getNode().getDescriptor() != null) { + ElementDescriptor descriptor = np.getNode().getDescriptor(); + if (descriptor.hasChildren()) { + targetNode = np; + break; + } + } + parent = parent.getParent(); + } + // try to find a rule for this element's FQCN IViewRule rule = loadRule(targetNode.getNode()); @@ -385,6 +406,8 @@ public class RulesEngine { e.toString()); } } + + return targetNode; } // ---- Resize operations ---- diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/GridLayoutConverter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/GridLayoutConverter.java index 912d216..daf1f54 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/GridLayoutConverter.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/GridLayoutConverter.java @@ -38,6 +38,7 @@ import static com.android.ide.common.layout.LayoutConstants.FQCN_GRID_LAYOUT; import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_FILL; import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_FILL_HORIZONTAL; import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_FILL_VERTICAL; +import static com.android.ide.common.layout.LayoutConstants.GRID_LAYOUT; import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX; import static com.android.ide.common.layout.LayoutConstants.LINEAR_LAYOUT; import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; @@ -210,20 +211,26 @@ class GridLayoutConverter { // TODO: May also have to increment column count! int offset = 0; // WHERE? + String gridLayout = mLayout.getTagName(); if (mLayout instanceof IndexedRegion) { IndexedRegion region = (IndexedRegion) mLayout; int end = region.getEndOffset(); // TODO: Look backwards for the "</" // (and can it ever be <foo/>) ? - end -= (mLayout.getTagName().length() + 3); // 3: <, /, > + end -= (gridLayout.length() + 3); // 3: <, /, > offset = end; } int row = rowFixed.size(); int column = columnFixed.size(); StringBuilder sb = new StringBuilder(64); - String tag = SPACE; - sb.append('<').append(tag).append(' '); + String spaceTag = SPACE; + if (!gridLayout.equals(GRID_LAYOUT) && gridLayout.length() > GRID_LAYOUT.length()) { + String pkg = gridLayout.substring(0, gridLayout.length() - GRID_LAYOUT.length()); + spaceTag = pkg + spaceTag; + } + + sb.append('<').append(spaceTag).append(' '); String gravity; if (!hasStretchableRow && !hasStretchableColumn) { gravity = GRAVITY_VALUE_FILL; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java index 0480cda..53b8f49 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java @@ -56,6 +56,7 @@ import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.w3c.dom.Attr; import org.w3c.dom.Document; @@ -283,6 +284,17 @@ public class UseCompoundDrawableRefactoring extends VisualRefactoring { setAndroidAttribute(newTextElement, androidNsPrefix, drawableAttribute, src); + // If the removed LinearLayout is the root container, transfer its namespace + // declaration to the TextView + if (layout.getParentNode() instanceof Document) { + List<Attr> declarations = findNamespaceAttributes(layout); + for (Attr attribute : declarations) { + if (attribute instanceof IndexedRegion) { + newTextElement.setAttribute(attribute.getName(), attribute.getValue()); + } + } + } + // Update any layout references to the layout to point to the text view String layoutId = getId(layout); if (layoutId.length() > 0) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java index 4ec3801..f749e2b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java @@ -29,6 +29,8 @@ import static com.android.sdklib.xml.AndroidManifest.NODE_ACTIVITY; import static com.android.sdklib.xml.AndroidManifest.NODE_USES_SDK; import static org.eclipse.jdt.core.search.IJavaSearchConstants.REFERENCES; +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; import com.android.ide.eclipse.adt.internal.sdk.Sdk; @@ -103,6 +105,7 @@ public class ManifestInfo { private Map<String, String> mActivityThemes; private IAbstractFile mManifestFile; private long mLastModified; + private int mMinSdk; private int mTargetSdk; private String mApplicationIcon; private String mApplicationLabel; @@ -130,6 +133,7 @@ public class ManifestInfo { * @param project the project the finder is associated with * @return a {@ManifestInfo} for the given project, never null */ + @NonNull public static ManifestInfo get(IProject project) { ManifestInfo finder = null; try { @@ -174,6 +178,7 @@ public class ManifestInfo { mActivityThemes = new HashMap<String, String>(); mManifestTheme = null; mTargetSdk = 1; // Default when not specified + mMinSdk = 1; // Default when not specified mPackage = ""; //$NON-NLS-1$ mApplicationIcon = null; mApplicationLabel = null; @@ -227,32 +232,8 @@ public class ManifestInfo { NodeList usesSdks = root.getElementsByTagName(NODE_USES_SDK); if (usesSdks.getLength() > 0) { Element usesSdk = (Element) usesSdks.item(0); - String targetSdk = null; - if (usesSdk.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_TARGET_SDK_VERSION)) { - targetSdk = usesSdk.getAttributeNS(NS_RESOURCES, - ATTRIBUTE_TARGET_SDK_VERSION); - } else if (usesSdk.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_MIN_SDK_VERSION)) { - targetSdk = usesSdk.getAttributeNS(NS_RESOURCES, - ATTRIBUTE_MIN_SDK_VERSION); - } - if (targetSdk != null) { - int apiLevel = -1; - try { - apiLevel = Integer.valueOf(targetSdk); - } catch (NumberFormatException e) { - // Handle codename - if (Sdk.getCurrent() != null) { - IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString( - "android-" + targetSdk); //$NON-NLS-1$ - if (target != null) { - // codename future API level is current api + 1 - apiLevel = target.getVersion().getApiLevel() + 1; - } - } - } - - mTargetSdk = apiLevel; - } + mMinSdk = getApiVersion(usesSdk, ATTRIBUTE_MIN_SDK_VERSION, 1); + mTargetSdk = getApiVersion(usesSdk, ATTRIBUTE_TARGET_SDK_VERSION, mMinSdk); } } else { mManifestTheme = defaultTheme; @@ -264,11 +245,40 @@ public class ManifestInfo { } } + private static int getApiVersion(Element usesSdk, String attribute, int defaultApiLevel) { + String valueString = null; + if (usesSdk.hasAttributeNS(NS_RESOURCES, attribute)) { + valueString = usesSdk.getAttributeNS(NS_RESOURCES, attribute); + } + + if (valueString != null) { + int apiLevel = -1; + try { + apiLevel = Integer.valueOf(valueString); + } catch (NumberFormatException e) { + // Handle codename + if (Sdk.getCurrent() != null) { + IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString( + "android-" + valueString); //$NON-NLS-1$ + if (target != null) { + // codename future API level is current api + 1 + apiLevel = target.getVersion().getApiLevel() + 1; + } + } + } + + return apiLevel; + } + + return defaultApiLevel; + } + /** * Returns the default package registered in the Android manifest * * @return the default package registered in the manifest */ + @NonNull public String getPackage() { sync(); return mPackage; @@ -280,6 +290,7 @@ public class ManifestInfo { * * @return a map from activity fqcn to theme style */ + @NonNull public Map<String, String> getActivityThemes() { sync(); return mActivityThemes; @@ -293,6 +304,7 @@ public class ManifestInfo { * @param screenSize the screen size to obtain a default theme for, or null if unknown * @return the theme to use for this project, never null */ + @NonNull public String getDefaultTheme(IAndroidTarget renderingTarget, ScreenSize screenSize) { sync(); @@ -320,6 +332,7 @@ public class ManifestInfo { * * @return the application icon, or null */ + @Nullable public String getApplicationIcon() { sync(); return mApplicationIcon; @@ -330,16 +343,38 @@ public class ManifestInfo { * * @return the application label, or null */ + @Nullable public String getApplicationLabel() { sync(); return mApplicationLabel; } /** + * Returns the target SDK version + * + * @return the target SDK version + */ + public int getTargetSdkVersion() { + sync(); + return mTargetSdk; + } + + /** + * Returns the minimum SDK version + * + * @return the minimum SDK version + */ + public int getMinSdkVersion() { + sync(); + return mMinSdk; + } + + /** * Returns the {@link IPackageFragment} for the package registered in the manifest * * @return the {@link IPackageFragment} for the package registered in the manifest */ + @Nullable public IPackageFragment getPackageFragment() { sync(); try { @@ -367,6 +402,7 @@ public class ManifestInfo { * @param pkg the package containing activities * @return the activity name */ + @Nullable public static String guessActivity(IProject project, String layoutName, String pkg) { final AtomicReference<String> activity = new AtomicReference<String>(); SearchRequestor requestor = new SearchRequestor() { @@ -446,6 +482,7 @@ public class ManifestInfo { * @return the activity name */ @SuppressWarnings("all") + @Nullable public String guessActivityBySetContentView(String layoutName) { if (false) { // These should be fields @@ -568,7 +605,13 @@ public class ManifestInfo { return scope; } - /** Returns the first package root for the given java project */ + /** + * Returns the first package root for the given java project + * + * @param javaProject the project to search in + * @return the first package root, or null + */ + @Nullable public static IPackageFragmentRoot getSourcePackageRoot(IJavaProject javaProject) { IPackageFragmentRoot packageRoot = null; List<IPath> sources = BaseProjectHelper.getSourceClasspaths(javaProject); @@ -589,8 +632,10 @@ public class ManifestInfo { /** * Computes the minimum SDK and target SDK versions for the project * + * @param project the project to look up the versions for * @return a pair of (minimum SDK, target SDK) versions, never null */ + @NonNull public static Pair<Integer, Integer> computeSdkVersions(IProject project) { int mMinSdkVersion = 1; int mTargetSdkVersion = 1; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java index a25d07b..dfe38b0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java @@ -1725,15 +1725,17 @@ public class UiElementNode implements IPropertySource { /** * Returns the namespace prefix matching the requested namespace URI. - * If no such declaration is found, returns the default "android" prefix. + * If no such declaration is found, returns the default "android" prefix for + * the Android URI, and "app" for other URI's. * * @param node The current node. Must not be null. * @param nsUri The namespace URI of which the prefix is to be found, * e.g. SdkConstants.NS_RESOURCES - * @return The first prefix declared or the default "android" prefix. + * @return The first prefix declared or the default "android" prefix + * (or "app" for non-Android URIs) */ public static String lookupNamespacePrefix(Node node, String nsUri) { - String defaultPrefix = NS_RESOURCES.equals(nsUri) ? ANDROID_NS_NAME : "ns"; //$NON-NLS-1$ + String defaultPrefix = NS_RESOURCES.equals(nsUri) ? ANDROID_NS_NAME : "app"; //$NON-NLS-1$ return lookupNamespacePrefix(node, nsUri, defaultPrefix); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java index fde228b..1137901 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java @@ -28,7 +28,6 @@ import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.tools.lint.checks.DuplicateIdDetector; import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.CoreException; @@ -195,21 +194,9 @@ class AddSuppressAttribute implements ICompletionProposal { return null; } - // Some issues cannot find a specific node scope associated with the error - // (for example because it involves cross-file analysis and at the end of - // the project scan when the warnings are computed the DOM model is no longer - // available). Until that's resolved, we need to filter these out such that - // we don't add misleading annotations on individual elements; the fallback - // path is the DOM document itself instead. - if (id.equals(DuplicateIdDetector.CROSS_LAYOUT.getId())) { - node = document.getDocumentElement(); - } - + node = document.getDocumentElement(); if (node == null) { - node = document.getDocumentElement(); - if (node == null) { - return null; - } + return null; } String desc = String.format("Add ignore '%1$s\' to element", id); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/CompatibilityLibraryHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/CompatibilityLibraryHelper.java new file mode 100644 index 0000000..8f6de3a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/CompatibilityLibraryHelper.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.adt.internal.project; + +import static com.android.ide.common.layout.LayoutConstants.FQCN_GRID_LAYOUT; +import static com.android.ide.common.layout.LayoutConstants.FQCN_GRID_LAYOUT_V7; +import static com.android.ide.common.layout.LayoutConstants.FQCN_SPACE; +import static com.android.ide.common.layout.LayoutConstants.FQCN_SPACE_V7; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.eclipse.adt.AdtUtils; +import com.android.ide.eclipse.adt.internal.actions.AddCompatibilityJarAction; +import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.widgets.Display; + +/** + * Helper class for the Android Support Library. The support library provides + * (for example) a backport of GridLayout, which must be used as a library + * project rather than a jar library since it has resources. This class provides + * support for finding the library project, or downloading and installing it on + * demand if it does not, as well as translating tags such as + * {@code <GridLayout>} into {@code <com.android.support.v7.GridLayout>} if it + * does not. + */ +public class CompatibilityLibraryHelper { + /** + * Returns the correct tag to use for the given view tag. This is normally + * the same as the tag itself. However, for some views which are not available + * on all platforms, this will: + * <ul> + * <li> Check if the view is available in the compatibility library, + * and if so, if the support library is not installed, will offer to + * install it via the SDK manager. + * <li> (The tool may also offer to adjust the minimum SDK of the project + * up to a level such that the given tag is supported directly, and then + * this method will return the original tag.) + * <li> Check whether the compatibility library is included in the project, and + * if not, offer to copy it into the workspace and add a library dependency. + * <li> Return the alternative tag. For example, for "GridLayout", it will + * (if the minimum SDK is less than 14) return "com.android.support.v7.GridLayout" + * instead. + * </ul> + * + * @param project the project to add the dependency into + * @param tag the tag to look up, such as "GridLayout" + * @return the tag to use in the layout, normally the same as the input tag but possibly + * an equivalent compatibility library tag instead. + */ + @NonNull + public static String getTagFor(@NonNull IProject project, @NonNull String tag) { + boolean isGridLayout = tag.equals(FQCN_GRID_LAYOUT); + boolean isSpace = tag.equals(FQCN_SPACE); + if (isGridLayout || isSpace) { + int minSdk = ManifestInfo.get(project).getMinSdkVersion(); + if (minSdk < 14) { + // See if the support library is installed in the SDK area + // See if there is a local project in the workspace providing the + // project + IProject supportProject = getSupportProjectV7(); + if (supportProject != null) { + // Make sure I have a dependency on it + ProjectState state = Sdk.getProjectState(project); + if (state != null) { + for (LibraryState library : state.getLibraries()) { + if (supportProject.equals(library.getProjectState().getProject())) { + // Found it: you have the compatibility library and have linked + // to it: use the alternative tag + return isGridLayout ? FQCN_GRID_LAYOUT_V7 : FQCN_SPACE_V7; + } + } + } + } + + // Ask user to install it + String message = String.format( + "%1$s requires API level 14 or higher, or a compatibility " + + "library for older versions.\n\n" + + " Do you want to install the compatibility library?", tag); + MessageDialog dialog = + new MessageDialog( + Display.getCurrent().getActiveShell(), + "Warning", + null, + message, + MessageDialog.QUESTION, + new String[] { + "Install", "Cancel" + }, + 1 /* default button: Cancel */); + int answer = dialog.open(); + if (answer == 0) { + if (supportProject != null) { + // Just add library dependency + if (!AddCompatibilityJarAction.addLibraryDependency( + supportProject, + project, + true /* waitForFinish */)) { + return tag; + } + } else { + // Install library AND add dependency + if (!AddCompatibilityJarAction.installLibrary( + project, + true /* waitForFinish */)) { + return tag; + } + } + + return isGridLayout ? FQCN_GRID_LAYOUT_V7 : FQCN_SPACE_V7; + } + } + } + + return tag; + } + + /** Cache for {@link #getSupportProjectV7()} */ + private static IProject sCachedProject; + + /** + * Finds and returns the support project in the workspace, if any. + * + * @return the android support library project, or null if not found + */ + @Nullable + public static IProject getSupportProjectV7() { + if (sCachedProject != null) { + if (sCachedProject.isAccessible()) { + return sCachedProject; + } else { + sCachedProject = null; + } + } + + sCachedProject = findSupportProjectV7(); + return sCachedProject; + } + + @Nullable + private static IProject findSupportProjectV7() { + for (IJavaProject javaProject : AdtUtils.getOpenAndroidProjects()) { + IProject project = javaProject.getProject(); + ProjectState state = Sdk.getProjectState(project); + if (state.isLibrary()) { + ManifestInfo manifestInfo = ManifestInfo.get(project); + if (manifestInfo.getPackage().equals("android.support.v7.gridlayout")) { //$NON-NLS-1$ + return project; + } + } + } + + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java index 93fe43d..86c9b22 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java @@ -20,7 +20,6 @@ import static com.android.sdklib.internal.project.ProjectProperties.PROPERTY_SDK import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; import com.android.ide.eclipse.adt.AndroidPrintStream; import com.android.ide.eclipse.adt.internal.build.BuildHelper; import com.android.ide.eclipse.adt.internal.build.DexException; @@ -62,6 +61,8 @@ import java.io.OutputStream; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; @@ -122,7 +123,8 @@ public final class ExportHelper { BuildHelper helper = new BuildHelper(project, fakeStream, fakeStream, - debugMode, false /*verbose*/); + debugMode, false /*verbose*/, + null /*resourceMarker*/); // get the list of library projects ProjectState projectState = Sdk.getProjectState(project); @@ -200,50 +202,51 @@ public final class ExportHelper { } } - String[] dxInput; + Collection<String> dxInput; if (runProguard) { - // the output of the main project (and any java-only project dependency) - String[] projectOutputs = helper.getProjectJavaOutputs(); + // get all the compiled code paths. This will contain both project output + // folder and jar files. + Collection<String> paths = helper.getCompiledCodePaths(); - // create a jar from the output of these projects + // create a jar file containing all the project output (as proguard cannot + // process folders of .class files). File inputJar = File.createTempFile(TEMP_PREFIX, AdtConstants.DOT_JAR); inputJar.deleteOnExit(); - JarOutputStream jos = new JarOutputStream(new FileOutputStream(inputJar)); - for (String po : projectOutputs) { - File root = new File(po); - if (root.exists()) { + + // a list of the other paths (jar files.) + List<String> jars = new ArrayList<String>(); + + for (String path : paths) { + File root = new File(path); + if (root.isDirectory()) { addFileToJar(jos, root, root); + } else if (root.isFile()) { + jars.add(path); } } jos.close(); - // get the other jar files - String[] jarFiles = helper.getCompiledCodePaths(false /*includeProjectOutputs*/, - null /*resourceMarker*/); - // destination file for proguard File obfuscatedJar = File.createTempFile(TEMP_PREFIX, AdtConstants.DOT_JAR); obfuscatedJar.deleteOnExit(); // run proguard - helper.runProguard(proguardConfigFiles, inputJar, jarFiles, obfuscatedJar, + helper.runProguard(proguardConfigFiles, inputJar, jars, obfuscatedJar, new File(project.getLocation().toFile(), SdkConstants.FD_PROGUARD)); + helper.setProguardOutput(obfuscatedJar.getAbsolutePath()); + // dx input is proguard's output - dxInput = new String[] { obfuscatedJar.getAbsolutePath() }; + dxInput = Collections.singletonList(obfuscatedJar.getAbsolutePath()); } else { // no proguard, simply get all the compiled code path: project output(s) + // jar file(s) - dxInput = helper.getCompiledCodePaths(true /*includeProjectOutputs*/, - null /*resourceMarker*/); + dxInput = helper.getCompiledCodePaths(); } IJavaProject javaProject = JavaCore.create(project); - List<IProject> javaProjects = ProjectHelper.getReferencedProjects(project); - List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects( - javaProjects); helper.executeDx(javaProject, dxInput, dexFile.getAbsolutePath()); @@ -253,9 +256,7 @@ public final class ExportHelper { resourceFile.getAbsolutePath(), dexFile.getAbsolutePath(), outputFile.getAbsolutePath(), - javaProject, libProjects, - referencedJavaProjects, key, certificate, null); //resourceMarker @@ -370,17 +371,12 @@ public final class ExportHelper { private static void addFileToJar(JarOutputStream jar, File file, File rootDirectory) throws IOException { if (file.isDirectory()) { - for (File child: file.listFiles()) { - addFileToJar(jar, child, rootDirectory); + if (file.getName().equals("META-INF") == false) { + for (File child: file.listFiles()) { + addFileToJar(jar, child, rootDirectory); + } } - } else if (file.isFile()) { - // check the extension - String name = file.getName(); - if (!AdtUtils.endsWith(name, AdtConstants.DOT_CLASS)) { - return; - } - String rootPath = rootDirectory.getAbsolutePath(); String path = file.getAbsolutePath(); path = path.substring(rootPath.length()).replace("\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java index 65fd9c7..c2f2510 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java @@ -214,17 +214,16 @@ public class LibraryClasspathContainerInitializer extends BaseClasspathContainer entries.add(entry); - // now, gather the content of this library project's libs folder. + // process all of the library project's dependencies + getDependencyListFromClasspath(libProject, refProjects, jarFiles, true); + // and the content of its libs folder. getJarListFromLibsFolder(libProject, jarFiles); } - - // get project dependencies - processReferencedProjects(libProject, refProjects, jarFiles); } - // now process this projects' referenced projects + // now process this projects' referenced projects only. processReferencedProjects(iProject, refProjects, jarFiles); - // and its own jar files from libs + // and the content of its libs folder getJarListFromLibsFolder(iProject, jarFiles); // annotations support for older version of android @@ -288,30 +287,6 @@ public class LibraryClasspathContainerInitializer extends BaseClasspathContainer IClasspathContainer.K_APPLICATION); } - - private static void processReferencedProjects(IProject project, - Set<IProject> projects, Set<File> jarFiles) { - try { - IProject[] refs = project.getReferencedProjects(); - for (IProject p : refs) { - // ignore if it's an Android project, or if it's not a Java Project - if (p.hasNature(JavaCore.NATURE_ID) && - p.hasNature(AdtConstants.NATURE_DEFAULT) == false) { - // add this project to the list - projects.add(p); - - // get the jar dependencies of the project in the list - getJarListFromClasspath(p, jarFiles); - - // and then process the referenced project by this project too. - processReferencedProjects(p, projects, jarFiles); - } - } - } catch (CoreException e) { - // can't get the referenced projects? ignore - } - } - /** * Finds all the jar files inside a project's libs folder. * @param project @@ -335,13 +310,45 @@ public class LibraryClasspathContainerInitializer extends BaseClasspathContainer } /** - * Finds all the jars a given project depends on by looking at the classpath. - * This must be a non android project, as android project have container that really - * we shouldn't go through. + * Process reference projects from the main projects to add indirect dependencies coming + * from Java project. + * @param project the main project + * @param projects the project list to add to + * @param jarFiles the jar list to add to. + */ + private static void processReferencedProjects(IProject project, + Set<IProject> projects, Set<File> jarFiles) { + try { + IProject[] refs = project.getReferencedProjects(); + for (IProject p : refs) { + // ignore if it's an Android project, or if it's not a Java + // Project + if (p.hasNature(JavaCore.NATURE_ID) + && p.hasNature(AdtConstants.NATURE_DEFAULT) == false) { + + // process this project's dependencies + getDependencyListFromClasspath(p, projects, jarFiles, true /*includeJarFiles*/); + } + } + } catch (CoreException e) { + // can't get the referenced projects? ignore + } + } + + /** + * Finds all the dependencies of a given project and add them to a project list and + * a jar list. + * Only classpath entries that are exported are added, and only Java project (not Android + * project) are added. + * * @param project the project to query - * @param jarFiles the list of file to fill. + * @param projects the referenced project list to add to + * @param jarFiles the jar list to add to + * @param includeJarFiles whether to include jar files or just projects. This is useful when + * calling on an Android project (value should be <code>false</code>) */ - private static void getJarListFromClasspath(IProject project, Set<File> jarFiles) { + private static void getDependencyListFromClasspath(IProject project, Set<IProject> projects, + Set<File> jarFiles, boolean includeJarFiles) { IJavaProject javaProject = JavaCore.create(project); IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); @@ -350,38 +357,70 @@ public class LibraryClasspathContainerInitializer extends BaseClasspathContainer IClasspathEntry[] classpaths = javaProject.readRawClasspath(); if (classpaths != null) { for (IClasspathEntry e : classpaths) { - // only consider the classpath entries that are exported. - if (e.isExported() == false) { - continue; - } - // if this is a classpath variable reference, we resolve it. - if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { - e = JavaCore.getResolvedClasspathEntry(e); + // ignore entries that are not exported + if (e.isExported()) { + processCPE(e, javaProject, wsRoot, projects, jarFiles, includeJarFiles); } + } + } + } - if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { - handleClasspathEntry(e, wsRoot, jarFiles); - } else if (e.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { - // get the container. - try { - IClasspathContainer container = JavaCore.getClasspathContainer( - e.getPath(), javaProject); - // ignore the system and default_system types as they represent - // libraries that are part of the runtime. - if (container != null && - container.getKind() == IClasspathContainer.K_APPLICATION) { - IClasspathEntry[] entries = container.getClasspathEntries(); - for (IClasspathEntry entry : entries) { - if (entry.isExported()) { - handleClasspathEntry(entry, wsRoot, jarFiles); - } - } - } - } catch (JavaModelException jme) { - // can't resolve the container? ignore it. - AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s", e.getPath()); + /** + * Processes a {@link IClasspathEntry} and add it to one of the list if applicable. + * @param entry the entry to process + * @param javaProject the {@link IJavaProject} from which this entry came. + * @param wsRoot the {@link IWorkspaceRoot} + * @param projects the project list to add to + * @param jarFiles the jar list to add to + * @param includeJarFiles whether to include jar files or just projects. This is useful when + * calling on an Android project (value should be <code>false</code>) + */ + private static void processCPE(IClasspathEntry entry, IJavaProject javaProject, + IWorkspaceRoot wsRoot, + Set<IProject> projects, Set<File> jarFiles, boolean includeJarFiles) { + + // if this is a classpath variable reference, we resolve it. + if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { + entry = JavaCore.getResolvedClasspathEntry(entry); + } + + if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) { + IProject refProject = wsRoot.getProject(entry.getPath().lastSegment()); + try { + // ignore if it's an Android project, or if it's not a Java Project + if (refProject.hasNature(JavaCore.NATURE_ID) && + refProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) { + // add this project to the list + projects.add(refProject); + + // also get the dependency from this project. + getDependencyListFromClasspath(refProject, projects, jarFiles, + true /*includeJarFiles*/); + } + } catch (CoreException exception) { + // can't query the project nature? ignore + } + } else if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { + if (includeJarFiles) { + handleClasspathLibrary(entry, wsRoot, jarFiles); + } + } else if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { + // get the container and its content + try { + IClasspathContainer container = JavaCore.getClasspathContainer( + entry.getPath(), javaProject); + // ignore the system and default_system types as they represent + // libraries that are part of the runtime. + if (container != null && + container.getKind() == IClasspathContainer.K_APPLICATION) { + IClasspathEntry[] entries = container.getClasspathEntries(); + for (IClasspathEntry cpe : entries) { + processCPE(cpe, javaProject, wsRoot, projects, jarFiles, includeJarFiles); } } + } catch (JavaModelException jme) { + // can't resolve the container? ignore it. + AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s", entry.getPath()); } } } @@ -406,7 +445,7 @@ public class LibraryClasspathContainerInitializer extends BaseClasspathContainer } } - private static void handleClasspathEntry(IClasspathEntry e, IWorkspaceRoot wsRoot, + private static void handleClasspathLibrary(IClasspathEntry e, IWorkspaceRoot wsRoot, Set<File> jarFiles) { // get the IPath IPath path = e.getPath(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java index eb0ddf1..e118ff7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java @@ -17,6 +17,7 @@ package com.android.ide.eclipse.adt.internal.resources.manager; import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.build.BuildHelper; import com.android.ide.eclipse.adt.internal.sdk.ProjectState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; @@ -27,9 +28,11 @@ import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; +import org.eclipse.jdt.core.IClasspathContainer; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; import java.io.File; import java.io.FileInputStream; @@ -253,8 +256,6 @@ public final class ProjectClassLoader extends ClassLoader { // get a java project from it IJavaProject javaProject = JavaCore.create(mJavaProject.getProject()); - IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); - ArrayList<URL> oslibraryList = new ArrayList<URL>(); IClasspathEntry[] classpaths = javaProject.readRawClasspath(); if (classpaths != null) { @@ -266,38 +267,30 @@ public final class ProjectClassLoader extends ClassLoader { e = JavaCore.getResolvedClasspathEntry(e); } - // get the IPath - IPath path = e.getPath(); - - // check the name ends with .jar - if (AdtConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { - boolean local = false; - IResource resource = wsRoot.findMember(path); - if (resource != null && resource.exists() && - resource.getType() == IResource.FILE) { - local = true; - try { - oslibraryList.add(new File(resource.getLocation().toOSString()) - .toURI().toURL()); - } catch (MalformedURLException mue) { - // pass - } - } - - if (local == false) { - // if the jar path doesn't match a workspace resource, - // then we get an OSString and check if this links to a valid file. - String osFullPath = path.toOSString(); - - File f = new File(osFullPath); - if (f.exists()) { - try { - oslibraryList.add(f.toURI().toURL()); - } catch (MalformedURLException mue) { - // pass + handleClassPathEntry(e, oslibraryList); + } else if (e.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { + // get the container. + try { + IClasspathContainer container = JavaCore.getClasspathContainer( + e.getPath(), javaProject); + // ignore the system and default_system types as they represent + // libraries that are part of the runtime. + if (container != null && + container.getKind() == IClasspathContainer.K_APPLICATION) { + IClasspathEntry[] entries = container.getClasspathEntries(); + for (IClasspathEntry entry : entries) { + // TODO: Xav -- is this necessary? + if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { + entry = JavaCore.getResolvedClasspathEntry(entry); } + + handleClassPathEntry(entry, oslibraryList); } } + } catch (JavaModelException jme) { + // can't resolve the container? ignore it. + AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s", + e.getPath()); } } } @@ -305,4 +298,40 @@ public final class ProjectClassLoader extends ClassLoader { return oslibraryList.toArray(new URL[oslibraryList.size()]); } + + private void handleClassPathEntry(IClasspathEntry e, ArrayList<URL> oslibraryList) { + // get the IPath + IPath path = e.getPath(); + + // check the name ends with .jar + if (AdtConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { + boolean local = false; + IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path); + if (resource != null && resource.exists() && + resource.getType() == IResource.FILE) { + local = true; + try { + oslibraryList.add(new File(resource.getLocation().toOSString()) + .toURI().toURL()); + } catch (MalformedURLException mue) { + // pass + } + } + + if (local == false) { + // if the jar path doesn't match a workspace resource, + // then we get an OSString and check if this links to a valid file. + String osFullPath = path.toOSString(); + + File f = new File(osFullPath); + if (f.exists()) { + try { + oslibraryList.add(f.toURI().toURL()); + } catch (MalformedURLException mue) { + // pass + } + } + } + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java index a661334..2a1fba9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java @@ -352,7 +352,7 @@ public final class Sdk { /** * Initializes a new project with a target. This creates the <code>project.properties</code> * file. - * @param project the project to intialize + * @param project the project to initialize * @param target the project's target. * @throws IOException if creating the file failed in any way. * @throws StreamException diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java index ff77b9f..d4c342c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java @@ -40,6 +40,7 @@ import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.IWizardPage; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.osgi.util.TextProcessor; import org.eclipse.swt.SWT; @@ -625,11 +626,11 @@ class ProjectNamePage extends WizardPage implements SelectionListener, ModifyLis } @Override - public boolean canFlipToNextPage() { + public IWizardPage getNextPage() { // Sync working set data to the value object, since the WorkingSetGroup // doesn't let us add listeners to do this lazily mValues.workingSets = getWorkingSets(); - return super.canFlipToNextPage(); + return super.getNextPage(); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java index 4e17125..815848e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java @@ -18,6 +18,9 @@ package com.android.ide.eclipse.adt.internal.wizards.newxmlfile; +import static com.android.ide.common.layout.LayoutConstants.FQCN_GRID_LAYOUT; +import static com.android.ide.common.layout.LayoutConstants.GRID_LAYOUT; + import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; @@ -26,7 +29,9 @@ import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences; import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle; import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter; +import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; +import com.android.ide.eclipse.adt.internal.project.CompatibilityLibraryHelper; import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileCreationPage.TypeInfo; import com.android.resources.ResourceFolderType; import com.android.util.Pair; @@ -201,6 +206,17 @@ public class NewXmlFileWizard extends Wizard implements INewWizard { StringBuilder sb = new StringBuilder(XML_HEADER_LINE); + if (folderType == ResourceFolderType.LAYOUT && root.equals(GRID_LAYOUT)) { + IProject project = file.getParent().getProject(); + int minSdk = ManifestInfo.get(project).getMinSdkVersion(); + if (minSdk < 14) { + root = CompatibilityLibraryHelper.getTagFor(project, FQCN_GRID_LAYOUT); + if (root.equals(FQCN_GRID_LAYOUT)) { + root = GRID_LAYOUT; + } + } + } + sb.append('<').append(root); if (xmlns != null) { sb.append('\n').append(" xmlns:android=\"").append(xmlns).append('"'); //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoringTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoringTest.java index 04b9b1a..c1a5ca4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoringTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoringTest.java @@ -57,6 +57,11 @@ public class UseCompoundDrawableRefactoringTest extends RefactoringTest { checkRefactoring("refactoring/usecompound/compound_all.xml", "@+id/layout3"); } + public void test7() throws Exception { + // Test converting where a namespace needs to be migrated + checkRefactoring("refactoring/usecompound/compound5.xml", "@+id/layout"); + } + private void checkRefactoring(String basename, String id) throws Exception { IFile file = getLayoutFile(getProject(), basename); TestContext info = setupTestContext(file, basename); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/refactoring/usecompound/compound5-expected-7.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/refactoring/usecompound/compound5-expected-7.xml new file mode 100644 index 0000000..e909811 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/refactoring/usecompound/compound5-expected-7.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/TextView1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:drawableBottom="@drawable/ic_launcher" + android:text="Hello World" > + +</TextView>
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/refactoring/usecompound/compound5.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/refactoring/usecompound/compound5.info new file mode 100644 index 0000000..8eb5c4b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/refactoring/usecompound/compound5.info @@ -0,0 +1,3 @@ +android.widget.LinearLayout [0,74,480,800] <LinearLayout> + android.widget.TextView [0,0,107,26] <TextView> + android.widget.ImageView [0,26,72,98] <ImageView> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/refactoring/usecompound/compound5.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/refactoring/usecompound/compound5.xml new file mode 100644 index 0000000..49c0594 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/refactoring/usecompound/compound5.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:id="@+id/layout" + android:orientation="vertical" > + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Hello World" /> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_launcher" /> + +</LinearLayout> |