diff options
183 files changed, 3024 insertions, 2064 deletions
diff --git a/android/gen/icons/AndroidIcons.java b/android/gen/icons/AndroidIcons.java index 031c812c842..67e6d4ff845 100755 --- a/android/gen/icons/AndroidIcons.java +++ b/android/gen/icons/AndroidIcons.java @@ -152,6 +152,20 @@ public class AndroidIcons { public static final Icon NewProjectMascotGreen = load("/icons/wizards/welcome_green.png"); // 60x60 } + public static class NeleIcons { + // All 16x16 and 32x32 in Retina mode + public static final Icon Api = load("/icons/nele/api.png"); + public static final Icon Language = load("/icons/nele/language.png"); + public static final Icon Preview = load("/icons/nele/preview.png"); + public static final Icon Rotate = load("/icons/nele/rotate.png"); + public static final Icon Size = load("/icons/nele/size.png"); + public static final Icon Phone = load("/icons/nele/phone.png"); + public static final Icon Tablet = load("/icons/nele/tablet.png"); + public static final Icon Wear = load("/icons/nele/wear.png"); + public static final Icon Tv = load("/icons/nele/tv.png"); + public static final Icon Theme = load("/icons/nele/theme.png"); + } + public static class Views { public static final Icon AbsoluteLayout = load("/icons/views/AbsoluteLayout.png"); // 16x16 public static final Icon AdapterViewFlipper = load("/icons/views/AdapterViewFlipper.png"); // 16x16 diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/editing/RefactoringFlowTest.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/editing/RefactoringFlowTest.java index 5fcb8cc48e5..ba0b695ef95 100644 --- a/android/guiTestSrc/com/android/tools/idea/tests/gui/editing/RefactoringFlowTest.java +++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/editing/RefactoringFlowTest.java @@ -45,6 +45,7 @@ public class RefactoringFlowTest extends GuiTestCase { ConflictsDialogFixture conflictsDialog = ConflictsDialogFixture.find(myRobot); conflictsDialog.requireMessageTextContains("Resource @string/action_settings already exists"); conflictsDialog.clickCancel(); + refactoringDialog.clickCancel(); } @Test @IdeGuiTest() diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/editors/translations/TranslationsEditorTest.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/editors/translations/TranslationsEditorTest.java index 8c91c6ac1d3..a59f94713cf 100644 --- a/android/guiTestSrc/com/android/tools/idea/tests/gui/editors/translations/TranslationsEditorTest.java +++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/editors/translations/TranslationsEditorTest.java @@ -32,9 +32,11 @@ import org.fest.swing.timing.Condition; import org.jetbrains.annotations.NotNull; import org.junit.Test; +import javax.swing.*; import java.io.IOException; import java.util.List; +import static com.android.tools.idea.tests.gui.framework.GuiTests.waitUntilFound; import static com.android.tools.idea.tests.gui.framework.fixture.EditorFixture.Tab.EDITOR; import static org.fest.assertions.Assertions.assertThat; import static org.fest.swing.edt.GuiActionRunner.execute; @@ -56,18 +58,11 @@ public class TranslationsEditorTest extends GuiTestCase { ideFrame.requireEditorNotification("Edit translations for all locales in the translations editor."); notificationPanel.performAction("Open editor"); - // Wait for the translations editor table to show up, and the loading panel to complete loading - pause(new Condition("Waiting for string resources to load") { + // Wait for the translations editor table to show up, and the table to be initialized + waitUntilFound(myRobot, new GenericTypeMatcher<JTable>(JTable.class) { @Override - public boolean test() { - ComponentFinder finder = myRobot.finder(); - JBLoadingPanel loadingPanel = finder.find(new GenericTypeMatcher<JBLoadingPanel>(JBLoadingPanel.class) { - @Override - protected boolean isMatching(@NotNull JBLoadingPanel component) { - return true; - } - }); - return !loadingPanel.isLoading(); + protected boolean isMatching(@NotNull JTable table) { + return table.getModel() != null && table.getModel().getColumnCount() > 0; } }); @@ -90,7 +85,7 @@ public class TranslationsEditorTest extends GuiTestCase { // See FontUtil.getFontAbleToDisplay() final FontFixture font = cancel.font(); //noinspection ConstantConditions - assertTrue("Font " + font.description() + " cannot display Chinese characters.", execute(new GuiQuery<Boolean>() { + assertTrue("Font " + font.target().getName() + " cannot display Chinese characters.", execute(new GuiQuery<Boolean>() { @Override protected Boolean executeInEDT() throws Throwable { return font.target().canDisplay('消'); diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/GuiTestRunner.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/GuiTestRunner.java index 8a3980c46d9..b8aa9d6f923 100644 --- a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/GuiTestRunner.java +++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/GuiTestRunner.java @@ -15,16 +15,18 @@ */ package com.android.tools.idea.tests.gui.framework; -import com.intellij.openapi.diagnostic.Logger; import org.fest.swing.image.ScreenshotTaker; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.After; import org.junit.Before; +import org.junit.internal.AssumptionViolatedException; import org.junit.internal.runners.model.ReflectiveCallable; import org.junit.internal.runners.statements.Fail; import org.junit.internal.runners.statements.RunAfters; import org.junit.internal.runners.statements.RunBefores; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; @@ -61,14 +63,21 @@ public class GuiTestRunner extends BlockJUnit4ClassRunner { } @Override - protected Statement methodBlock(FrameworkMethod method) { + protected void runChild(final FrameworkMethod method, RunNotifier notifier) { if (!canRunGuiTests()) { - Class<?> testClass = getTestClass().getJavaClass(); - Logger logger = Logger.getInstance(testClass); - logger.info("Skipping GUI test " + testClass.getCanonicalName() + " due to headless environment"); - return super.methodBlock(method); + notifier.fireTestAssumptionFailed(new Failure(describeChild(method), new AssumptionViolatedException("Headless environment"))); + System.out.println(String.format("Skipping test '%1$s'. UI tests cannot run in a headless environment.", method.getName())); + } else if (MethodInvoker.doesIdeHaveFatalErrors()) { + notifier.fireTestIgnored(describeChild(method)); // TODO: can we restart the IDE at this point, instead of giving up? + System.out.println(String.format("Skipping test '%1$s': a fatal error has occurred in the IDE", method.getName())); + notifier.pleaseStop(); + } else { + super.runChild(method, notifier); } + } + @Override + protected Statement methodBlock(FrameworkMethod method) { FrameworkMethod newMethod; try { loadClassesWithIdeClassLoader(); @@ -135,22 +144,12 @@ public class GuiTestRunner extends BlockJUnit4ClassRunner { @Override protected Statement methodInvoker(final FrameworkMethod method, Object test) { - if (canRunGuiTests()) { - try { - assertNotNull(myScreenshotTaker); - return new MethodInvoker(method, test, myScreenshotTaker); - } - catch (Throwable e) { - return new Fail(e); - } + try { + assertNotNull(myScreenshotTaker); + return new MethodInvoker(method, test, myScreenshotTaker); + } + catch (Throwable e) { + return new Fail(e); } - // Skip the test. - return new Statement() { - @Override - public void evaluate() throws Throwable { - String msg = String.format("Skipping test '%1$s'. UI tests cannot run in a headless environment.", method.getName()); - System.out.println(msg); - } - }; } } diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/MethodInvoker.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/MethodInvoker.java index edc6937a153..0ecc8e0b0b3 100644 --- a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/MethodInvoker.java +++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/MethodInvoker.java @@ -79,11 +79,16 @@ public class MethodInvoker extends Statement { failIfIdeHasFatalErrors(); } - private static boolean doesIdeHaveFatalErrors() throws ClassNotFoundException { + public static boolean doesIdeHaveFatalErrors() { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - Class<?> guiTestsType = Class.forName(GuiTests.class.getCanonicalName(), true, classLoader); - //noinspection ConstantConditions - return method("doesIdeHaveFatalErrors").withReturnType(boolean.class).in(guiTestsType).invoke(); + try { + Class<?> guiTestsType = Class.forName(GuiTests.class.getCanonicalName(), true, classLoader); + //noinspection ConstantConditions + return method("doesIdeHaveFatalErrors").withReturnType(boolean.class).in(guiTestsType).invoke(); + } catch (ClassNotFoundException ex) { + // ignore exception + return true; + } } private static void failIfIdeHasFatalErrors() throws ClassNotFoundException { diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/ChooseResourceDialogFixture.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/ChooseResourceDialogFixture.java new file mode 100644 index 00000000000..baf12c7e2b3 --- /dev/null +++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/ChooseResourceDialogFixture.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tools.idea.tests.gui.framework.fixture; + +import com.android.tools.idea.tests.gui.framework.GuiTests; +import com.intellij.icons.AllIcons; +import org.fest.swing.core.GenericTypeMatcher; +import org.fest.swing.core.Robot; +import org.fest.swing.fixture.JTextComponentFixture; +import org.jetbrains.android.uipreview.ChooseResourceDialog; +import org.jetbrains.annotations.NotNull; + +import javax.swing.JLabel; +import javax.swing.text.JTextComponent; + + +public class ChooseResourceDialogFixture extends IdeaDialogFixture<ChooseResourceDialog> { + + @NotNull + public static ChooseResourceDialogFixture find(@NotNull Robot robot) { + return new ChooseResourceDialogFixture(robot, find(robot, ChooseResourceDialog.class)); + } + + private ChooseResourceDialogFixture(@NotNull Robot robot, @NotNull DialogAndWrapper<ChooseResourceDialog> dialogAndWrapper) { + super(robot, dialogAndWrapper); + } + + @NotNull + public JTextComponentFixture getNameTextField() { + return new JTextComponentFixture(robot(), (JTextComponent)robot().finder().findByLabel("Name")); + } + + @NotNull + public String getError() { + final JLabel error = + GuiTests.waitUntilFound(robot(), new GenericTypeMatcher<JLabel>(JLabel.class) { + @Override + protected boolean isMatching(@NotNull JLabel component) { + return component.isShowing() && !"".equals(component.getText()) && component.getIcon() == AllIcons.Actions.Lightning; + } + }); + return error.getText(); + } +} diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/EditorFixture.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/EditorFixture.java index 3086dedbe41..75b8d097d81 100644 --- a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/EditorFixture.java +++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/EditorFixture.java @@ -18,8 +18,7 @@ package com.android.tools.idea.tests.gui.framework.fixture; import com.android.resources.ResourceFolderType; import com.android.tools.idea.editors.strings.StringResourceEditor; import com.android.tools.idea.editors.strings.StringsVirtualFile; -import com.android.tools.idea.editors.theme.ThemeEditor; -import com.android.tools.idea.editors.theme.ThemeEditorVirtualFile; +import com.android.tools.idea.editors.theme.ThemeEditorComponent; import com.android.tools.idea.rendering.ResourceHelper; import com.android.tools.idea.tests.gui.framework.GuiTests; import com.android.tools.idea.tests.gui.framework.fixture.layout.LayoutEditorFixture; @@ -55,8 +54,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.FocusManager; -import javax.swing.*; -import java.awt.*; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.KeyStroke; +import java.awt.Component; import java.awt.event.KeyEvent; import java.util.List; @@ -971,30 +972,17 @@ public class EditorFixture { * Returns a fixture around the {@link com.android.tools.idea.editors.theme.ThemeEditor} <b>if</b> the currently * displayed editor is a theme editor. */ - @Nullable + @NotNull public ThemeEditorFixture getThemeEditor() { - VirtualFile currentFile = getCurrentFile(); - if (!(currentFile instanceof ThemeEditorVirtualFile)) { - return null; - } - - return execute(new GuiQuery<ThemeEditorFixture>() { - @Override - @Nullable - protected ThemeEditorFixture executeInEDT() throws Throwable { - FileEditorManager manager = FileEditorManager.getInstance(myFrame.getProject()); - FileEditor[] editors = manager.getSelectedEditors(); - if (editors.length == 0) { - return null; - } - FileEditor selected = editors[0]; - if (!(selected instanceof ThemeEditor)) { - return null; + final ThemeEditorComponent themeEditorComponent = + GuiTests.waitUntilFound(robot, new GenericTypeMatcher<ThemeEditorComponent>(ThemeEditorComponent.class) { + @Override + protected boolean isMatching(@NotNull ThemeEditorComponent component) { + return true; } + }); - return new ThemeEditorFixture(robot, (ThemeEditor)selected); - } - }); + return new ThemeEditorFixture(robot, themeEditorComponent); } /** diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/theme/ThemeEditorFixture.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/theme/ThemeEditorFixture.java index 0105a942be9..14078fecebb 100644 --- a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/theme/ThemeEditorFixture.java +++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/theme/ThemeEditorFixture.java @@ -46,8 +46,8 @@ import static org.junit.Assert.assertNotNull; public class ThemeEditorFixture extends ComponentFixture<ThemeEditorFixture, ThemeEditorComponent> { private final JComboBoxFixture myThemesComboBox; - public ThemeEditorFixture(@NotNull Robot robot, @NotNull ThemeEditor themeEditor) { - super(ThemeEditorFixture.class, robot, (ThemeEditorComponent)themeEditor.getComponent()); + public ThemeEditorFixture(@NotNull Robot robot, @NotNull ThemeEditorComponent themeEditorComponent) { + super(ThemeEditorFixture.class, robot, themeEditorComponent); myThemesComboBox = new JComboBoxFixture(robot(), robot().finder().findByName(this.target().getSecondComponent(), AttributesPanel.THEME_SELECTOR_NAME, JComboBox.class)); } diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTableTest.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTableTest.java index b67f3531f72..edfd62bfd19 100644 --- a/android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTableTest.java +++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTableTest.java @@ -19,6 +19,7 @@ import com.android.tools.idea.tests.gui.framework.BelongsToTestGroups; import com.android.tools.idea.tests.gui.framework.GuiTestCase; import com.android.tools.idea.tests.gui.framework.GuiTests; import com.android.tools.idea.tests.gui.framework.IdeGuiTest; +import com.android.tools.idea.tests.gui.framework.fixture.ChooseResourceDialogFixture; import com.android.tools.idea.tests.gui.framework.fixture.EditorFixture; import com.android.tools.idea.tests.gui.framework.fixture.IdeFrameFixture; import com.android.tools.idea.tests.gui.framework.fixture.theme.ThemeEditorFixture; @@ -29,6 +30,7 @@ import org.fest.swing.fixture.JMenuItemFixture; import org.fest.swing.fixture.JPopupMenuFixture; import org.fest.swing.fixture.JTableCellFixture; import org.fest.swing.fixture.JTableFixture; +import org.fest.swing.fixture.JTextComponentFixture; import org.fest.swing.timing.Condition; import org.jetbrains.annotations.NotNull; import org.junit.BeforeClass; @@ -170,4 +172,31 @@ public class ThemeEditorTableTest extends GuiTestCase { } }, GuiTests.SHORT_TIMEOUT); } + + @Test @IdeGuiTest + public void testResoucePickerNameError() throws IOException { + IdeFrameFixture projectFrame = importSimpleApplication(); + ThemeEditorFixture themeEditor = ThemeEditorTestUtils.openThemeEditor(projectFrame); + + JTableFixture themeEditorTable = themeEditor.getPropertiesTable(); + assertNotNull(themeEditorTable); + + // Cell (1,0) should be some color + JTableCellFixture colorCell = themeEditorTable.cell(row(1).column(0)); + + // click on a color + colorCell.click(); + + ChooseResourceDialogFixture dialog = ChooseResourceDialogFixture.find(myRobot); + JTextComponentFixture name = dialog.getNameTextField(); + + // add mistake into name field + String badText = "("; + name.enterText(badText); + String text = name.text(); + assertNotNull(text); + assertTrue(text.endsWith(badText)); + + assertEquals("<html><font color='#ff0000'><left>'"+badText+"' is not a valid resource name character</left></b></font></html>", dialog.getError()); + } } diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTestUtils.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTestUtils.java index 3166c7dc752..9f07d51decf 100644 --- a/android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTestUtils.java +++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTestUtils.java @@ -21,8 +21,6 @@ import com.android.tools.idea.tests.gui.framework.fixture.IdeFrameFixture; import com.android.tools.idea.tests.gui.framework.fixture.theme.ThemeEditorFixture; import org.jetbrains.annotations.NotNull; -import static org.junit.Assert.assertNotNull; - /** * Utility class for static methods used in UI tests for the Theme Editor */ @@ -40,11 +38,7 @@ public class ThemeEditorTestUtils { projectFrame.requireEditorNotification("Edit all themes in the project in the theme editor."); notificationPanel.performAction("Open editor"); - // Makes sure the Theme Editor is opened before pursuing - projectFrame.robot().waitForIdle(); - ThemeEditorFixture themeEditor = editor.getThemeEditor(); - assertNotNull(themeEditor); themeEditor.getThemePreviewPanel().getPreviewPanel().waitForRender(); diff --git a/android/resources/icons/nele/api.png b/android/resources/icons/nele/api.png Binary files differnew file mode 100644 index 00000000000..509221d162f --- /dev/null +++ b/android/resources/icons/nele/api.png diff --git a/android/resources/icons/nele/api@2x.png b/android/resources/icons/nele/api@2x.png Binary files differnew file mode 100644 index 00000000000..3c97d54a4a3 --- /dev/null +++ b/android/resources/icons/nele/api@2x.png diff --git a/android/resources/icons/nele/api@2x_dark.png b/android/resources/icons/nele/api@2x_dark.png Binary files differnew file mode 100644 index 00000000000..35a2ada7676 --- /dev/null +++ b/android/resources/icons/nele/api@2x_dark.png diff --git a/android/resources/icons/nele/api_dark.png b/android/resources/icons/nele/api_dark.png Binary files differnew file mode 100644 index 00000000000..87f16931561 --- /dev/null +++ b/android/resources/icons/nele/api_dark.png diff --git a/android/resources/icons/nele/language.png b/android/resources/icons/nele/language.png Binary files differnew file mode 100644 index 00000000000..d4546020525 --- /dev/null +++ b/android/resources/icons/nele/language.png diff --git a/android/resources/icons/nele/language@2x.png b/android/resources/icons/nele/language@2x.png Binary files differnew file mode 100644 index 00000000000..f8aa012a498 --- /dev/null +++ b/android/resources/icons/nele/language@2x.png diff --git a/android/resources/icons/nele/language@2x_dark.png b/android/resources/icons/nele/language@2x_dark.png Binary files differnew file mode 100644 index 00000000000..ec870e0a699 --- /dev/null +++ b/android/resources/icons/nele/language@2x_dark.png diff --git a/android/resources/icons/nele/language_dark.png b/android/resources/icons/nele/language_dark.png Binary files differnew file mode 100644 index 00000000000..4bedb12faf8 --- /dev/null +++ b/android/resources/icons/nele/language_dark.png diff --git a/android/resources/icons/nele/phone.png b/android/resources/icons/nele/phone.png Binary files differnew file mode 100644 index 00000000000..c3aeaec51a2 --- /dev/null +++ b/android/resources/icons/nele/phone.png diff --git a/android/resources/icons/nele/phone@2x.png b/android/resources/icons/nele/phone@2x.png Binary files differnew file mode 100644 index 00000000000..bcff2f60b0e --- /dev/null +++ b/android/resources/icons/nele/phone@2x.png diff --git a/android/resources/icons/nele/phone@2x_dark.png b/android/resources/icons/nele/phone@2x_dark.png Binary files differnew file mode 100644 index 00000000000..25496665440 --- /dev/null +++ b/android/resources/icons/nele/phone@2x_dark.png diff --git a/android/resources/icons/nele/phone_dark.png b/android/resources/icons/nele/phone_dark.png Binary files differnew file mode 100644 index 00000000000..cfa93521652 --- /dev/null +++ b/android/resources/icons/nele/phone_dark.png diff --git a/android/resources/icons/nele/preview.png b/android/resources/icons/nele/preview.png Binary files differnew file mode 100644 index 00000000000..68fa9954197 --- /dev/null +++ b/android/resources/icons/nele/preview.png diff --git a/android/resources/icons/nele/preview@2x.png b/android/resources/icons/nele/preview@2x.png Binary files differnew file mode 100644 index 00000000000..8ce9031d982 --- /dev/null +++ b/android/resources/icons/nele/preview@2x.png diff --git a/android/resources/icons/nele/preview@2x_dark.png b/android/resources/icons/nele/preview@2x_dark.png Binary files differnew file mode 100644 index 00000000000..365441e073d --- /dev/null +++ b/android/resources/icons/nele/preview@2x_dark.png diff --git a/android/resources/icons/nele/preview_dark.png b/android/resources/icons/nele/preview_dark.png Binary files differnew file mode 100644 index 00000000000..480a7c4a8cc --- /dev/null +++ b/android/resources/icons/nele/preview_dark.png diff --git a/android/resources/icons/nele/rotate.png b/android/resources/icons/nele/rotate.png Binary files differnew file mode 100644 index 00000000000..780a73a7a6c --- /dev/null +++ b/android/resources/icons/nele/rotate.png diff --git a/android/resources/icons/nele/rotate@2x.png b/android/resources/icons/nele/rotate@2x.png Binary files differnew file mode 100644 index 00000000000..3b76df27c7b --- /dev/null +++ b/android/resources/icons/nele/rotate@2x.png diff --git a/android/resources/icons/nele/rotate@2x_dark.png b/android/resources/icons/nele/rotate@2x_dark.png Binary files differnew file mode 100644 index 00000000000..a00918e5c1b --- /dev/null +++ b/android/resources/icons/nele/rotate@2x_dark.png diff --git a/android/resources/icons/nele/rotate_dark.png b/android/resources/icons/nele/rotate_dark.png Binary files differnew file mode 100644 index 00000000000..924def96477 --- /dev/null +++ b/android/resources/icons/nele/rotate_dark.png diff --git a/android/resources/icons/nele/size.png b/android/resources/icons/nele/size.png Binary files differnew file mode 100644 index 00000000000..858d48ddf5b --- /dev/null +++ b/android/resources/icons/nele/size.png diff --git a/android/resources/icons/nele/size@2x.png b/android/resources/icons/nele/size@2x.png Binary files differnew file mode 100644 index 00000000000..de8fd625541 --- /dev/null +++ b/android/resources/icons/nele/size@2x.png diff --git a/android/resources/icons/nele/size@2x_dark.png b/android/resources/icons/nele/size@2x_dark.png Binary files differnew file mode 100644 index 00000000000..75425fa0327 --- /dev/null +++ b/android/resources/icons/nele/size@2x_dark.png diff --git a/android/resources/icons/nele/size_dark.png b/android/resources/icons/nele/size_dark.png Binary files differnew file mode 100644 index 00000000000..71b02e61284 --- /dev/null +++ b/android/resources/icons/nele/size_dark.png diff --git a/android/resources/icons/nele/tablet.png b/android/resources/icons/nele/tablet.png Binary files differnew file mode 100644 index 00000000000..db395003ef5 --- /dev/null +++ b/android/resources/icons/nele/tablet.png diff --git a/android/resources/icons/nele/tablet@2x.png b/android/resources/icons/nele/tablet@2x.png Binary files differnew file mode 100644 index 00000000000..4fb306477ce --- /dev/null +++ b/android/resources/icons/nele/tablet@2x.png diff --git a/android/resources/icons/nele/tablet@2x_dark.png b/android/resources/icons/nele/tablet@2x_dark.png Binary files differnew file mode 100644 index 00000000000..f8d1edc9312 --- /dev/null +++ b/android/resources/icons/nele/tablet@2x_dark.png diff --git a/android/resources/icons/nele/tablet_dark.png b/android/resources/icons/nele/tablet_dark.png Binary files differnew file mode 100644 index 00000000000..c8e12ab824b --- /dev/null +++ b/android/resources/icons/nele/tablet_dark.png diff --git a/android/resources/icons/nele/theme.png b/android/resources/icons/nele/theme.png Binary files differnew file mode 100644 index 00000000000..0f2be6c8b6c --- /dev/null +++ b/android/resources/icons/nele/theme.png diff --git a/android/resources/icons/nele/theme@2x.png b/android/resources/icons/nele/theme@2x.png Binary files differnew file mode 100644 index 00000000000..e9e7586926d --- /dev/null +++ b/android/resources/icons/nele/theme@2x.png diff --git a/android/resources/icons/nele/theme@2x_dark.png b/android/resources/icons/nele/theme@2x_dark.png Binary files differnew file mode 100644 index 00000000000..08e0581614e --- /dev/null +++ b/android/resources/icons/nele/theme@2x_dark.png diff --git a/android/resources/icons/nele/theme_dark.png b/android/resources/icons/nele/theme_dark.png Binary files differnew file mode 100644 index 00000000000..2ec8819db91 --- /dev/null +++ b/android/resources/icons/nele/theme_dark.png diff --git a/android/resources/icons/nele/tv.png b/android/resources/icons/nele/tv.png Binary files differnew file mode 100644 index 00000000000..5345b7466e3 --- /dev/null +++ b/android/resources/icons/nele/tv.png diff --git a/android/resources/icons/nele/tv@2x.png b/android/resources/icons/nele/tv@2x.png Binary files differnew file mode 100644 index 00000000000..e3ff800f5ee --- /dev/null +++ b/android/resources/icons/nele/tv@2x.png diff --git a/android/resources/icons/nele/tv@2x_dark.png b/android/resources/icons/nele/tv@2x_dark.png Binary files differnew file mode 100644 index 00000000000..c03d78cc1fa --- /dev/null +++ b/android/resources/icons/nele/tv@2x_dark.png diff --git a/android/resources/icons/nele/tv_dark.png b/android/resources/icons/nele/tv_dark.png Binary files differnew file mode 100644 index 00000000000..97cffef303b --- /dev/null +++ b/android/resources/icons/nele/tv_dark.png diff --git a/android/resources/icons/nele/wear.png b/android/resources/icons/nele/wear.png Binary files differnew file mode 100644 index 00000000000..165f51927ea --- /dev/null +++ b/android/resources/icons/nele/wear.png diff --git a/android/resources/icons/nele/wear@2x.png b/android/resources/icons/nele/wear@2x.png Binary files differnew file mode 100644 index 00000000000..19e2321e708 --- /dev/null +++ b/android/resources/icons/nele/wear@2x.png diff --git a/android/resources/icons/nele/wear@2x_dark.png b/android/resources/icons/nele/wear@2x_dark.png Binary files differnew file mode 100644 index 00000000000..96a3c3609b6 --- /dev/null +++ b/android/resources/icons/nele/wear@2x_dark.png diff --git a/android/resources/icons/nele/wear_dark.png b/android/resources/icons/nele/wear_dark.png Binary files differnew file mode 100644 index 00000000000..3b52183a3af --- /dev/null +++ b/android/resources/icons/nele/wear_dark.png diff --git a/android/resources/messages/AndroidBundle.properties b/android/resources/messages/AndroidBundle.properties index f8b116380ad..f2ad8219b17 100644 --- a/android/resources/messages/AndroidBundle.properties +++ b/android/resources/messages/AndroidBundle.properties @@ -47,6 +47,7 @@ not.activity.subclass.error={0} is not an Activity subclass or alias no.facet.error=No Android facet found for {0} android.logcat.tab.title=Logcat android.adb.logs.tab.title=ADB logs +android.captures.title=Captures android.logcat.title=Android android.logcat.error.dialog.title=Android Logcat Error android.logcat.color.page.name=Android Logcat @@ -523,6 +524,7 @@ android.lint.inspections.sd.card.path=Hardcoded reference to /sdcard android.lint.inspections.selectable.text=Dynamic text should probably be selectable android.lint.inspections.service.cast=Wrong system service casts android.lint.inspections.set.java.script.enabled=Using setJavaScriptEnabled +android.lint.inspections.shift.flags=Dangerous Flag Constant Declaration android.lint.inspections.short.alarm=Short or Frequent Alarm android.lint.inspections.show.toast=Toast created but not shown android.lint.inspections.signature.or.system.permissions=signatureOrSystem permissions declared diff --git a/android/src/META-INF/plugin.xml b/android/src/META-INF/plugin.xml index 8c038cdeb8e..c2795cd35d5 100755 --- a/android/src/META-INF/plugin.xml +++ b/android/src/META-INF/plugin.xml @@ -210,6 +210,7 @@ id ="gradle.experimental" displayName="Experimental" parentId="reference.settingsdialog.project.gradle"/> <applicationService serviceInterface="com.android.tools.idea.gradle.project.GradleExperimentalSettings" serviceImplementation="com.android.tools.idea.gradle.project.GradleExperimentalSettings"/> + <applicationService serviceImplementation="com.android.tools.idea.sdk.remote.internal.updater.SettingsController"/> <errorHandler implementation="com.android.tools.idea.diagnostics.error.ErrorReporter"/> <statisticsService implementationClass="com.android.tools.idea.stats.AndroidStatisticsService" key="android-studio" /> @@ -245,6 +246,9 @@ <fileTypeFactory implementation="org.jetbrains.android.fileTypes.AndroidFileTypeFactory"/> <fileTypeFactory implementation="com.android.tools.idea.lang.proguard.ProguardFileTypeFactory" /> <fileTypeFactory implementation="com.android.tools.idea.lang.databinding.DbFileTypeFactory" /> + <fileTypeFactory implementation="com.android.tools.idea.editors.hprof.HprofFileTypeFactory" /> + <fileTypeFactory implementation="com.android.tools.idea.editors.allocations.AllocationsFileTypeFactory" /> + <fileTypeFactory implementation="com.android.tools.idea.editors.vmtrace.VmTraceFileTypeFactory" /> <lang.parserDefinition language="Renderscript" implementationClass="com.android.tools.idea.lang.rs.RenderscriptParserDefinition"/> <lang.parserDefinition language="PROGUARD" implementationClass="com.android.tools.idea.lang.proguard.ProguardParserDefinition" /> @@ -498,6 +502,7 @@ <globalInspection hasStaticDescription="true" shortName="AndroidLintSelectableText" displayName="Dynamic text should probably be selectable" groupKey="android.lint.inspections.group.name" bundle="messages.AndroidBundle" enabledByDefault="false" level="WARNING" implementationClass="org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSelectableTextInspection"/> <globalInspection hasStaticDescription="true" shortName="AndroidLintServiceCast" displayName="Wrong system service casts" groupKey="android.lint.inspections.group.name" bundle="messages.AndroidBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintServiceCastInspection"/> <globalInspection hasStaticDescription="true" shortName="AndroidLintSetJavaScriptEnabled" displayName="Using setJavaScriptEnabled" groupKey="android.lint.inspections.group.name" bundle="messages.AndroidBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSetJavaScriptEnabledInspection"/> + <globalInspection hasStaticDescription="true" shortName="AndroidLintShiftFlags" displayName="Dangerous Flag Constant Declaration" groupKey="android.lint.inspections.group.name" bundle="messages.AndroidBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintShiftFlagsInspection"/> <globalInspection hasStaticDescription="true" shortName="AndroidLintShortAlarm" displayName="Short or Frequent Alarm" groupKey="android.lint.inspections.group.name" bundle="messages.AndroidBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintShortAlarmInspection"/> <globalInspection hasStaticDescription="true" shortName="AndroidLintShowToast" displayName="Toast created but not shown" groupKey="android.lint.inspections.group.name" bundle="messages.AndroidBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintShowToastInspection"/> <globalInspection hasStaticDescription="true" shortName="AndroidLintSignatureOrSystemPermissions" displayName="signatureOrSystem permissions declared" groupKey="android.lint.inspections.group.name" bundle="messages.AndroidBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSignatureOrSystemPermissionsInspection"/> diff --git a/android/src/com/android/tools/idea/actions/AndroidNewProjectAction.java b/android/src/com/android/tools/idea/actions/AndroidNewProjectAction.java index 2158fdd3c21..b808e0f8f2e 100644 --- a/android/src/com/android/tools/idea/actions/AndroidNewProjectAction.java +++ b/android/src/com/android/tools/idea/actions/AndroidNewProjectAction.java @@ -35,7 +35,7 @@ public class AndroidNewProjectAction extends AnAction implements DumbAware { dialog.init(); } catch (IllegalStateException e1) { - Logger.getInstance(AndroidNewProjectAction.class).error("Unable to launch New Project Wizard", e1); + Logger.getInstance(AndroidNewProjectAction.class).warn("Unable to launch New Project Wizard", e1); return; } diff --git a/android/src/com/android/tools/idea/actions/ShowLicensesUsedAction.java b/android/src/com/android/tools/idea/actions/ShowLicensesUsedAction.java index b7f0f931dc3..fef9a6a22f3 100644 --- a/android/src/com/android/tools/idea/actions/ShowLicensesUsedAction.java +++ b/android/src/com/android/tools/idea/actions/ShowLicensesUsedAction.java @@ -31,6 +31,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import javax.swing.text.JTextComponent; import java.awt.*; import java.io.File; import java.io.IOException; @@ -84,7 +85,11 @@ public class ShowLicensesUsedAction extends AnAction { } String text = "<html>" + sb.toString() + "</html>"; - JBScrollPane pane = new JBScrollPane(new JBLabel(text)); + JTextPane label = new JTextPane(); + label.setContentType("text/html"); + label.setText(text); + JBScrollPane pane = new JBScrollPane(label); + pane.setPreferredSize(JBUI.size(600, 400)); panel.add(pane, BorderLayout.CENTER); return panel; diff --git a/android/src/com/android/tools/idea/configurations/DeviceMenuAction.java b/android/src/com/android/tools/idea/configurations/DeviceMenuAction.java index d87224717c2..799ebfb0a5a 100644 --- a/android/src/com/android/tools/idea/configurations/DeviceMenuAction.java +++ b/android/src/com/android/tools/idea/configurations/DeviceMenuAction.java @@ -17,12 +17,11 @@ package com.android.tools.idea.configurations; import com.android.annotations.Nullable; import com.android.ide.common.rendering.HardwareConfigHelper; -import com.android.sdklib.AndroidVersion; -import com.android.sdklib.IAndroidTarget; import com.android.sdklib.devices.Device; import com.android.sdklib.devices.State; import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.internal.avd.AvdManager; +import com.android.tools.idea.ddms.screenshot.DeviceArtPainter; import com.android.tools.idea.rendering.multi.RenderPreviewMode; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -44,12 +43,20 @@ import static com.android.ide.common.rendering.HardwareConfigHelper.*; public class DeviceMenuAction extends FlatComboAction { private static final boolean LIST_RECENT_DEVICES = false; private final RenderContext myRenderContext; + private final boolean myClassicStyle; public DeviceMenuAction(@NotNull RenderContext renderContext) { + this(renderContext, true); + } + + public DeviceMenuAction(@NotNull RenderContext renderContext, boolean classicStyle) { myRenderContext = renderContext; + myClassicStyle = classicStyle; Presentation presentation = getTemplatePresentation(); presentation.setDescription("The virtual device to render the layout with"); - presentation.setIcon(AndroidIcons.Display); + if (classicStyle) { + presentation.setIcon(AndroidIcons.Display); + } updatePresentation(presentation); } @@ -63,8 +70,13 @@ public class DeviceMenuAction extends FlatComboAction { Configuration configuration = myRenderContext.getConfiguration(); boolean visible = configuration != null; if (visible) { - String label = getDeviceLabel(configuration.getDevice(), true); + Device device = configuration.getDevice(); + String label = getDeviceLabel(device, true); presentation.setText(label); + + if (!myClassicStyle) { + presentation.setIcon(getDeviceClassIcon(device)); + } } if (visible != presentation.isVisible()) { presentation.setVisible(visible); @@ -113,6 +125,34 @@ public class DeviceMenuAction extends FlatComboAction { return name; } + /** + * Similar to {@link DeviceMenuAction.FormFactor#getFormFactor(Device)} + * but (a) distinguishes between tablets and phones, and (b) uses the new Nele icons + */ + public Icon getDeviceClassIcon(@Nullable Device device) { + if (myClassicStyle) { + FormFactor formFactor = device != null ? FormFactor.getFormFactor(device) : FormFactor.MOBILE; + return formFactor.getIcon(); + } + + if (device != null) { + if (HardwareConfigHelper.isWear(device)) { + return AndroidIcons.NeleIcons.Wear; + } + else if (HardwareConfigHelper.isTv(device)) { + return AndroidIcons.NeleIcons.Tv; + } + + // Glass, Car not yet in the device list + + if (DeviceArtPainter.isTablet(device)) { + return AndroidIcons.NeleIcons.Tablet; + } + } + + return AndroidIcons.NeleIcons.Phone; + } + /** TODO: Combine with {@link com.android.tools.idea.wizard.FormFactorUtils.FormFactor} */ public enum FormFactor { MOBILE, WEAR, GLASS, TV, CAR; @@ -190,7 +230,7 @@ public class DeviceMenuAction extends FlatComboAction { boolean separatorNeeded = false; for (Device device : recent) { String label = getLabel(device, isNexus(device)); - Icon icon = FormFactor.getFormFactor(device).getIcon(); + Icon icon = getDeviceClassIcon(device); group.add(new SetDeviceAction(myRenderContext, label, device, icon, device == current)); separatorNeeded = true; } @@ -213,7 +253,7 @@ public class DeviceMenuAction extends FlatComboAction { if (device != null) { String avdName = "AVD: " + avd.getName(); boolean selected = current != null && (current.getDisplayName().equals(avdName) || current.getId().equals(avdName)); - Icon icon = FormFactor.getFormFactor(device).getIcon(); + Icon icon = getDeviceClassIcon(device); group.add(new SetDeviceAction(myRenderContext, avdName, device, icon, selected)); separatorNeeded = true; } @@ -279,7 +319,7 @@ public class DeviceMenuAction extends FlatComboAction { private void addNexusDeviceSection(@NotNull DefaultActionGroup group, @Nullable Device current, @NotNull List<Device> devices) { for (final Device device : devices) { String label = getLabel(device, true /*nexus*/); - Icon icon = FormFactor.getFormFactor(device).getIcon(); + Icon icon = getDeviceClassIcon(device); group.add(new SetDeviceAction(myRenderContext, label, device, icon, current == device)); } } @@ -295,7 +335,7 @@ public class DeviceMenuAction extends FlatComboAction { } for (final Device device : generic) { String label = getLabel(device, false /*nexus*/); - Icon icon = FormFactor.getFormFactor(device).getIcon(); + Icon icon = getDeviceClassIcon(device); group.add(new SetDeviceAction(myRenderContext, label, device, icon, current == device)); } } diff --git a/android/src/com/android/tools/idea/configurations/LocaleMenuAction.java b/android/src/com/android/tools/idea/configurations/LocaleMenuAction.java index 8af9c055f08..6f55848d895 100644 --- a/android/src/com/android/tools/idea/configurations/LocaleMenuAction.java +++ b/android/src/com/android/tools/idea/configurations/LocaleMenuAction.java @@ -44,9 +44,15 @@ import static com.android.ide.common.resources.configuration.LocaleQualifier.BCP public class LocaleMenuAction extends FlatComboAction { private final RenderContext myRenderContext; + private final boolean myClassicStyle; public LocaleMenuAction(RenderContext renderContext) { + this(renderContext, true); + } + + public LocaleMenuAction(RenderContext renderContext, boolean classicStyle) { myRenderContext = renderContext; + myClassicStyle = classicStyle; Presentation presentation = getTemplatePresentation(); presentation.setDescription("Locale to render layout with inside the IDE"); updatePresentation(presentation); @@ -67,16 +73,16 @@ public class LocaleMenuAction extends FlatComboAction { Configuration configuration = myRenderContext.getConfiguration(); if (configuration != null && locales.size() > 0) { - group.add(new SetLocaleAction(myRenderContext, getLocaleLabel(Locale.ANY, false), Locale.ANY)); + group.add(new SetLocaleAction(myRenderContext, getLocaleLabel(Locale.ANY, false, myClassicStyle), Locale.ANY)); group.addSeparator(); Collections.sort(locales, Locale.LANGUAGE_CODE_COMPARATOR); for (Locale locale : locales) { - String title = getLocaleLabel(locale, false); + String title = getLocaleLabel(locale, false, myClassicStyle); VirtualFile better = ConfigurationMatcher.getBetterMatch(configuration, null, null, locale, null); if (better != null) { - title = ConfigurationAction.getBetterMatchLabel(getLocaleLabel(locale, true), better, configuration.getFile()); + title = ConfigurationAction.getBetterMatchLabel(getLocaleLabel(locale, true, myClassicStyle), better, configuration.getFile()); } group.add(new SetLocaleAction(myRenderContext, title, locale)); @@ -175,14 +181,17 @@ public class LocaleMenuAction extends FlatComboAction { //Locale locale = configuration.isLocaleSpecificLayout() // ? configuration.getLocale() : configuration.getConfigurationManager().getLocale(); Locale locale = configuration.getLocale(); - if (locale == Locale.ANY) { + if (!myClassicStyle) { + presentation.setIcon(AndroidIcons.NeleIcons.Language); + } else if (locale == Locale.ANY) { presentation.setIcon(AndroidIcons.Globe); } else { presentation.setIcon(locale.getFlagImage()); } - String brief = getLocaleLabel(locale, true); + String brief = getLocaleLabel(locale, true, myClassicStyle); presentation.setText(brief); - } else { + } + else { presentation.setIcon(AndroidIcons.Globe); } if (visible != presentation.isVisible()) { @@ -205,11 +214,31 @@ public class LocaleMenuAction extends FlatComboAction { */ @NotNull public static String getLocaleLabel(@Nullable Locale locale, boolean brief) { + return getLocaleLabel(locale, brief, true); + } + + /** + * Returns a suitable label to use to display the given locale + * + * @param locale the locale to look up a label for + * @param brief if true, generate a brief label (suitable for a toolbar + * button), otherwise a fuller name (suitable for a menu item) + * @param classicStyle if true, use the pre Android Studio 1.5 configuration toolbar style (temporary compatibility code) + * @return the label + */ + public static String getLocaleLabel(@Nullable Locale locale, boolean brief, boolean classicStyle) { if (locale == null) { + if (!classicStyle) { + return "Language"; + } return ""; } if (!locale.hasLanguage()) { + if (!classicStyle) { + return "Language"; + } + if (brief) { // Just use the icon return ""; diff --git a/android/src/com/android/tools/idea/configurations/OrientationMenuAction.java b/android/src/com/android/tools/idea/configurations/OrientationMenuAction.java index 6a6ed85b3f7..3fed61a1835 100644 --- a/android/src/com/android/tools/idea/configurations/OrientationMenuAction.java +++ b/android/src/com/android/tools/idea/configurations/OrientationMenuAction.java @@ -35,9 +35,15 @@ import java.util.List; public class OrientationMenuAction extends FlatComboAction { private final RenderContext myRenderContext; + private final boolean myClassicStyle; public OrientationMenuAction(RenderContext renderContext) { + this(renderContext, true); + } + + public OrientationMenuAction(RenderContext renderContext, boolean classicStyle) { myRenderContext = renderContext; + myClassicStyle = classicStyle; Presentation presentation = getTemplatePresentation(); presentation.setDescription("Go to next state"); updatePresentation(presentation); @@ -50,6 +56,10 @@ public class OrientationMenuAction extends FlatComboAction { } private void updatePresentation(Presentation presentation) { + if (!myClassicStyle) { + presentation.setIcon(AndroidIcons.NeleIcons.Rotate); + return; + } Configuration configuration = myRenderContext.getConfiguration(); if (configuration != null) { State current = configuration.getDeviceState(); diff --git a/android/src/com/android/tools/idea/configurations/TargetMenuAction.java b/android/src/com/android/tools/idea/configurations/TargetMenuAction.java index d16f57f5565..755e8dcce9c 100644 --- a/android/src/com/android/tools/idea/configurations/TargetMenuAction.java +++ b/android/src/com/android/tools/idea/configurations/TargetMenuAction.java @@ -20,6 +20,7 @@ import com.android.sdklib.AndroidVersion; import com.android.sdklib.IAndroidTarget; import com.android.tools.idea.AndroidPsiUtils; import com.android.tools.idea.rendering.ResourceHelper; +import com.android.tools.idea.rendering.multi.CompatibilityRenderTarget; import com.android.tools.idea.rendering.multi.RenderPreviewMode; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.AnActionEvent; @@ -41,15 +42,31 @@ import static com.android.tools.idea.configurations.Configuration.PREFERENCES_MI public class TargetMenuAction extends FlatComboAction { private final RenderContext myRenderContext; + private final boolean myUseCompatibilityTarget; - public TargetMenuAction(RenderContext renderContext) { + /** + * Creates a {@code TargetMenuAction} + * @param renderContext A {@link RenderContext} instance + * @param useCompatibilityTarget when true, this menu action will set a CompatibilityRenderTarget as instead of a real IAndroidTarget + * @param classicStyle if true, use the pre Android Studio 1.5 configuration toolbar style (temporary compatibility code) + */ + public TargetMenuAction(RenderContext renderContext, boolean useCompatibilityTarget, boolean classicStyle) { myRenderContext = renderContext; + myUseCompatibilityTarget = useCompatibilityTarget; Presentation presentation = getTemplatePresentation(); presentation.setDescription("Android version to use when rendering layouts in the IDE"); - presentation.setIcon(AndroidIcons.Targets); + presentation.setIcon(classicStyle ? AndroidIcons.Targets : AndroidIcons.NeleIcons.Api); updatePresentation(presentation); } + public TargetMenuAction(RenderContext renderContext, boolean useCompatibilityTarget) { + this(renderContext, useCompatibilityTarget, true); + } + + public TargetMenuAction(RenderContext renderContext) { + this(renderContext, false); + } + @Override public void update(AnActionEvent e) { super.update(e); @@ -98,7 +115,7 @@ public class TargetMenuAction extends FlatComboAction { for (int i = targets.length - 1; i >= 0; i--) { IAndroidTarget target = targets[i]; - if (!ConfigurationManager.isLayoutLibTarget(target)) { + if (!myUseCompatibilityTarget && !ConfigurationManager.isLayoutLibTarget(target)) { continue; } @@ -121,6 +138,12 @@ public class TargetMenuAction extends FlatComboAction { String title = getRenderingTargetLabel(target, false); boolean select = current == target; + + if (myUseCompatibilityTarget) { + target = new CompatibilityRenderTarget(configuration.getConfigurationManager().getHighestApiTarget(), + target.getVersion().getFeatureLevel(), + target); + } group.add(new SetTargetAction(myRenderContext, title, target, select)); } diff --git a/android/src/com/android/tools/idea/configurations/ThemeSelectionPanel.java b/android/src/com/android/tools/idea/configurations/ThemeSelectionPanel.java index 7be4f147ba3..f9bab8d5065 100644 --- a/android/src/com/android/tools/idea/configurations/ThemeSelectionPanel.java +++ b/android/src/com/android/tools/idea/configurations/ThemeSelectionPanel.java @@ -78,6 +78,7 @@ public class ThemeSelectionPanel implements TreeSelectionListener, ListSelection @NotNull private ThemeFilterComponent myFilter; @Nullable private List<String> myFrameworkThemes; @Nullable private List<String> myProjectThemes; + @Nullable private List<String> myLibraryThemes; @Nullable private static Deque<String> ourRecent; @Nullable private ThemeCategory myCategory = ThemeCategory.ALL; @NotNull private Map<ThemeCategory, List<String>> myThemeMap = Maps.newEnumMap(ThemeCategory.class); @@ -329,6 +330,9 @@ public class ThemeSelectionPanel implements TreeSelectionListener, ListSelection for (String theme : getFrameworkThemes()) { themes.add(theme); } + for (String theme : getLibraryThemes()) { + themes.add(theme); + } break; case ROOT: default: @@ -386,6 +390,14 @@ public class ThemeSelectionPanel implements TreeSelectionListener, ListSelection return myProjectThemes; } + private List<String> getLibraryThemes() { + if (myLibraryThemes == null) { + myLibraryThemes = getSortedNames(getPublicThemes(myThemeResolver.getExternalLibraryThemes())); + } + + return myLibraryThemes; + } + // ---- Implements ListSelectionListener ---- @Override public void valueChanged(ListSelectionEvent listSelectionEvent) { diff --git a/android/src/com/android/tools/idea/ddms/actions/ScreenshotAction.java b/android/src/com/android/tools/idea/ddms/actions/ScreenshotAction.java index 28e16674670..d69979d1201 100644 --- a/android/src/com/android/tools/idea/ddms/actions/ScreenshotAction.java +++ b/android/src/com/android/tools/idea/ddms/actions/ScreenshotAction.java @@ -71,6 +71,7 @@ public class ScreenshotAction extends AbstractDeviceAction { File screenshot = viewer.getScreenshot(); VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(screenshot); if (vf != null) { + vf.refresh(false, false); FileEditorManager.getInstance(project).openFile(vf, true); } } diff --git a/android/src/com/android/tools/idea/editors/allocations/AllocationCaptureType.java b/android/src/com/android/tools/idea/editors/allocations/AllocationCaptureType.java index 31a7c1aab15..a314f92250b 100644 --- a/android/src/com/android/tools/idea/editors/allocations/AllocationCaptureType.java +++ b/android/src/com/android/tools/idea/editors/allocations/AllocationCaptureType.java @@ -24,7 +24,7 @@ import org.jetbrains.annotations.NotNull; public class AllocationCaptureType extends FileCaptureType { protected AllocationCaptureType() { - super("Allocation Tracking", AndroidIcons.Ddms.AllocationTracker, "Allocations_", ".alloc"); + super("Allocation Tracking", AndroidIcons.Ddms.AllocationTracker, "Allocations_", "." + AllocationsFileType.DEFAULT_EXTENSION); } @NotNull diff --git a/android/src/com/android/tools/idea/editors/allocations/AllocationsFileType.java b/android/src/com/android/tools/idea/editors/allocations/AllocationsFileType.java new file mode 100644 index 00000000000..a193d50f449 --- /dev/null +++ b/android/src/com/android/tools/idea/editors/allocations/AllocationsFileType.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tools.idea.editors.allocations; + +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.vfs.VirtualFile; +import icons.AndroidIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class AllocationsFileType implements FileType { + public static final String DEFAULT_EXTENSION = "alloc"; + public static final AllocationsFileType INSTANCE = new AllocationsFileType(); + + @NotNull + @Override + public String getName() { + return "Allocations"; + } + + @NotNull + @Override + public String getDescription() { + return "Memory allocations capture file"; + } + + @NotNull + @Override + public String getDefaultExtension() { + return DEFAULT_EXTENSION; + } + + @Nullable + @Override + public Icon getIcon() { + return AndroidIcons.Ddms.AllocationTracker; + } + + @Override + public boolean isBinary() { + return true; + } + + @Override + public boolean isReadOnly() { + return true; + } + + @Nullable + @Override + public String getCharset(@NotNull VirtualFile file, @NotNull byte[] content) { + return null; + } +} diff --git a/android/src/com/android/tools/idea/editors/allocations/AllocationsFileTypeFactory.java b/android/src/com/android/tools/idea/editors/allocations/AllocationsFileTypeFactory.java new file mode 100644 index 00000000000..3e3b20fbd14 --- /dev/null +++ b/android/src/com/android/tools/idea/editors/allocations/AllocationsFileTypeFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tools.idea.editors.allocations; + +import com.intellij.openapi.fileTypes.FileTypeConsumer; +import com.intellij.openapi.fileTypes.FileTypeFactory; +import org.jetbrains.annotations.NotNull; + +public class AllocationsFileTypeFactory extends FileTypeFactory { + @Override + public void createFileTypes(@NotNull FileTypeConsumer consumer) { + consumer.consume(AllocationsFileType.INSTANCE, AllocationsFileType.INSTANCE.getDefaultExtension()); + } +} diff --git a/android/src/com/android/tools/idea/editors/allocations/AllocationsView.java b/android/src/com/android/tools/idea/editors/allocations/AllocationsView.java index 1901b2d6ec6..21ad109ce4f 100644 --- a/android/src/com/android/tools/idea/editors/allocations/AllocationsView.java +++ b/android/src/com/android/tools/idea/editors/allocations/AllocationsView.java @@ -88,8 +88,9 @@ public class AllocationsView implements SunburstComponent.SliceSelectionListener myTree = new Tree(myTreeModel); myTree.setRootVisible(false); - myTree.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, this.new TreeDataProvider()); + myTree.setShowsRootHandles(true); + myTree.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, this.new TreeDataProvider()); final DefaultActionGroup popupGroup = new DefaultActionGroup(); popupGroup.add(new EditMultipleSourcesAction()); myTree.addMouseListener(new PopupHandler() { diff --git a/android/src/com/android/tools/idea/editors/allocations/ColumnTreeBuilder.java b/android/src/com/android/tools/idea/editors/allocations/ColumnTreeBuilder.java index 227e0feb818..a8ccfd887b9 100644 --- a/android/src/com/android/tools/idea/editors/allocations/ColumnTreeBuilder.java +++ b/android/src/com/android/tools/idea/editors/allocations/ColumnTreeBuilder.java @@ -19,7 +19,6 @@ import com.android.annotations.Nullable; import com.google.common.collect.ImmutableList; import com.intellij.ui.ColoredTreeCellRenderer; import com.intellij.ui.components.JBScrollPane; -import com.intellij.ui.components.JBViewport; import com.intellij.ui.table.JBTable; import com.intellij.util.ui.tree.WideSelectionTreeUI; import org.jetbrains.annotations.NotNull; @@ -96,9 +95,10 @@ public class ColumnTreeBuilder { } public JComponent build() { + boolean showsRootHandles = myTree.getShowsRootHandles(); // Stash this value since it'll get stomped WideSelectionTreeUI. myTree.setUI(new ColumnTreeUI()); + myTree.setShowsRootHandles(showsRootHandles); myTree.setCellRenderer(myCellRenderer); - myTree.setShowsRootHandles(true); myTable.getColumnModel().addColumnModelListener(new TableColumnModelListener() { @Override @@ -134,8 +134,10 @@ public class ColumnTreeBuilder { Enumeration<TreePath> expanded = myTree.getExpandedDescendants(new TreePath(myTree.getModel().getRoot())); comparator = key.getSortOrder() == SortOrder.ASCENDING ? comparator : Collections.reverseOrder(comparator); myTreeSorter.sort(comparator, key.getSortOrder()); - while (expanded.hasMoreElements()) { - myTree.expandPath(expanded.nextElement()); + if (expanded != null) { + while (expanded.hasMoreElements()) { + myTree.expandPath(expanded.nextElement()); + } } } } diff --git a/android/src/com/android/tools/idea/editors/hprof/ComputeDominatorAction.java b/android/src/com/android/tools/idea/editors/hprof/ComputeDominatorAction.java deleted file mode 100644 index a16afb86b24..00000000000 --- a/android/src/com/android/tools/idea/editors/hprof/ComputeDominatorAction.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tools.idea.editors.hprof; - -import com.android.tools.perflib.heap.Snapshot; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.Project; -import icons.AndroidIcons; -import org.jetbrains.annotations.NotNull; - -public abstract class ComputeDominatorAction extends AnAction { - @NotNull private Snapshot mySnapshot; - @NotNull Project myProject; - - public ComputeDominatorAction(@NotNull Snapshot snapshot, @NotNull Project project) { - super(null, "Compute Dominators", AndroidIcons.Ddms.AllocationTracker); - mySnapshot = snapshot; - myProject = project; - } - - @Override - public void actionPerformed(AnActionEvent e) { - ProgressManager.getInstance().run(new ComputeDominatorIndicator(myProject)); - } - - public abstract void onDominatorsComputed(); - - private class ComputeDominatorIndicator extends Task.Modal { - public ComputeDominatorIndicator(@NotNull Project project) { - super(project, "Computing dominators...", true); - } - - @Override - public void onSuccess() { - super.onSuccess(); - onDominatorsComputed(); - } - - @Override - public void run(@NotNull ProgressIndicator indicator) { - indicator.setIndeterminate(true); - // TODO do this in a separate thread. - mySnapshot.computeDominators(); - } - } -} diff --git a/android/src/com/android/tools/idea/editors/hprof/HprofEditor.java b/android/src/com/android/tools/idea/editors/hprof/HprofEditor.java index be050247c5e..1d5ca46bf9c 100644 --- a/android/src/com/android/tools/idea/editors/hprof/HprofEditor.java +++ b/android/src/com/android/tools/idea/editors/hprof/HprofEditor.java @@ -112,15 +112,6 @@ public class HprofEditor extends UserDataHolderBase implements FileEditor { indicator.setFraction(0.5); indicator.setText("Computing dominators..."); mySnapshot.computeDominators(); - - ApplicationManager.getApplication().invokeLater(new Runnable() { - @Override - public void run() { - myPanel.removeAll(); - myPanel.setLayout(new BorderLayout()); - myPanel.add(new HprofViewPanel(project, HprofEditor.this, mySnapshot).getComponent(), BorderLayout.CENTER); - } - }); } catch (Throwable throwable) { LOG.info(throwable); diff --git a/android/src/com/android/tools/idea/editors/hprof/HprofFileType.java b/android/src/com/android/tools/idea/editors/hprof/HprofFileType.java new file mode 100644 index 00000000000..ac21c01834c --- /dev/null +++ b/android/src/com/android/tools/idea/editors/hprof/HprofFileType.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tools.idea.editors.hprof; + +import com.android.SdkConstants; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.vfs.VirtualFile; +import icons.AndroidIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class HprofFileType implements FileType { + public static final HprofFileType INSTANCE = new HprofFileType(); + + @NotNull + @Override + public String getName() { + return "Hprof"; + } + + @NotNull + @Override + public String getDescription() { + return "Heap Profiler (Hprof) files"; + } + + @NotNull + @Override + public String getDefaultExtension() { + return SdkConstants.EXT_HPROF; + } + + @Nullable + @Override + public Icon getIcon() { + return AndroidIcons.Ddms.DumpHprof; + } + + @Override + public boolean isBinary() { + return true; + } + + @Override + public boolean isReadOnly() { + return true; + } + + @Nullable + @Override + public String getCharset(@NotNull VirtualFile file, @NotNull byte[] content) { + return null; + } +} diff --git a/android/src/com/android/tools/idea/editors/hprof/HprofFileTypeFactory.java b/android/src/com/android/tools/idea/editors/hprof/HprofFileTypeFactory.java new file mode 100644 index 00000000000..5e02d66197b --- /dev/null +++ b/android/src/com/android/tools/idea/editors/hprof/HprofFileTypeFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tools.idea.editors.hprof; + +import com.intellij.openapi.fileTypes.FileTypeConsumer; +import com.intellij.openapi.fileTypes.FileTypeFactory; +import org.jetbrains.annotations.NotNull; + +public class HprofFileTypeFactory extends FileTypeFactory { + @Override + public void createFileTypes(@NotNull FileTypeConsumer consumer) { + consumer.consume(HprofFileType.INSTANCE, HprofFileType.INSTANCE.getDefaultExtension()); + } +} diff --git a/android/src/com/android/tools/idea/editors/hprof/HprofViewPanel.java b/android/src/com/android/tools/idea/editors/hprof/HprofViewPanel.java index 1fdf56596b5..693e67c1963 100644 --- a/android/src/com/android/tools/idea/editors/hprof/HprofViewPanel.java +++ b/android/src/com/android/tools/idea/editors/hprof/HprofViewPanel.java @@ -15,10 +15,10 @@ */ package com.android.tools.idea.editors.hprof; -import com.android.tools.idea.editors.hprof.tables.ClassesTreeView; -import com.android.tools.idea.editors.hprof.tables.InstanceReferenceTree; -import com.android.tools.idea.editors.hprof.tables.InstancesTree; -import com.android.tools.idea.editors.hprof.tables.SelectionModel; +import com.android.tools.idea.editors.hprof.views.ClassesTreeView; +import com.android.tools.idea.editors.hprof.views.InstanceReferenceTreeView; +import com.android.tools.idea.editors.hprof.views.InstancesTreeView; +import com.android.tools.idea.editors.hprof.views.SelectionModel; import com.android.tools.perflib.heap.Heap; import com.android.tools.perflib.heap.Snapshot; import com.intellij.openapi.Disposable; @@ -61,16 +61,6 @@ public class HprofViewPanel implements Disposable { } mySelectionModel = new SelectionModel(currentHeap); - final InstanceReferenceTree referenceTree = new InstanceReferenceTree(project, mySelectionModel); - treePanel.add(referenceTree.getComponent(), BorderLayout.CENTER); - - final InstancesTree instancesTree = new InstancesTree(project, mySelectionModel); - final ClassesTreeView classesTreeView = new ClassesTreeView(project, mySelectionModel); - JBSplitter splitter = createNavigationSplitter(classesTreeView.getComponent(), instancesTree.getComponent()); - - JBPanel classPanel = new JBPanel(new BorderLayout()); - classPanel.add(splitter, BorderLayout.CENTER); - DefaultActionGroup group = new DefaultActionGroup(new ComboBoxAction() { @NotNull @Override @@ -98,7 +88,18 @@ public class HprofViewPanel implements Disposable { } }); + final InstanceReferenceTreeView referenceTree = new InstanceReferenceTreeView(project, mySelectionModel); + treePanel.add(referenceTree.getComponent(), BorderLayout.CENTER); + + final InstancesTreeView instancesTreeView = new InstancesTreeView(project, mySelectionModel); + final ClassesTreeView classesTreeView = new ClassesTreeView(project, group, mySelectionModel); + JBSplitter splitter = createNavigationSplitter(classesTreeView.getComponent(), instancesTreeView.getComponent()); + + JBPanel classPanel = new JBPanel(new BorderLayout()); + classPanel.add(splitter, BorderLayout.CENTER); + ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true); + toolbar.getComponent().setName("HprofActionToolbar"); classPanel.add(toolbar.getComponent(), BorderLayout.NORTH); JBSplitter mainSplitter = new JBSplitter(true); diff --git a/android/src/com/android/tools/idea/editors/hprof/descriptors/InstanceFieldDescriptorImpl.java b/android/src/com/android/tools/idea/editors/hprof/descriptors/InstanceFieldDescriptorImpl.java index 2be8260f6ba..5bf91ee000b 100644 --- a/android/src/com/android/tools/idea/editors/hprof/descriptors/InstanceFieldDescriptorImpl.java +++ b/android/src/com/android/tools/idea/editors/hprof/descriptors/InstanceFieldDescriptorImpl.java @@ -27,8 +27,6 @@ import com.sun.jdi.Value; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Map; - public class InstanceFieldDescriptorImpl extends HprofFieldDescriptorImpl { private static final int MAX_VALUE_TEXT_LENGTH = 1024; @NotNull private ObjectReferenceImpl myObjectReference; @@ -99,28 +97,46 @@ public class InstanceFieldDescriptorImpl extends HprofFieldDescriptorImpl { myTruncatedValueText = String.format(" \"class %s\"", ((ClassObj)myValueData).getClassName()); } else if (isString()) { + int count = -1; + int offset = 0; ArrayInstance charBufferArray = null; assert (myValueData instanceof ClassInstance); ClassInstance classInstance = (ClassInstance)myValueData; for (ClassInstance.FieldValue entry : classInstance.getValues()) { - if ("value".equals(entry.getField().getName())) { - charBufferArray = (ArrayInstance)entry.getValue(); + if (charBufferArray == null && "value".equals(entry.getField().getName())) { + if (entry.getValue() instanceof ArrayInstance) { + charBufferArray = (ArrayInstance)entry.getValue(); + } + } + else if ("count".equals(entry.getField().getName())) { + if (entry.getValue() instanceof Integer) { + count = (Integer)entry.getValue(); + } + } + else if ("offset".equals(entry.getField().getName())) { + if (entry.getValue() instanceof Integer) { + offset = (Integer)entry.getValue(); + } } } - assert (charBufferArray != null); - - char[] stringChars = charBufferArray.asCharArray(MAX_VALUE_TEXT_LENGTH); - int charLength = stringChars.length; - StringBuilder builder = new StringBuilder(6 + charLength); - builder.append(" \""); - if (charLength == MAX_VALUE_TEXT_LENGTH) { - builder.append(stringChars, 0, charLength - 1).append("..."); + + if (charBufferArray != null) { + char[] stringChars = charBufferArray.asCharArray(offset >= 0 ? offset : 0, Math.max(Math.min(count, MAX_VALUE_TEXT_LENGTH), 0)); + int charLength = stringChars.length; + StringBuilder builder = new StringBuilder(6 + charLength); + builder.append(" \""); + if (charLength == MAX_VALUE_TEXT_LENGTH) { + builder.append(stringChars, 0, charLength - 1).append("..."); + } + else { + builder.append(stringChars); + } + builder.append("\""); + myTruncatedValueText = builder.toString(); } else { - builder.append(stringChars); + myTruncatedValueText = " ...<invalid string value>..."; } - builder.append("\""); - myTruncatedValueText = builder.toString(); } else { myTruncatedValueText = ""; diff --git a/android/src/com/android/tools/idea/editors/hprof/tables/ClassesTreeView.java b/android/src/com/android/tools/idea/editors/hprof/tables/ClassesTreeView.java deleted file mode 100644 index a9f8ca806cf..00000000000 --- a/android/src/com/android/tools/idea/editors/hprof/tables/ClassesTreeView.java +++ /dev/null @@ -1,459 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tools.idea.editors.hprof.tables; - -import com.android.tools.idea.actions.EditMultipleSourcesAction; -import com.android.tools.idea.actions.PsiFileAndLineNavigation; -import com.android.tools.idea.editors.allocations.ColumnTreeBuilder; -import com.android.tools.perflib.heap.ClassObj; -import com.android.tools.perflib.heap.Heap; -import com.android.tools.perflib.heap.Instance; -import com.intellij.ide.DataManager; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.ui.ColoredTreeCellRenderer; -import com.intellij.ui.JBColor; -import com.intellij.ui.PopupHandler; -import com.intellij.ui.SimpleTextAttributes; -import com.intellij.ui.components.JBList; -import com.intellij.ui.treeStructure.Tree; -import com.intellij.util.PlatformIcons; -import com.intellij.util.containers.HashSet; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreeNode; -import javax.swing.tree.TreePath; -import java.awt.*; -import java.util.*; -import java.util.List; - -public class ClassesTreeView implements DataProvider { - @NotNull private Project myProject; - @NotNull private Tree myTree; - @NotNull private JComponent myColumnTree; - @Nullable private Comparator<DefaultMutableTreeNode> myComparator; - private int myCurrentHeapId; - - public ClassesTreeView(@NotNull Project project, @NotNull final SelectionModel selectionModel) { - myProject = project; - - final DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode("Root node")); - myTree = new Tree(model); - myTree.setRootVisible(false); - myTree.setShowsRootHandles(false); - myTree.setLargeModel(true); - - myTree.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, this); - JBList contextActionList = new JBList(new EditMultipleSourcesAction()); - JBPopupFactory.getInstance().createListPopupBuilder(contextActionList); - final DefaultActionGroup popupGroup = new DefaultActionGroup(new EditMultipleSourcesAction()); - myTree.addMouseListener(new PopupHandler() { - @Override - public void invokePopup(Component comp, int x, int y) { - ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, popupGroup).getComponent().show(comp, x, y); - } - }); - - selectionModel.addListener(new SelectionModel.SelectionListener() { - @Override - public void onHeapChanged(@NotNull Heap heap) { - final Heap selectedHeap = selectionModel.getHeap(); - myCurrentHeapId = selectedHeap.getId(); - - assert model.getRoot() instanceof DefaultMutableTreeNode; - DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot(); - root.removeAllChildren(); - - ArrayList<ClassObj> entries = new ArrayList<ClassObj>(selectedHeap.getClasses().size() + selectedHeap.getInstancesCount()); - // Find the union of the classObjs this heap has instances of, plus the classObjs themselves that are allocated on this heap. - HashSet<ClassObj> entriesSet = new HashSet<ClassObj>(selectedHeap.getClasses().size() + selectedHeap.getInstancesCount()); - for (ClassObj classObj : selectedHeap.getClasses()) { - entriesSet.add(classObj); - } - for (Instance instance : selectedHeap.getInstances()) { - entriesSet.add(instance.getClassObj()); - } - entries.addAll(entriesSet); - - ClassObj classToSelect = selectionModel.getClassObj(); - TreeNode nodeToSelect = null; - for (ClassObj classObj : entries) { - root.add(new DefaultMutableTreeNode(new HeapClassObj(classObj, myCurrentHeapId))); - if (classObj == classToSelect) { - nodeToSelect = root.getLastChild(); - } - } - - sortTree(root); - model.nodeStructureChanged(root); - final TreeNode targetNode = nodeToSelect; - - // This is kind of clunky, but the viewport doesn't know how big the tree is until it repaints. - // We need to do this because the contents of this tree has been more or less completely replaced. - // Unfortunately, calling repaint() only queues it, so we actually need an extra frame to select the node. - ApplicationManager.getApplication().invokeLater(new Runnable() { - @Override - public void run() { - // If the new heap has the selected class (from a previous heap), then select it and scroll to it. - if (targetNode != null) { - myColumnTree.revalidate(); - TreePath pathToSelect = new TreePath(model.getPathToRoot(targetNode)); - myTree.setSelectionPath(pathToSelect); - myTree.scrollPathToVisible(pathToSelect); - } - else { - selectionModel.setClassObj(null); - myTree.scrollRowToVisible(0); - } - } - }); - } - - @Override - public void onClassObjChanged(@Nullable ClassObj classObj) { - - } - - @Override - public void onInstanceChanged(@Nullable Instance instance) { - - } - }); - - myTree.addTreeSelectionListener(new TreeSelectionListener() { - @Override - public void valueChanged(TreeSelectionEvent e) { - TreePath path = e.getPath(); - if (path == null || path.getPathCount() < 2 || !e.isAddedPath()) { - selectionModel.setClassObj(null); - return; - } - - DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getPathComponent(1); - if (node.getUserObject() instanceof HeapClassObj) { - selectionModel.setClassObj(((HeapClassObj)node.getUserObject()).getClassObj()); - } - } - }); - - ColumnTreeBuilder builder = new ColumnTreeBuilder(myTree).addColumn( - new ColumnTreeBuilder.ColumnBuilder() - .setName("Class Name") - .setPreferredWidth(800) - .setHeaderAlignment(SwingConstants.LEFT) - .setComparator(new Comparator<DefaultMutableTreeNode>() { - @Override - public int compare(DefaultMutableTreeNode a, DefaultMutableTreeNode b) { - int comparisonResult = ((HeapClassObj)a.getUserObject()).getSimpleName() - .compareToIgnoreCase(((HeapClassObj)b.getUserObject()).getSimpleName()); - if (comparisonResult == 0) { - return ((HeapClassObj)a.getUserObject()).getClassObj().getClassName() - .compareToIgnoreCase(((HeapClassObj)b.getUserObject()).getClassObj().getClassName()); - } - return comparisonResult; - } - }) - .setRenderer(new ColoredTreeCellRenderer() { - @Override - public void customizeCellRenderer(@NotNull JTree tree, - Object value, - boolean selected, - boolean expanded, - boolean leaf, - int row, - boolean hasFocus) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; - if (node.getUserObject() instanceof HeapClassObj) { - ClassObj clazz = ((HeapClassObj)node.getUserObject()).getClassObj(); - String name = clazz.getClassName(); - String pkg = null; - int i = name.lastIndexOf("."); - if (i != -1) { - pkg = name.substring(0, i); - name = name.substring(i + 1); - } - append(name, SimpleTextAttributes.REGULAR_ATTRIBUTES); - if (pkg != null) { - append(" (" + pkg + ")", new SimpleTextAttributes(Font.PLAIN, JBColor.GRAY)); - } - setTransparentIconBackground(true); - setIcon(PlatformIcons.CLASS_ICON); - // TODO reformat anonymous classes (ANONYMOUS_CLASS_ICON) to match IJ. - } - } - }) - ).addColumn( - new ColumnTreeBuilder.ColumnBuilder() - .setName("Total Count") - .setPreferredWidth(100) - .setHeaderAlignment(SwingConstants.RIGHT) - .setComparator(new Comparator<DefaultMutableTreeNode>() { - @Override - public int compare(DefaultMutableTreeNode a, DefaultMutableTreeNode b) { - return ((HeapClassObj)a.getUserObject()).getClassObj().getInstanceCount() - - ((HeapClassObj)b.getUserObject()).getClassObj().getInstanceCount(); - } - }) - .setRenderer(new ColoredTreeCellRenderer() { - @Override - public void customizeCellRenderer(@NotNull JTree tree, - Object value, - boolean selected, - boolean expanded, - boolean leaf, - int row, - boolean hasFocus) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; - if (node.getUserObject() instanceof HeapClassObj) { - append(Integer.toString(((HeapClassObj)node.getUserObject()).getClassObj().getInstanceCount())); - } - setTextAlign(SwingConstants.RIGHT); - } - }) - ).addColumn( - new ColumnTreeBuilder.ColumnBuilder() - .setName("Heap Count") - .setPreferredWidth(100) - .setHeaderAlignment(SwingConstants.RIGHT) - .setComparator(new Comparator<DefaultMutableTreeNode>() { - @Override - public int compare(DefaultMutableTreeNode a, DefaultMutableTreeNode b) { - return ((HeapClassObj)a.getUserObject()).getClassObj().getHeapInstancesCount(myCurrentHeapId) - - ((HeapClassObj)b.getUserObject()).getClassObj().getHeapInstancesCount(myCurrentHeapId); - } - }) - .setRenderer(new ColoredTreeCellRenderer() { - @Override - public void customizeCellRenderer(@NotNull JTree tree, - Object value, - boolean selected, - boolean expanded, - boolean leaf, - int row, - boolean hasFocus) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; - if (node.getUserObject() instanceof HeapClassObj) { - append(Integer.toString(((HeapClassObj)node.getUserObject()).getClassObj().getHeapInstancesCount(myCurrentHeapId))); - } - setTextAlign(SwingConstants.RIGHT); - } - }) - ).addColumn( - new ColumnTreeBuilder.ColumnBuilder() - .setName("Sizeof") - .setPreferredWidth(80) - .setHeaderAlignment(SwingConstants.RIGHT) - .setComparator(new Comparator<DefaultMutableTreeNode>() { - @Override - public int compare(DefaultMutableTreeNode a, DefaultMutableTreeNode b) { - return ((HeapClassObj)a.getUserObject()).getClassObj().getInstanceSize() - - ((HeapClassObj)b.getUserObject()).getClassObj().getInstanceSize(); - } - }) - .setRenderer(new ColoredTreeCellRenderer() { - @Override - public void customizeCellRenderer(@NotNull JTree tree, - Object value, - boolean selected, - boolean expanded, - boolean leaf, - int row, - boolean hasFocus) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; - if (node.getUserObject() instanceof HeapClassObj) { - append(Integer.toString(((HeapClassObj)node.getUserObject()).getClassObj().getInstanceSize())); - } - setTextAlign(SwingConstants.RIGHT); - } - }) - ).addColumn( - new ColumnTreeBuilder.ColumnBuilder() - .setName("Shallow Size") - .setPreferredWidth(100) - .setHeaderAlignment(SwingConstants.RIGHT) - .setComparator(new Comparator<DefaultMutableTreeNode>() { - @Override - public int compare(DefaultMutableTreeNode a, DefaultMutableTreeNode b) { - return ((HeapClassObj)a.getUserObject()).getClassObj().getShallowSize(myCurrentHeapId) - - ((HeapClassObj)b.getUserObject()).getClassObj().getShallowSize(myCurrentHeapId); - } - }) - .setRenderer(new ColoredTreeCellRenderer() { - @Override - public void customizeCellRenderer(@NotNull JTree tree, - Object value, - boolean selected, - boolean expanded, - boolean leaf, - int row, - boolean hasFocus) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; - if (node.getUserObject() instanceof HeapClassObj) { - append(Integer.toString(((HeapClassObj)node.getUserObject()).getClassObj().getShallowSize(myCurrentHeapId))); - } - setTextAlign(SwingConstants.RIGHT); - } - }) - ).addColumn( - new ColumnTreeBuilder.ColumnBuilder() - .setName("Retained Size") - .setPreferredWidth(120) - .setHeaderAlignment(SwingConstants.RIGHT) - .setComparator(new Comparator<DefaultMutableTreeNode>() { - @Override - public int compare(DefaultMutableTreeNode a, DefaultMutableTreeNode b) { - return (int)(((HeapClassObj)a.getUserObject()).getRetainedSize() - ((HeapClassObj)b.getUserObject()).getRetainedSize()); - } - }) - .setRenderer(new ColoredTreeCellRenderer() { - @Override - public void customizeCellRenderer(@NotNull JTree tree, - Object value, - boolean selected, - boolean expanded, - boolean leaf, - int row, - boolean hasFocus) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; - if (node.getUserObject() instanceof HeapClassObj) { - append(Long.toString(((HeapClassObj)node.getUserObject()).getRetainedSize())); - } - setTextAlign(SwingConstants.RIGHT); - } - }) - ); - - //noinspection NullableProblems - builder.setTreeSorter(new ColumnTreeBuilder.TreeSorter<DefaultMutableTreeNode>() { - @Override - public void sort(@NotNull Comparator<DefaultMutableTreeNode> comparator, @NotNull SortOrder sortOrder) { - if (myComparator != comparator) { - myComparator = comparator; - - DefaultTreeModel model = (DefaultTreeModel)myTree.getModel(); - DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot(); - - selectionModel.setSelectionLocked(true); - TreePath selectionPath = myTree.getSelectionPath(); - sortTree(root); - model.nodeStructureChanged(root); - myTree.setSelectionPath(selectionPath); - myTree.scrollPathToVisible(selectionPath); - selectionModel.setSelectionLocked(false); - } - } - }); - - myColumnTree = builder.build(); - } - - @NotNull - public JComponent getComponent() { - return myColumnTree; - } - - private void sortTree(@NotNull DefaultMutableTreeNode parent) { - if (parent.getChildCount() == 0 || myComparator == null) { - return; - } - - //noinspection unchecked - List<DefaultMutableTreeNode> children = Collections.list((Enumeration<DefaultMutableTreeNode>)parent.children()); - Collections.sort(children, myComparator); - - parent.removeAllChildren(); - for (DefaultMutableTreeNode child : children) { - parent.add(child); - sortTree(child); - } - } - - @Nullable - @Override - public Object getData(@NonNls String dataId) { - if (CommonDataKeys.NAVIGATABLE_ARRAY.is(dataId)) { - return getTargetFiles(); - } - else if (CommonDataKeys.PROJECT.is(dataId)) { - return myProject; - } - return null; - } - - @Nullable - private PsiFileAndLineNavigation[] getTargetFiles() { - TreePath path = myTree.getSelectionPath(); - if (path.getPathCount() < 2) { - return null; - } - - DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); - if (node.getUserObject() instanceof HeapClassObj) { - ClassObj classObj = ((HeapClassObj)node.getUserObject()).getClassObj(); - String className = classObj.getClassName(); - - int arrayIndex = className.indexOf("["); - if (arrayIndex >= 0) { - className = className.substring(0, arrayIndex); - } - - return PsiFileAndLineNavigation.wrappersForClassName(myProject, className, 1); - } - - return null; - } - - private static class HeapClassObj { - @NotNull private ClassObj myClassObj; - private long myRetainedSize; - private String mySimpleName; - - private HeapClassObj(@NotNull ClassObj classObj, int heapId) { - myClassObj = classObj; - for (Instance instance : myClassObj.getHeapInstances(heapId)) { - myRetainedSize += instance.getTotalRetainedSize(); - } - - mySimpleName = myClassObj.getClassName(); - int index = mySimpleName.lastIndexOf('.'); - if (index >= 0 && index < mySimpleName.length() - 1) { - mySimpleName = mySimpleName.substring(index + 1, mySimpleName.length()); - } - } - - @NotNull - public ClassObj getClassObj() { - return myClassObj; - } - - public long getRetainedSize() { - return myRetainedSize; - } - - public String getSimpleName() { - return mySimpleName; - } - } -} diff --git a/android/src/com/android/tools/idea/editors/hprof/views/ClassesTreeView.java b/android/src/com/android/tools/idea/editors/hprof/views/ClassesTreeView.java new file mode 100644 index 00000000000..8063610c121 --- /dev/null +++ b/android/src/com/android/tools/idea/editors/hprof/views/ClassesTreeView.java @@ -0,0 +1,670 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tools.idea.editors.hprof.views; + +import com.android.tools.idea.actions.EditMultipleSourcesAction; +import com.android.tools.idea.actions.PsiFileAndLineNavigation; +import com.android.tools.idea.editors.allocations.ColumnTreeBuilder; +import com.android.tools.perflib.heap.ClassObj; +import com.android.tools.perflib.heap.Heap; +import com.android.tools.perflib.heap.Instance; +import com.intellij.ide.DataManager; +import com.intellij.openapi.actionSystem.*; +import com.android.tools.idea.editors.hprof.views.nodedata.HeapNode; +import com.android.tools.idea.editors.hprof.views.nodedata.HeapClassObjNode; +import com.android.tools.idea.editors.hprof.views.nodedata.HeapPackageNode; +import com.intellij.openapi.actionSystem.ex.CheckboxAction; +import com.intellij.openapi.actionSystem.ex.ComboBoxAction; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.JBColor; +import com.intellij.ui.PopupHandler; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.components.JBList; +import com.intellij.ui.TreeSpeedSearch; +import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.PlatformIcons; +import com.intellij.util.containers.Convertor; +import com.intellij.util.containers.HashSet; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.util.*; +import java.util.List; + +public class ClassesTreeView implements DataProvider { + public static final String TREE_NAME = "HprofClassesTree"; + + @NotNull private Project myProject; + @NotNull private Tree myTree; + @NotNull private DefaultTreeModel myTreeModel; + @NotNull private HeapPackageNode myRoot; + @NotNull private JComponent myColumnTree; + @Nullable private Comparator<HeapNode> myComparator; + + private int mySelectedHeapId; + + @NotNull private ListIndex myListIndex; + @NotNull private TreeIndex myTreeIndex; + @NotNull private DisplayMode myDisplayMode; + + public ClassesTreeView(@NotNull Project project, + @NotNull DefaultActionGroup editorActionGroup, + @NotNull final SelectionModel selectionModel) { + myProject = project; + + myRoot = new HeapPackageNode(null, ""); + myTreeModel = new DefaultTreeModel(myRoot); + myTree = new Tree(myTreeModel); + myTree.setName(TREE_NAME); + myDisplayMode = DisplayMode.LIST; + myTree.setRootVisible(false); + myTree.setShowsRootHandles(false); + myTree.setLargeModel(true); + + myTree.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, this); + JBList contextActionList = new JBList(new EditMultipleSourcesAction()); + JBPopupFactory.getInstance().createListPopupBuilder(contextActionList); + final DefaultActionGroup popupGroup = new DefaultActionGroup(new EditMultipleSourcesAction()); + myTree.addMouseListener(new PopupHandler() { + @Override + public void invokePopup(Component comp, int x, int y) { + ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, popupGroup).getComponent().show(comp, x, y); + } + }); + + editorActionGroup.addAction(new ComboBoxAction() { + @NotNull + @Override + protected DefaultActionGroup createPopupActionGroup(JComponent button) { + return new DefaultActionGroup(new CheckboxAction(DisplayMode.LIST.toString()) { + @Override + public boolean isSelected(AnActionEvent e) { + return myDisplayMode == DisplayMode.LIST; + } + + @Override + public void setSelected(AnActionEvent e, boolean state) { + if (state) { + myDisplayMode = DisplayMode.LIST; + myTree.setShowsRootHandles(false); + + myListIndex.buildList(myRoot); + restoreViewState(selectionModel); + } + } + }, new CheckboxAction(DisplayMode.TREE.toString()) { + @Override + public boolean isSelected(AnActionEvent e) { + return myDisplayMode == DisplayMode.TREE; + } + + @Override + public void setSelected(AnActionEvent e, boolean state) { + if (state) { + myDisplayMode = DisplayMode.TREE; + myTree.setShowsRootHandles(true); + + myTreeIndex.buildTree(mySelectedHeapId); + restoreViewState(selectionModel); + } + } + }); + } + + @Override + public void update(AnActionEvent e) { + super.update(e); + getTemplatePresentation().setText(myDisplayMode.toString()); + e.getPresentation().setText(myDisplayMode.toString()); + } + }); + + myListIndex = new ListIndex(); + myTreeIndex = new TreeIndex(); + selectionModel.addListener(myListIndex); // Add list index first, since that always updates; and tree index depends on it. + selectionModel.addListener(myTreeIndex); + + selectionModel.addListener(new SelectionModel.SelectionListener() { + @Override + public void onHeapChanged(@NotNull Heap heap) { + mySelectedHeapId = heap.getId(); + + assert myListIndex.myHeapId == mySelectedHeapId; + if (myDisplayMode == DisplayMode.LIST) { + myListIndex.buildList(myRoot); + } + else if (myDisplayMode == DisplayMode.TREE) { + myTreeIndex.buildTree(mySelectedHeapId); + } + + restoreViewState(selectionModel); + } + + @Override + public void onClassObjChanged(@Nullable ClassObj classObj) { + + } + + @Override + public void onInstanceChanged(@Nullable Instance instance) { + + } + }); + + myTree.addTreeSelectionListener(new TreeSelectionListener() { + @Override + public void valueChanged(TreeSelectionEvent e) { + TreePath path = e.getPath(); + if (!e.isAddedPath()) { + return; + } + + if (path == null || path.getPathCount() < 2) { + selectionModel.setClassObj(null); + return; + } + + assert path.getLastPathComponent() instanceof HeapNode; + HeapNode heapNode = (HeapNode)path.getLastPathComponent(); + if (heapNode instanceof HeapClassObjNode) { + selectionModel.setClassObj(((HeapClassObjNode)heapNode).getClassObj()); + } + } + }); + + ColumnTreeBuilder builder = new ColumnTreeBuilder(myTree).addColumn( + new ColumnTreeBuilder.ColumnBuilder() + .setName("Class Name") + .setPreferredWidth(800) + .setHeaderAlignment(SwingConstants.LEFT) + .setComparator(new Comparator<HeapNode>() { + @Override + public int compare(HeapNode a, HeapNode b) { + int valueA = a instanceof HeapPackageNode ? 0 : 1; + int valueB = b instanceof HeapPackageNode ? 0 : 1; + if (valueA != valueB) { + return valueA - valueB; + } + return compareNames(a, b); + } + }) + .setRenderer(new ColoredTreeCellRenderer() { + @Override + public void customizeCellRenderer(@NotNull JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + if (value instanceof HeapClassObjNode) { + ClassObj clazz = ((HeapClassObjNode)value).getClassObj(); + String name = clazz.getClassName(); + String pkg = null; + int i = name.lastIndexOf("."); + if (i != -1) { + pkg = name.substring(0, i); + name = name.substring(i + 1); + } + append(name, SimpleTextAttributes.REGULAR_ATTRIBUTES); + if (pkg != null) { + append(" (" + pkg + ")", new SimpleTextAttributes(Font.PLAIN, JBColor.GRAY)); + } + setTransparentIconBackground(true); + setIcon(PlatformIcons.CLASS_ICON); + // TODO reformat anonymous classes (ANONYMOUS_CLASS_ICON) to match IJ. + } + else if (value instanceof HeapNode) { + append(((HeapNode)value).getSimpleName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + setTransparentIconBackground(true); + setIcon(PlatformIcons.PACKAGE_ICON); + } + else { + append("This should not be rendered"); + } + } + }) + ).addColumn( + new ColumnTreeBuilder.ColumnBuilder() + .setName("Total Count") + .setPreferredWidth(100) + .setHeaderAlignment(SwingConstants.RIGHT) + .setComparator(new Comparator<HeapNode>() { + @Override + public int compare(HeapNode a, HeapNode b) { + int result = a.getTotalCount() - b.getTotalCount(); + return result == 0 ? compareNames(a, b) : result; + } + }) + .setRenderer(new ColoredTreeCellRenderer() { + @Override + public void customizeCellRenderer(@NotNull JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + if (value instanceof HeapNode) { + append(Integer.toString(((HeapNode)value).getTotalCount())); + } + setTextAlign(SwingConstants.RIGHT); + } + }) + ).addColumn( + new ColumnTreeBuilder.ColumnBuilder() + .setName("Heap Count") + .setPreferredWidth(100) + .setHeaderAlignment(SwingConstants.RIGHT) + .setComparator(new Comparator<HeapNode>() { + @Override + public int compare(HeapNode a, HeapNode b) { + int result = a.getHeapInstancesCount(mySelectedHeapId) - b.getHeapInstancesCount(mySelectedHeapId); + return result == 0 ? compareNames(a, b) : result; + } + }) + .setRenderer(new ColoredTreeCellRenderer() { + @Override + public void customizeCellRenderer(@NotNull JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + if (value instanceof HeapNode) { + append(Integer.toString(((HeapNode)value).getHeapInstancesCount(mySelectedHeapId))); + } + setTextAlign(SwingConstants.RIGHT); + } + }) + ).addColumn( + new ColumnTreeBuilder.ColumnBuilder() + .setName("Sizeof") + .setPreferredWidth(80) + .setHeaderAlignment(SwingConstants.RIGHT) + .setComparator(new Comparator<HeapNode>() { + @Override + public int compare(HeapNode a, HeapNode b) { + int sizeA = a.getInstanceSize(); + int sizeB = b.getInstanceSize(); + if (sizeA < 0 && sizeB < 0) { + return compareNames(a, b); + } + int result = sizeA - sizeB; + return result == 0 ? compareNames(a, b) : result; + } + }) + .setRenderer(new ColoredTreeCellRenderer() { + @Override + public void customizeCellRenderer(@NotNull JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + if (value instanceof HeapClassObjNode) { + append(Integer.toString(((HeapClassObjNode)value).getInstanceSize())); + } + setTextAlign(SwingConstants.RIGHT); + } + }) + ).addColumn( + new ColumnTreeBuilder.ColumnBuilder() + .setName("Shallow Size") + .setPreferredWidth(100) + .setHeaderAlignment(SwingConstants.RIGHT) + .setComparator(new Comparator<HeapNode>() { + @Override + public int compare(HeapNode a, HeapNode b) { + int result = a.getShallowSize(mySelectedHeapId) - b.getShallowSize(mySelectedHeapId); + return result == 0 ? compareNames(a, b) : result; + } + }).setRenderer(new ColoredTreeCellRenderer() { + @Override + public void customizeCellRenderer(@NotNull JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + if (value instanceof HeapNode) { + append(Integer.toString(((HeapNode)value).getShallowSize(mySelectedHeapId))); + } + setTextAlign(SwingConstants.RIGHT); + } + }) + ).addColumn( + new ColumnTreeBuilder.ColumnBuilder() + .setName("Retained Size") + .setPreferredWidth(120) + .setHeaderAlignment(SwingConstants.RIGHT) + .setInitialOrder(SortOrder.DESCENDING) + .setComparator(new Comparator<HeapNode>() { + @Override + public int compare(HeapNode a, HeapNode b) { + long result = a.getRetainedSize() - b.getRetainedSize(); + return result == 0 ? compareNames(a, b) : (result > 0 ? 1 : -1); + } + }) + .setRenderer(new ColoredTreeCellRenderer() { + @Override + public void customizeCellRenderer(@NotNull JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + if (value instanceof HeapNode) { + append(Long.toString(((HeapNode)value).getRetainedSize())); + } + setTextAlign(SwingConstants.RIGHT); + } + }) + ); + + //noinspection NullableProblems + builder.setTreeSorter(new ColumnTreeBuilder.TreeSorter<HeapNode>() { + @Override + public void sort(@NotNull Comparator<HeapNode> comparator, @NotNull SortOrder sortOrder) { + if (myComparator != comparator) { + myComparator = comparator; + + selectionModel.setSelectionLocked(true); + TreePath selectionPath = myTree.getSelectionPath(); + sortTree(myRoot); + myTreeModel.nodeStructureChanged(myRoot); + myTree.setSelectionPath(selectionPath); + myTree.scrollPathToVisible(selectionPath); + selectionModel.setSelectionLocked(false); + } + } + }); + + myColumnTree = builder.build(); + installTreeSpeedSearch(); + } + + @NotNull + public JComponent getComponent() { + return myColumnTree; + } + + private void installTreeSpeedSearch() { + new TreeSpeedSearch(myTree, new Convertor<TreePath, String>() { + @Override + public String convert(TreePath e) { + Object o = e.getLastPathComponent(); + if (o instanceof HeapNode) { + if (o instanceof HeapClassObjNode) { + return ((HeapClassObjNode)o).getSimpleName(); + } + else if (o instanceof HeapPackageNode) { + return ((HeapPackageNode)o).getFullName(); + } + } + return o.toString(); + } + }, true); + } + + private void sortTree(@NotNull HeapPackageNode parent) { + if (parent.isLeaf() || myComparator == null) { + return; + } + + List<HeapNode> children = parent.getChildren(); + Collections.sort(children, myComparator); + + for (HeapNode child : children) { + if (child instanceof HeapPackageNode) { + sortTree((HeapPackageNode)child); + } + } + } + + private static int compareNames(@NotNull HeapNode a, @NotNull HeapNode b) { + int comparisonResult = a.getSimpleName() + .compareToIgnoreCase(b.getSimpleName()); + if (comparisonResult == 0) { + return a.getFullName().compareToIgnoreCase(b.getFullName()); + } + return comparisonResult; + } + + private void restoreViewState(@NotNull final SelectionModel selectionModel) { + ClassObj classToSelect = selectionModel.getClassObj(); + TreeNode nodeToSelect = null; + if (classToSelect != null) { + nodeToSelect = findClassObjNode(classToSelect); + } + + sortTree(myRoot); + myTreeModel.nodeStructureChanged(myRoot); + final TreeNode targetNode = nodeToSelect; + + if (targetNode != null) { + // If the new heap has the selected class (from a previous heap), then select it and scroll to it. + myColumnTree.revalidate(); + final TreePath pathToSelect = new TreePath(myTreeModel.getPathToRoot(targetNode)); + myTree.setSelectionPath(pathToSelect); + + // This is kind of clunky, but the viewport doesn't know how big the tree is until it repaints. + // We need to do this because the contents of this tree has been more or less completely replaced. + // Unfortunately, calling repaint() only queues it, so we actually need an extra frame to select the node. + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + myTree.scrollPathToVisible(pathToSelect); + } + }); + } + else { + selectionModel.setClassObj(null); + myTree.scrollRowToVisible(0); + } + } + + @Nullable + private HeapClassObjNode findClassObjNode(@NotNull ClassObj targetClass) { + if (myDisplayMode == DisplayMode.LIST) { + for (int i = 0; i < myRoot.getChildCount(); ++i) { + TreeNode child = myRoot.getChildAt(i); + assert child instanceof HeapClassObjNode; + if (((HeapClassObjNode)child).getClassObj() == targetClass) { + return (HeapClassObjNode)child; + } + } + } + else if (myDisplayMode == DisplayMode.TREE) { + HeapPackageNode currentNode = myRoot; + + String[] packages = targetClass.getClassName().split("\\."); + assert packages.length > 0; + int currentPackageIndex = 0; + + while (currentPackageIndex < packages.length - 1) { + if (currentNode.getSubPackages().containsKey(packages[currentPackageIndex])) { + currentNode = currentNode.getSubPackages().get(packages[currentPackageIndex]); + ++currentPackageIndex; + } + else { + return null; + } + } + + for (int i = 0; i < currentNode.getChildCount(); ++i) { + TreeNode childTreeNode = currentNode.getChildAt(i); + assert childTreeNode instanceof HeapNode; + HeapNode child = (HeapNode)childTreeNode; + if (child instanceof HeapClassObjNode && ((HeapClassObjNode)child).getClassObj() == targetClass) { + return (HeapClassObjNode)child; + } + } + } + + return null; + } + + @Nullable + @Override + public Object getData(@NonNls String dataId) { + if (CommonDataKeys.NAVIGATABLE_ARRAY.is(dataId)) { + return getTargetFiles(); + } + else if (CommonDataKeys.PROJECT.is(dataId)) { + return myProject; + } + return null; + } + + @Nullable + private PsiFileAndLineNavigation[] getTargetFiles() { + TreePath path = myTree.getSelectionPath(); + if (path.getPathCount() < 2) { + return null; + } + + assert path.getLastPathComponent() instanceof HeapNode; + HeapNode node = (HeapNode)path.getLastPathComponent(); + if (node instanceof HeapClassObjNode) { + ClassObj classObj = ((HeapClassObjNode)node).getClassObj(); + String className = classObj.getClassName(); + + int arrayIndex = className.indexOf("["); + if (arrayIndex >= 0) { + className = className.substring(0, arrayIndex); + } + + return PsiFileAndLineNavigation.wrappersForClassName(myProject, className, 1); + } + + return null; + } + + private static class ListIndex implements SelectionModel.SelectionListener { + ArrayList<HeapClassObjNode> myClasses = new ArrayList<HeapClassObjNode>(); + private int myHeapId = -1; + + @Override + public void onHeapChanged(@NotNull Heap heap) { + if (myHeapId != heap.getId()) { + myHeapId = heap.getId(); + myClasses.clear(); + + // Find the union of the classObjs this heap has instances of, plus the classObjs themselves that are allocated on this heap. + HashSet<ClassObj> entriesSet = new HashSet<ClassObj>(heap.getClasses().size() + heap.getInstancesCount()); + for (ClassObj classObj : heap.getClasses()) { + entriesSet.add(classObj); + } + for (Instance instance : heap.getInstances()) { + entriesSet.add(instance.getClassObj()); + } + + for (ClassObj classObj : entriesSet) { + myClasses.add(new HeapClassObjNode(classObj, myHeapId)); + } + } + } + + @Override + public void onClassObjChanged(@Nullable ClassObj classObj) { + + } + + @Override + public void onInstanceChanged(@Nullable Instance instance) { + + } + + public void buildList(@NotNull HeapNode root) { + root.removeAllChildren(); + for (HeapClassObjNode heapClassObjNode : myClasses) { + heapClassObjNode.removeFromParent(); + root.add(heapClassObjNode); + } + } + } + + private class TreeIndex implements SelectionModel.SelectionListener { + private int myHeapId = -1; + + @Override + public void onHeapChanged(@NotNull Heap heap) { + // TODO save the expansion state + if (myDisplayMode == DisplayMode.TREE) { + assert myListIndex.myHeapId == heap.getId(); + buildTree(heap.getId()); + } + } + + @Override + public void onClassObjChanged(@Nullable ClassObj classObj) { + + } + + @Override + public void onInstanceChanged(@Nullable Instance instance) { + + } + + public void buildTree(int heapId) { + if (myHeapId != heapId) { + myHeapId = heapId; + myRoot.clear(); + + for (HeapClassObjNode heapClassObjNode : myListIndex.myClasses) { + myRoot.classifyClassObj(heapClassObjNode); + } + + myRoot.update(mySelectedHeapId); + } + + myRoot.buildTree(); + } + } + + private enum DisplayMode { + LIST("Class List View"), + TREE("Package Tree View"); + + @NotNull + private String myName; + + DisplayMode(@NotNull String name) { + myName = name; + } + + @Override + public String toString() { + return myName; + } + } +} diff --git a/android/src/com/android/tools/idea/editors/hprof/tables/InstanceReferenceTree.java b/android/src/com/android/tools/idea/editors/hprof/views/InstanceReferenceTreeView.java index b006e6dcb2d..31d80e13ba0 100644 --- a/android/src/com/android/tools/idea/editors/hprof/tables/InstanceReferenceTree.java +++ b/android/src/com/android/tools/idea/editors/hprof/views/InstanceReferenceTreeView.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.tools.idea.editors.hprof.tables; +package com.android.tools.idea.editors.hprof.views; import com.android.tools.idea.actions.EditMultipleSourcesAction; import com.android.tools.idea.actions.PsiFileAndLineNavigation; @@ -47,7 +47,9 @@ import java.awt.*; import java.util.*; import java.util.List; -public class InstanceReferenceTree implements DataProvider { +public class InstanceReferenceTreeView implements DataProvider { + public static final String TREE_NAME = "HprofInstanceReferenceTree"; + private static final int MAX_AUTO_EXPANSION_DEPTH = 5; private static final SimpleTextAttributes SOFT_REFERENCE_TEXT_ATTRIBUTE = new SimpleTextAttributes(SimpleTextAttributes.STYLE_ITALIC, XDebuggerUIConstants.VALUE_NAME_ATTRIBUTES.getFgColor()); @@ -64,7 +66,7 @@ public class InstanceReferenceTree implements DataProvider { private Instance myInstance; - public InstanceReferenceTree(@NotNull Project project, @NotNull SelectionModel selectionModel) { + public InstanceReferenceTreeView(@NotNull Project project, @NotNull SelectionModel selectionModel) { myProject = project; final TreeBuilder model = new TreeBuilder(null) { @@ -100,6 +102,7 @@ public class InstanceReferenceTree implements DataProvider { }); myTree = new Tree(model); + myTree.setName(TREE_NAME); myTree.setRootVisible(false); myTree.setShowsRootHandles(true); myTree.setLargeModel(true); diff --git a/android/src/com/android/tools/idea/editors/hprof/tables/InstancesTree.java b/android/src/com/android/tools/idea/editors/hprof/views/InstancesTreeView.java index cff7601d324..9c7e78fed6e 100644 --- a/android/src/com/android/tools/idea/editors/hprof/tables/InstancesTree.java +++ b/android/src/com/android/tools/idea/editors/hprof/views/InstancesTreeView.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.tools.idea.editors.hprof.tables; +package com.android.tools.idea.editors.hprof.views; import com.android.tools.idea.actions.EditMultipleSourcesAction; import com.android.tools.idea.actions.PsiFileAndLineNavigation; @@ -54,7 +54,9 @@ import java.awt.*; import java.util.*; import java.util.List; -public class InstancesTree implements DataProvider { +public class InstancesTreeView implements DataProvider { + public static final String TREE_NAME = "HprofInstancesTree"; + private static final int NODES_PER_EXPANSION = 100; @NotNull private Project myProject; @@ -69,7 +71,7 @@ public class InstancesTree implements DataProvider { @Nullable private Comparator<DebuggerTreeNodeImpl> myComparator; @NotNull private SortOrder mySortOrder = SortOrder.UNSORTED; - public InstancesTree(@NotNull Project project, @NotNull final SelectionModel selectionModel) { + public InstancesTreeView(@NotNull Project project, @NotNull final SelectionModel selectionModel) { myProject = project; myDebuggerTree = new DebuggerTree(project) { @@ -82,9 +84,10 @@ public class InstancesTree implements DataProvider { @Override public Object getData(@NonNls String dataId) { - return InstancesTree.this.getData(dataId); + return InstancesTreeView.this.getData(dataId); } }; + myDebuggerTree.getComponent().setName(TREE_NAME); myHeap = selectionModel.getHeap(); myDebugProcess = new DebugProcessEvents(project); diff --git a/android/src/com/android/tools/idea/editors/hprof/tables/SelectionModel.java b/android/src/com/android/tools/idea/editors/hprof/views/SelectionModel.java index afde1088ec8..5ad364ce3a6 100644 --- a/android/src/com/android/tools/idea/editors/hprof/tables/SelectionModel.java +++ b/android/src/com/android/tools/idea/editors/hprof/views/SelectionModel.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.tools.idea.editors.hprof.tables; +package com.android.tools.idea.editors.hprof.views; import com.android.tools.perflib.heap.ClassObj; import com.android.tools.perflib.heap.Heap; @@ -26,11 +26,11 @@ import java.util.EventListener; public class SelectionModel { public interface SelectionListener extends EventListener { - public void onHeapChanged(@NotNull Heap heap); + void onHeapChanged(@NotNull Heap heap); - public void onClassObjChanged(@Nullable ClassObj classObj); + void onClassObjChanged(@Nullable ClassObj classObj); - public void onInstanceChanged(@Nullable Instance instance); + void onInstanceChanged(@Nullable Instance instance); } private final EventDispatcher<SelectionListener> myDispatcher = EventDispatcher.create(SelectionListener.class); diff --git a/android/src/com/android/tools/idea/editors/hprof/views/nodedata/HeapClassObjNode.java b/android/src/com/android/tools/idea/editors/hprof/views/nodedata/HeapClassObjNode.java new file mode 100644 index 00000000000..de14c7f0d68 --- /dev/null +++ b/android/src/com/android/tools/idea/editors/hprof/views/nodedata/HeapClassObjNode.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tools.idea.editors.hprof.views.nodedata; + +import com.android.tools.perflib.heap.ClassObj; +import com.android.tools.perflib.heap.Instance; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.tree.TreeNode; +import java.util.Enumeration; +import java.util.List; + +public class HeapClassObjNode implements HeapNode { + @Nullable private HeapNode myParent; + @NotNull private ClassObj myClassObj; + private long myRetainedSize; + private String mySimpleName; + + public HeapClassObjNode(@NotNull ClassObj classObj, int heapId) { + myClassObj = classObj; + for (Instance instance : myClassObj.getHeapInstances(heapId)) { + myRetainedSize += instance.getTotalRetainedSize(); + } + + mySimpleName = myClassObj.getClassName(); + int index = mySimpleName.lastIndexOf('.'); + if (index >= 0 && index < mySimpleName.length() - 1) { + mySimpleName = mySimpleName.substring(index + 1, mySimpleName.length()); + } + } + + @NotNull + public ClassObj getClassObj() { + return myClassObj; + } + + @NotNull + @Override + public String getFullName() { + return getClassObj().getClassName(); + } + + @Override + @NotNull + public String getSimpleName() { + return mySimpleName; + } + + @Override + public int getTotalCount() { + return getClassObj().getInstanceCount(); + } + + @Override + public int getHeapInstancesCount(int heapId) { + return getClassObj().getHeapInstancesCount(heapId); + } + + @Override + public int getInstanceSize() { + return getClassObj().getInstanceSize(); + } + + @Override + public int getShallowSize(int heapId) { + return getClassObj().getShallowSize(heapId); + } + + @Override + public long getRetainedSize() { + return myRetainedSize; + } + + @Override + public void add(@NotNull HeapNode heapNode) { + throw new RuntimeException("Invalid operation on " + getClass().getSimpleName()); + } + + @NotNull + @Override + public List<HeapNode> getChildren() { + throw new RuntimeException("Invalid operation on " + getClass().getSimpleName()); + } + + @Override + public void removeAllChildren() { + throw new RuntimeException("Invalid operation on " + getClass().getSimpleName()); + } + + @Override + public TreeNode getChildAt(int childIndex) { + throw new RuntimeException("Invalid operation on " + getClass().getSimpleName()); + } + + @Override + public int getChildCount() { + return 0; + } + + @Override + public TreeNode getParent() { + return myParent; + } + + @Override + public int getIndex(TreeNode node) { + throw new RuntimeException("Invalid operation on " + getClass().getSimpleName()); + } + + @Override + public boolean getAllowsChildren() { + return false; + } + + @Override + public boolean isLeaf() { + return true; + } + + @Override + public Enumeration children() { + throw new RuntimeException("Invalid operation on " + getClass().getSimpleName()); + } + + @Override + public void removeFromParent() { + if (myParent != null) { + assert myParent instanceof HeapPackageNode; + ((HeapPackageNode)myParent).remove(this); + myParent = null; + } + } + + @Override + public void setParent(@Nullable HeapNode newParent) { + assert newParent == null || newParent instanceof HeapPackageNode; + myParent = newParent; + } +} diff --git a/android/src/com/android/tools/idea/editors/hprof/views/nodedata/HeapNode.java b/android/src/com/android/tools/idea/editors/hprof/views/nodedata/HeapNode.java new file mode 100644 index 00000000000..cec397069bc --- /dev/null +++ b/android/src/com/android/tools/idea/editors/hprof/views/nodedata/HeapNode.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tools.idea.editors.hprof.views.nodedata; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.tree.TreeNode; +import java.util.List; + +public interface HeapNode extends TreeNode { + @NotNull + String getFullName(); + + @NotNull + String getSimpleName(); + + int getTotalCount(); + + int getHeapInstancesCount(int heapId); + + int getInstanceSize(); + + int getShallowSize(int heapId); + + long getRetainedSize(); + + void add(@NotNull HeapNode heapNode); + + @NotNull + List<HeapNode> getChildren(); + + void removeAllChildren(); + + void removeFromParent(); + + void setParent(@Nullable HeapNode newParent); +} diff --git a/android/src/com/android/tools/idea/editors/hprof/views/nodedata/HeapPackageNode.java b/android/src/com/android/tools/idea/editors/hprof/views/nodedata/HeapPackageNode.java new file mode 100644 index 00000000000..30f8850dc59 --- /dev/null +++ b/android/src/com/android/tools/idea/editors/hprof/views/nodedata/HeapPackageNode.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tools.idea.editors.hprof.views.nodedata; + +import com.intellij.util.containers.HashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.tree.TreeNode; +import java.util.*; + +public class HeapPackageNode implements HeapNode { + @Nullable private HeapNode myParent; + @NotNull private List<HeapNode> myChildren = new ArrayList<HeapNode>(); + + @NotNull private HashMap<String, HeapPackageNode> mySubPackages = new HashMap<String, HeapPackageNode>(); + @NotNull private Set<HeapClassObjNode> myClasses = new HashSet<HeapClassObjNode>(); + + @SuppressWarnings("NullableProblems") @NotNull private String myFullPackageName; + @NotNull private String myPackageName; + + private int myTotalCount; + private int myHeapInstanceCount; + private int myShallowSize; + private long myRetainedSize; + + public HeapPackageNode(@Nullable HeapPackageNode parent, @NotNull String packageName) { + myPackageName = packageName; + setParent(parent); + } + + public void update(int currentHeapId) { + myRetainedSize = 0; + for (HeapPackageNode heapPackageNode : mySubPackages.values()) { + heapPackageNode.update(currentHeapId); + updateCounts(heapPackageNode, currentHeapId); + } + + for (HeapClassObjNode heapClassObjNode : myClasses) { + updateCounts(heapClassObjNode, currentHeapId); + } + } + + private void updateCounts(@NotNull HeapNode heapNode, int currentHeapId) { + myTotalCount += heapNode.getTotalCount(); + myHeapInstanceCount += heapNode.getHeapInstancesCount(currentHeapId); + myShallowSize += heapNode.getShallowSize(currentHeapId); + myRetainedSize += heapNode.getRetainedSize(); + } + + public void classifyClassObj(@NotNull HeapClassObjNode heapClassObjNode) { + String className = heapClassObjNode.getClassObj().getClassName(); + assert className.startsWith(myFullPackageName) && className.length() > myFullPackageName.length() + 1; // Mind the dot. + String remainder = myFullPackageName.isEmpty() ? className : className.substring(myFullPackageName.length() + 1); + + int dotIndex = remainder.indexOf('.'); + if (dotIndex > 0) { + assert remainder.length() > 0; + String subPackageName = remainder.substring(0, dotIndex); + HeapPackageNode heapPackageNode = mySubPackages.get(subPackageName); + if (heapPackageNode == null) { + heapPackageNode = new HeapPackageNode(this, subPackageName); + mySubPackages.put(subPackageName, heapPackageNode); + } + heapPackageNode.classifyClassObj(heapClassObjNode); + } + else { + myClasses.add(heapClassObjNode); + } + } + + public void clear() { + removeAllChildren(); + mySubPackages.clear(); + myClasses.clear(); + myTotalCount = 0; + myHeapInstanceCount = 0; + myShallowSize = 0; + myRetainedSize = 0; + } + + @NotNull + @Override + public String getFullName() { + return myFullPackageName; + } + + @NotNull + @Override + public String getSimpleName() { + return myPackageName; + } + + @Override + public int getTotalCount() { + return myTotalCount; + } + + @Override + public int getHeapInstancesCount(int heapId) { + return myHeapInstanceCount; + } + + @Override + public int getInstanceSize() { + return -1; + } + + @Override + public int getShallowSize(int heapId) { + return myShallowSize; + } + + @Override + public long getRetainedSize() { + return myRetainedSize; + } + + @Override + public void add(@NotNull HeapNode heapNode) { + heapNode.removeFromParent(); + heapNode.setParent(this); + myChildren.add(heapNode); + } + + @NotNull + @Override + public List<HeapNode> getChildren() { + return myChildren; + } + + @Override + public void removeAllChildren() { + for (HeapNode child : myChildren) { + child.setParent(null); + } + myChildren.clear(); + } + + @NotNull + public HashMap<String, HeapPackageNode> getSubPackages() { + return mySubPackages; + } + + public void buildTree() { + for (HeapPackageNode heapPackageNode : mySubPackages.values()) { + add(heapPackageNode); + heapPackageNode.buildTree(); + } + + for (HeapClassObjNode heapClassObjNode : myClasses) { + // TODO: Handle inner/anonymous classes. + add(heapClassObjNode); + } + } + + @Override + public TreeNode getChildAt(int childIndex) { + return myChildren.get(childIndex); + } + + @Override + public int getChildCount() { + return myChildren.size(); + } + + @Override + public TreeNode getParent() { + return myParent; + } + + @Override + public int getIndex(TreeNode node) { + assert node instanceof HeapNode; + return myChildren.indexOf(node); + } + + @Override + public boolean getAllowsChildren() { + return true; + } + + @Override + public boolean isLeaf() { + return myChildren.size() == 0; + } + + @Override + public Enumeration children() { + return Collections.enumeration(myChildren); + } + + public void remove(@NotNull HeapNode node) { + myChildren.remove(node); + } + + @Override + public void removeFromParent() { + if (myParent != null) { + assert myParent instanceof HeapPackageNode; + ((HeapPackageNode)myParent).remove(this); + setParent(null); + } + } + + @Override + public void setParent(@Nullable HeapNode newParent) { + assert newParent == null || newParent instanceof HeapPackageNode; + myParent = newParent; + myFullPackageName = myParent == null || myParent.getFullName().isEmpty() ? myPackageName : myParent.getFullName() + "." + myPackageName; + } +} diff --git a/android/src/com/android/tools/idea/editors/theme/NewStyleDialog.java b/android/src/com/android/tools/idea/editors/theme/NewStyleDialog.java index 0ecdeda5cfa..ab65ca3652d 100644 --- a/android/src/com/android/tools/idea/editors/theme/NewStyleDialog.java +++ b/android/src/com/android/tools/idea/editors/theme/NewStyleDialog.java @@ -83,7 +83,7 @@ public class NewStyleDialog extends DialogWrapper { final ImmutableList<ThemeEditorStyle> defaultThemes = ThemeEditorUtils.getDefaultThemes(themeResolver); ThemeEditorStyle defaultParent = null; if (defaultParentName != null) { - defaultParent = ResolutionUtils.getStyle(context.getConfiguration(), defaultParentName); + defaultParent = ResolutionUtils.getStyle(configuration, defaultParentName, null); } //noinspection GtkPreferredJComboBoxRenderer diff --git a/android/src/com/android/tools/idea/editors/theme/ResolutionUtils.java b/android/src/com/android/tools/idea/editors/theme/ResolutionUtils.java index 4136167e65c..0e1fb7fd076 100644 --- a/android/src/com/android/tools/idea/editors/theme/ResolutionUtils.java +++ b/android/src/com/android/tools/idea/editors/theme/ResolutionUtils.java @@ -91,12 +91,16 @@ public class ResolutionUtils { } /** + * Construct a ThemeEditorStyle instance for a theme with given name and source module, resolved in a given configuration. + * + * @param module source module of a theme, should be null only for framework or libraries themes. + * If you don't know source module of a theme, you should use {@link ThemeResolver#getTheme(String)} instead * @return returns ThemeEditorStyle for a qualifiedStyleName, if style doesn't exist will return null */ @Nullable - public static ThemeEditorStyle getStyle(@NotNull Configuration configuration, @NotNull final String qualifiedStyleName) { + public static ThemeEditorStyle getStyle(@NotNull Configuration configuration, @NotNull final String qualifiedStyleName, @Nullable Module module) { final StyleResourceValue style = getStyleResourceValue(configuration, qualifiedStyleName); - return style == null ? null : new ThemeEditorStyle(configuration, style); + return style == null ? null : new ThemeEditorStyle(configuration, style, module); } @Nullable diff --git a/android/src/com/android/tools/idea/editors/theme/ResourcesCompletionProvider.java b/android/src/com/android/tools/idea/editors/theme/ResourcesCompletionProvider.java index e1b0c48a21a..ef71c2e9e49 100644 --- a/android/src/com/android/tools/idea/editors/theme/ResourcesCompletionProvider.java +++ b/android/src/com/android/tools/idea/editors/theme/ResourcesCompletionProvider.java @@ -43,7 +43,7 @@ class ResourcesCompletionProvider implements AttributeReferenceRendererEditor.Co ThemeEditorStyle selectedStyle = value.getSourceStyle(); AttributeDefinition attrDefinition = - ResolutionUtils.getAttributeDefinition(selectedStyle.getConfiguration(), value.getItemResourceValue()); + ResolutionUtils.getAttributeDefinition(selectedStyle.getConfiguration(), value.getSelectedValue()); if (attrDefinition == null) { return Collections.emptyList(); } diff --git a/android/src/com/android/tools/idea/editors/theme/ThemeEditorComponent.java b/android/src/com/android/tools/idea/editors/theme/ThemeEditorComponent.java index fe39805f346..127c5d942d0 100644 --- a/android/src/com/android/tools/idea/editors/theme/ThemeEditorComponent.java +++ b/android/src/com/android/tools/idea/editors/theme/ThemeEditorComponent.java @@ -18,34 +18,13 @@ package com.android.tools.idea.editors.theme; import com.android.SdkConstants; import com.android.ide.common.rendering.api.ItemResourceValue; import com.android.ide.common.resources.ResourceResolver; -import com.android.tools.idea.configurations.Configuration; -import com.android.tools.idea.configurations.ConfigurationListener; -import com.android.tools.idea.configurations.ConfigurationManager; -import com.android.tools.idea.configurations.DeviceMenuAction; -import com.android.tools.idea.configurations.LocaleMenuAction; -import com.android.tools.idea.configurations.OrientationMenuAction; -import com.android.tools.idea.configurations.TargetMenuAction; -import com.android.tools.idea.configurations.ThemeSelectionDialog; -import com.android.tools.idea.editors.theme.attributes.AttributesGrouper; -import com.android.tools.idea.editors.theme.attributes.AttributesModelColorPaletteModel; -import com.android.tools.idea.editors.theme.attributes.AttributesTableModel; -import com.android.tools.idea.editors.theme.attributes.ShowJavadocAction; -import com.android.tools.idea.editors.theme.attributes.TableLabel; -import com.android.tools.idea.editors.theme.attributes.editors.AttributeReferenceRendererEditor; -import com.android.tools.idea.editors.theme.attributes.editors.BooleanRendererEditor; -import com.android.tools.idea.editors.theme.attributes.editors.ColorRendererEditor; -import com.android.tools.idea.editors.theme.attributes.editors.DelegatingCellEditor; -import com.android.tools.idea.editors.theme.attributes.editors.DelegatingCellRenderer; -import com.android.tools.idea.editors.theme.attributes.editors.DrawableRendererEditor; -import com.android.tools.idea.editors.theme.attributes.editors.EnumRendererEditor; -import com.android.tools.idea.editors.theme.attributes.editors.FlagRendererEditor; -import com.android.tools.idea.editors.theme.attributes.editors.IntegerRenderer; -import com.android.tools.idea.editors.theme.attributes.editors.ParentRendererEditor; -import com.android.tools.idea.editors.theme.ui.ResourceComponent; -import com.android.tools.idea.editors.theme.attributes.editors.StyleListCellRenderer; +import com.android.tools.idea.configurations.*; +import com.android.tools.idea.editors.theme.attributes.*; +import com.android.tools.idea.editors.theme.attributes.editors.*; import com.android.tools.idea.editors.theme.datamodels.EditedStyleItem; import com.android.tools.idea.editors.theme.datamodels.ThemeEditorStyle; import com.android.tools.idea.editors.theme.preview.AndroidThemePreviewPanel; +import com.android.tools.idea.editors.theme.ui.ResourceComponent; import com.android.tools.idea.rendering.ResourceNotificationManager; import com.android.tools.idea.rendering.ResourceNotificationManager.ResourceChangeListener; import com.google.common.collect.ImmutableList; @@ -55,14 +34,12 @@ import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.ActionToolbar; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.actionSystem.IdeActions; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.refactoring.rename.RenameDialog; import com.intellij.ui.JBColor; @@ -74,20 +51,12 @@ import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.DefaultComboBoxModel; -import javax.swing.JComboBox; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.RowFilter; +import javax.swing.*; import javax.swing.plaf.PanelUI; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableRowSorter; -import java.awt.Color; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.Font; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; @@ -156,16 +125,9 @@ public class ThemeEditorComponent extends Splitter { final Module selectedModule = myModuleComboModel.getSelected(); assert selectedModule != null; - final AndroidFacet facet = AndroidFacet.getInstance(selectedModule); - assert facet != null : "moduleComboModel must contain only Android modules"; - - ConfigurationManager configurationManager = facet.getConfigurationManager(); - final VirtualFile projectFile = project.getProjectFile(); - assert projectFile != null; - - final Configuration configuration = configurationManager.getConfiguration(projectFile); + final Configuration configuration = ThemeEditorUtils.getConfigurationForModule(selectedModule); - myThemeEditorContext = new ThemeEditorContext(configuration, selectedModule); + myThemeEditorContext = new ThemeEditorContext(configuration); myThemeEditorContext.addConfigurationListener(new ConfigurationListener() { @Override public boolean changed(int flags) { @@ -345,10 +307,10 @@ public class ThemeEditorComponent extends Splitter { // Adds the Device selection button DefaultActionGroup group = new DefaultActionGroup(); - group.add(new DeviceMenuAction(myPreviewPanel)); - group.add(new TargetMenuAction(myPreviewPanel)); - group.add(new LocaleMenuAction(myPreviewPanel)); - group.add(new OrientationMenuAction(myPreviewPanel)); + group.add(new OrientationMenuAction(myPreviewPanel, false)); + group.add(new DeviceMenuAction(myPreviewPanel, false)); + group.add(new TargetMenuAction(myPreviewPanel, true, false)); + group.add(new LocaleMenuAction(myPreviewPanel, false)); ActionToolbar actionToolbar = actionManager.createActionToolbar("ThemeToolbar", group, true); actionToolbar.setLayoutPolicy(ActionToolbar.WRAP_LAYOUT_POLICY); JPanel myConfigToolbar = myPanel.getConfigToolbar(); @@ -412,8 +374,8 @@ public class ThemeEditorComponent extends Splitter { return; } ResourceNotificationManager manager = ResourceNotificationManager.getInstance(myThemeEditorContext.getProject()); - AndroidFacet facet = AndroidFacet.getInstance(myThemeEditorContext.getCurrentThemeModule()); - assert facet != null : myThemeEditorContext.getCurrentThemeModule().getName() + " module doesn't have an AndroidFacet"; + AndroidFacet facet = AndroidFacet.getInstance(myThemeEditorContext.getCurrentContextModule()); + assert facet != null : myThemeEditorContext.getCurrentContextModule().getName() + " module doesn't have an AndroidFacet"; manager.addListener(myResourceChangeListener, facet, null, null); myIsSubscribedResourceNotification = true; } @@ -424,8 +386,8 @@ public class ThemeEditorComponent extends Splitter { private void unsubscribeResourceNotification() { if (myIsSubscribedResourceNotification) { ResourceNotificationManager manager = ResourceNotificationManager.getInstance(myThemeEditorContext.getProject()); - AndroidFacet facet = AndroidFacet.getInstance(myThemeEditorContext.getCurrentThemeModule()); - assert facet != null : myThemeEditorContext.getCurrentThemeModule().getName() + " module doesn't have an AndroidFacet"; + AndroidFacet facet = AndroidFacet.getInstance(myThemeEditorContext.getCurrentContextModule()); + assert facet != null : myThemeEditorContext.getCurrentContextModule().getName() + " module doesn't have an AndroidFacet"; manager.removeListener(myResourceChangeListener, facet, null, null); myIsSubscribedResourceNotification = false; } @@ -505,11 +467,9 @@ public class ThemeEditorComponent extends Splitter { if (renameDialog.isOK()) { String newName = renameDialog.getNewName(); String newQualifiedName = selectedTheme.getQualifiedName().replace(selectedTheme.getName(), newName); - AndroidFacet facet = AndroidFacet.getInstance(myThemeEditorContext.getCurrentThemeModule()); - if (facet != null) { - facet.refreshResources(); - } - reload(newQualifiedName); + // We don't need to call reload here, because myResourceChangeListener will take care of it + myThemeName = newQualifiedName; + mySubStyleName = null; return true; } return false; @@ -522,7 +482,7 @@ public class ThemeEditorComponent extends Splitter { return; } - ThemeEditorStyle parent = getUsedStyle().getParent(); + ThemeEditorStyle parent = getUsedStyle().getParent(myThemeEditorContext.getThemeResolver()); assert parent != null; // TODO: This seems like it could be confusing for users, we might want to differentiate parent navigation depending if it's @@ -541,7 +501,7 @@ public class ThemeEditorComponent extends Splitter { if (myThemeName == null) { return null; } - return ResolutionUtils.getStyle(myThemeEditorContext.getConfiguration(), myThemeName); + return myThemeEditorContext.getThemeResolver().getTheme(myThemeName); } @Nullable @@ -558,7 +518,7 @@ public class ThemeEditorComponent extends Splitter { if (mySubStyleName == null) { return null; } - return ResolutionUtils.getStyle(myThemeEditorContext.getConfiguration(), mySubStyleName); + return myThemeEditorContext.getThemeResolver().getTheme(mySubStyleName); } private boolean isSubStyleSelected() { @@ -660,7 +620,7 @@ public class ThemeEditorComponent extends Splitter { unsubscribeResourceNotification(); initializeModulesCombo(defaultModuleName); - myThemeEditorContext.setCurrentThemeModule(getSelectedModule()); + myThemeEditorContext.setCurrentContextModule(getSelectedModule()); // Subscribes to ResourceNotificationManager with new facet subscribeResourceNotification(); @@ -688,6 +648,7 @@ public class ThemeEditorComponent extends Splitter { return; } + myThemeEditorContext.setSelectedStyleSourceModule(selectedTheme.getSourceModule()); myPanel.setSubstyleName(mySubStyleName); myPanel.getBackButton().setVisible(mySubStyleName != null); final Configuration configuration = myThemeEditorContext.getConfiguration(); @@ -700,12 +661,7 @@ public class ThemeEditorComponent extends Splitter { myModel.addThemePropertyChangedListener(new AttributesTableModel.ThemePropertyChangedListener() { @Override public void attributeChangedOnReadOnlyTheme(final EditedStyleItem attribute, final String newValue) { - ApplicationManager.getApplication().invokeLater(new Runnable() { - @Override - public void run() { - createNewThemeWithAttributeValue(attribute, newValue); - } - }); + createNewThemeWithAttributeValue(attribute, newValue); } }); diff --git a/android/src/com/android/tools/idea/editors/theme/ThemeEditorContext.java b/android/src/com/android/tools/idea/editors/theme/ThemeEditorContext.java index 27a9864b794..ceaec80ecb2 100644 --- a/android/src/com/android/tools/idea/editors/theme/ThemeEditorContext.java +++ b/android/src/com/android/tools/idea/editors/theme/ThemeEditorContext.java @@ -18,10 +18,9 @@ package com.android.tools.idea.editors.theme; import com.android.ide.common.resources.ResourceResolver; import com.android.tools.idea.configurations.Configuration; import com.android.tools.idea.configurations.ConfigurationListener; +import com.android.tools.idea.editors.theme.datamodels.ThemeEditorStyle; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -47,13 +46,10 @@ public class ThemeEditorContext { @SuppressWarnings("NullableProblems") private @NotNull Configuration myConfiguration; - // Right now Module instance can be acquired from the Configuration, so, this field is - // extraneous, however, later (after adding proper multiple modules support), myModule - // would point to source module of currently selected theme which would be used for, - // e.g., resolution of resources available as a value for attribute in currently - // selected theme. Configuration field would point to one that is used for as a - // rendering context. - private @NotNull Module myCurrentThemeModule; + /** + * Stores {@link ThemeEditorStyle#mySourceModule} of a ThemeEditorStyle currently being edited + */ + private @Nullable Module mySelectedStyleSourceModule; // Field is initialized in method called from constructor which checker doesn't see, warning could be ignored @SuppressWarnings("NullableProblems") @@ -62,8 +58,7 @@ public class ThemeEditorContext { private final List<ChangeListener> myChangeListeners = new ArrayList<ChangeListener>(); private final List<ConfigurationListener> myConfigurationListeners = new ArrayList<ConfigurationListener>(); - public ThemeEditorContext(@NotNull Configuration configuration, @NotNull Module module) { - myCurrentThemeModule = module; + public ThemeEditorContext(@NotNull Configuration configuration) { setConfiguration(configuration); } @@ -81,24 +76,31 @@ public class ThemeEditorContext { } @NotNull - public Module getCurrentThemeModule() { - return myCurrentThemeModule; + public Module getCurrentContextModule() { + return myConfiguration.getModule(); } - public void setCurrentThemeModule(final @NotNull Module module) { - myCurrentThemeModule = module; - - AndroidFacet facet = AndroidFacet.getInstance(module); - assert facet != null; + public void setSelectedStyleSourceModule(@Nullable Module module) { + mySelectedStyleSourceModule = module; + } - VirtualFile projectFile = module.getProject().getProjectFile(); - assert projectFile != null; + /** + * Function for acquiring Module that should be used every time resource resolving + * for possible values is needed. + */ + @NotNull + public Module getModuleForResources() { + if (mySelectedStyleSourceModule != null) { + // If we have a source module, we want to use it for resolving possible values + return mySelectedStyleSourceModule; + } + // Otherwise, it should be a library theme or framework theme, in which case we will create + // a new theme in the current rendering context, which we are returning here; + return myConfiguration.getModule(); + } - // Using the project virtual file to set up configuration for the theme editor - // That fact is hard-coded in computeBestDevice() method in Configuration.java - // BEWARE if attempting to modify to use a different virtual file - Configuration configuration = facet.getConfigurationManager().getConfiguration(projectFile); - setConfiguration(configuration); + public void setCurrentContextModule(final @NotNull Module module) { + setConfiguration(ThemeEditorUtils.getConfigurationForModule(module)); } @Nullable @@ -108,7 +110,7 @@ public class ThemeEditorContext { @NotNull public Project getProject() { - return myCurrentThemeModule.getProject(); + return myConfiguration.getModule().getProject(); } public void addConfigurationListener(@NotNull ConfigurationListener configurationListener) { diff --git a/android/src/com/android/tools/idea/editors/theme/ThemeEditorProvider.java b/android/src/com/android/tools/idea/editors/theme/ThemeEditorProvider.java index 55fb9920b36..4c3d6957239 100644 --- a/android/src/com/android/tools/idea/editors/theme/ThemeEditorProvider.java +++ b/android/src/com/android/tools/idea/editors/theme/ThemeEditorProvider.java @@ -37,7 +37,7 @@ import static com.android.SdkConstants.TAG_RESOURCES; import static com.android.SdkConstants.TAG_STYLE; public class ThemeEditorProvider implements FileEditorProvider, DumbAware { - public final static boolean THEME_EDITOR_ENABLE = SystemProperties.getBooleanProperty("enable.theme.editor", false); + public final static boolean THEME_EDITOR_ENABLE = true; private final static String THEME_NAME = "theme-name"; private final static String STYLE_NAME = "style-name"; diff --git a/android/src/com/android/tools/idea/editors/theme/ThemeEditorUtils.java b/android/src/com/android/tools/idea/editors/theme/ThemeEditorUtils.java index 6a6ade12674..9949bcf3e05 100644 --- a/android/src/com/android/tools/idea/editors/theme/ThemeEditorUtils.java +++ b/android/src/com/android/tools/idea/editors/theme/ThemeEditorUtils.java @@ -27,6 +27,7 @@ import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; import com.android.tools.idea.actions.OverrideResourceAction; import com.android.tools.idea.configurations.Configuration; +import com.android.tools.idea.configurations.ConfigurationManager; import com.android.tools.idea.editors.theme.datamodels.EditedStyleItem; import com.android.tools.idea.editors.theme.datamodels.ThemeEditorStyle; import com.android.tools.idea.gradle.IdeaAndroidProject; @@ -268,9 +269,7 @@ public class ThemeEditorUtils { @NotNull public static ImmutableList<ThemeEditorStyle> getDefaultThemes(@NotNull ThemeResolver themeResolver) { - Collection<ThemeEditorStyle> editableThemes = themeResolver.getLocalThemes(); - Collection<ThemeEditorStyle> readOnlyLibThemes = new HashSet<ThemeEditorStyle>(themeResolver.getProjectThemes()); - readOnlyLibThemes.removeAll(editableThemes); + Collection<ThemeEditorStyle> readOnlyLibThemes = themeResolver.getExternalLibraryThemes(); Collection<ThemeEditorStyle> foundThemes = new HashSet<ThemeEditorStyle>(); foundThemes.addAll(findThemes(readOnlyLibThemes, DEFAULT_THEMES)); @@ -358,7 +357,7 @@ public class ThemeEditorUtils { return null; } - int minModuleApi = getMinApiLevel(myThemeEditorContext.getCurrentThemeModule()); + int minModuleApi = getMinApiLevel(myThemeEditorContext.getCurrentContextModule()); int themeParentApiLevel = getOriginalApiLevel(dialog.getStyleParentName(), myThemeEditorContext.getProject()); int newAttributeApiLevel = getOriginalApiLevel(newAttributeName, myThemeEditorContext.getProject()); int newValueApiLevel = getOriginalApiLevel(newAttributeValue, myThemeEditorContext.getProject()); @@ -382,7 +381,7 @@ public class ThemeEditorUtils { protected void run(@NotNull Result<Boolean> result) { CommandProcessor.getInstance().markCurrentCommandAsGlobal(myThemeEditorContext.getProject()); result.setResult(AndroidResourceUtil. - createValueResource(myThemeEditorContext.getCurrentThemeModule(), dialog.getStyleName(), + createValueResource(myThemeEditorContext.getCurrentContextModule(), dialog.getStyleName(), ResourceType.STYLE, fileName, dirNames, new Processor<ResourceElement>() { @Override public boolean process(ResourceElement element) { @@ -493,6 +492,23 @@ public class ThemeEditorUtils { return folders; } + @NotNull + public static Configuration getConfigurationForModule(@NotNull Module module) { + Project project = module.getProject(); + final AndroidFacet facet = AndroidFacet.getInstance(module); + assert facet != null : "moduleComboModel must contain only Android modules"; + + ConfigurationManager configurationManager = facet.getConfigurationManager(); + + // Using the project virtual file to set up configuration for the theme editor + // That fact is hard-coded in computeBestDevice() method in Configuration.java + // BEWARE if attempting to modify to use a different virtual file + final VirtualFile projectFile = project.getProjectFile(); + assert projectFile != null; + + return configurationManager.getConfiguration(projectFile); + } + /** * Interface to visit all the available {@link LocalResourceRepository} */ diff --git a/android/src/com/android/tools/idea/editors/theme/ThemeResolver.java b/android/src/com/android/tools/idea/editors/theme/ThemeResolver.java index fabea3dc6e9..25898225b4e 100644 --- a/android/src/com/android/tools/idea/editors/theme/ThemeResolver.java +++ b/android/src/com/android/tools/idea/editors/theme/ThemeResolver.java @@ -9,185 +9,171 @@ import com.android.tools.idea.configurations.Configuration; import com.android.tools.idea.editors.theme.datamodels.ThemeEditorStyle; import com.android.tools.idea.rendering.AppResourceRepository; import com.android.tools.idea.rendering.LocalResourceRepository; -import com.android.tools.idea.rendering.ProjectResourceRepository; -import com.google.common.collect.ForwardingQueue; +import com.android.tools.idea.rendering.ModuleResourceRepository; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.util.Pair; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.util.AndroidUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Queue; -import java.util.Set; import static com.android.ide.common.resources.ResourceResolver.THEME_NAME; import static com.android.ide.common.resources.ResourceResolver.THEME_NAME_DOT; /** * Class that provides methods to resolve themes for a given configuration. - * - * TODO(ddrone): get rid of this class - * @deprecated this class is supposed to be replaced by ProjectThemeResolver */ -@Deprecated public class ThemeResolver { - @SuppressWarnings("ConstantNamingConvention") - private static final Logger LOG = Logger.getInstance(ThemeResolver.class); - - // Order is important, we want project themes first. - private final Set<String> myThemeNames = Sets.newHashSet(); - private final List<ThemeEditorStyle> myAllThemes; - private final List<ThemeEditorStyle> myFrameworkThemes; - private final List<ThemeEditorStyle> myProjectThemes; - private final List<ThemeEditorStyle> myProjectLocalThemes; + private final Map<String, ThemeEditorStyle> myThemeByName = Maps.newHashMap(); + private final ImmutableList<ThemeEditorStyle> myFrameworkThemes; + private final ImmutableList<ThemeEditorStyle> myLocalThemes; + private final ImmutableList<ThemeEditorStyle> myExternalLibraryThemes; + private final Configuration myConfiguration; + private final ResourceResolver myResolver; public ThemeResolver(@NotNull Configuration configuration) { myConfiguration = configuration; + myResolver = configuration.getResourceResolver(); - final Queue<StyleResourceValue> localThemes = new LinkedList<StyleResourceValue>(getProjectThemesNoLibraries(configuration)); - // If there are no libraries, resolvedThemes will be the same as localThemes. - final Queue<StyleResourceValue> resolvedThemes = new LinkedList<StyleResourceValue>(getProjectThemes(configuration)); - final Queue<StyleResourceValue> frameworkThemes = new LinkedList<StyleResourceValue>(getFrameworkThemes(configuration)); - myProjectLocalThemes = Lists.newArrayListWithCapacity(resolvedThemes.size()); - // We expect every local theme to have 1 parent. - myProjectThemes = Lists.newArrayListWithExpectedSize(resolvedThemes.size() * 2); - myFrameworkThemes = Lists.newArrayListWithCapacity(frameworkThemes.size()); - myAllThemes = Lists.newArrayListWithExpectedSize(resolvedThemes.size() * 2 + frameworkThemes.size()); - - ResourceResolver resolver = configuration.getResourceResolver(); - if (resolver == null) { - LOG.error("Unable to get ResourceResolver."); - return; + if (myResolver == null) { + throw new IllegalArgumentException("Acquired ResourceResolver is null, not an Android module?"); } - /* - Process all the available themes and their parents. We process them in the following order: - - Local themes - - Resolved themes (this include the parent themes that result from the checking the local themes and their parents) - - Framework themes - - The order will ensure that we keep the attribute value that is lower in the hierarchy (first local, then any of the parents in order - and lastly the framework themes). - */ - Queue<StyleResourceValue> pendingThemes = new ForwardingQueue<StyleResourceValue>() { - @Override - protected Queue<StyleResourceValue> delegate() { - if (!localThemes.isEmpty()) { - return localThemes; - } - else if (!resolvedThemes.isEmpty()) { - return resolvedThemes; - } - else { - return frameworkThemes; - } - } - }; - while (!pendingThemes.isEmpty()) { - boolean isLocalTheme = !localThemes.isEmpty(); - boolean isProjectDependency = isLocalTheme || !resolvedThemes.isEmpty(); - StyleResourceValue style = pendingThemes.remove(); - String styleQualifiedName = ResolutionUtils.getQualifiedStyleName(style); - - if (myThemeNames.contains(styleQualifiedName)) { - continue; - } - myThemeNames.add(styleQualifiedName); - ThemeEditorStyle resolvedStyle = ResolutionUtils.getStyle(configuration, styleQualifiedName); + myFrameworkThemes = fillThemeResolverFromStyles(resolveFrameworkThemes()); - if (resolvedStyle == null) { - continue; + final ImmutableList.Builder<ThemeEditorStyle> localThemes = ImmutableList.builder(); + for (Pair<StyleResourceValue, Module> pair : resolveLocallyDefinedModuleThemes()) { + final ThemeEditorStyle theme = constructThemeFromResourceValue(pair.getFirst(), pair.getSecond()); + if (theme != null) { + localThemes.add(theme); } + } + myLocalThemes = localThemes.build(); - myAllThemes.add(resolvedStyle); - if (isProjectDependency) { - myProjectThemes.add(resolvedStyle); + // resolveNonFrameworkThemes() returns all themes available from the current module, including library themes. + // Because all local themes would be added at previous step to myLocalThemes, they'll be ignored + // at this step, and all that we've got here is library themes. + myExternalLibraryThemes = fillThemeResolverFromStyles(resolveNonFrameworkThemes()); + } - StyleResourceValue parent = resolver.getParent(style); - if (parent != null) { - resolvedThemes.add(parent); - } - } + /** + * Create a ThemeEditorStyle instance stored in ThemeResolver, which can be added to one of theme lists. + * @return null if theme with this name was already added or resolution has failed + */ + @Nullable + private ThemeEditorStyle constructThemeFromResourceValue(@NotNull StyleResourceValue value, @Nullable Module sourceModule) { + final String name = ResolutionUtils.getQualifiedStyleName(value); - if (isLocalTheme) { - myProjectLocalThemes.add(resolvedStyle); - } - else { - myFrameworkThemes.add(resolvedStyle); + if (myThemeByName.containsKey(name)) { + return null; + } + + final ThemeEditorStyle theme = ResolutionUtils.getStyle(myConfiguration, name, sourceModule); + if (theme != null) { + myThemeByName.put(name, theme); + } + + return theme; + } + + private ImmutableList<ThemeEditorStyle> fillThemeResolverFromStyles(@NotNull List<StyleResourceValue> source) { + ImmutableList.Builder<ThemeEditorStyle> builder = ImmutableList.builder(); + + for (StyleResourceValue value : source) { + ThemeEditorStyle theme = constructThemeFromResourceValue(value, null); + if (theme != null) { + builder.add(theme); } } + + return builder.build(); } @NotNull - private static List<StyleResourceValue> getFrameworkThemes(@NotNull Configuration myConfiguration) { + private List<StyleResourceValue> resolveFrameworkThemes() { ResourceRepository repository = myConfiguration.getFrameworkResources(); if (repository == null) { return Collections.emptyList(); } - Map<ResourceType, Map<String, ResourceValue>> resources = repository.getConfiguredResources(myConfiguration.getFullConfig()); - return getThemes(myConfiguration, resources, true /*isFramework*/); + return getThemes(repository.getConfiguredResources(myConfiguration.getFullConfig()).get(ResourceType.STYLE), true /*isFramework*/); } + /** + * Resolve all non-framework themes available from module of passed Configuration + */ @NotNull - private static List<StyleResourceValue> getProjectThemes(@NotNull Configuration myConfiguration) { + private List<StyleResourceValue> resolveNonFrameworkThemes() { LocalResourceRepository repository = AppResourceRepository.getAppResources(myConfiguration.getModule(), true); if (repository == null) { return Collections.emptyList(); } - Map<ResourceType, Map<String, ResourceValue>> resources = repository.getConfiguredResources(myConfiguration.getFullConfig()); - return getThemes(myConfiguration, resources, false /*isFramework*/); + return getThemes(repository.getConfiguredResources(ResourceType.STYLE, myConfiguration.getFullConfig()), false /*isFramework*/); } + /** + * Resolve all themes available from passed Configuration's source module and its dependencies which are defined + * in the current project (doesn't include themes available from libraries) + */ @NotNull - private static List<StyleResourceValue> getProjectThemesNoLibraries(@NotNull Configuration myConfiguration) { - LocalResourceRepository repository = ProjectResourceRepository.getProjectResources(myConfiguration.getModule(), true); + private List<Pair<StyleResourceValue, Module>> resolveLocallyDefinedModuleThemes() { + final Module module = myConfiguration.getModule(); + final List<Pair<StyleResourceValue, Module>> result = Lists.newArrayList(); + + fillModuleResources(module, ModuleResourceRepository.getModuleResources(module, true), result); + + final List<AndroidFacet> allAndroidDependencies = AndroidUtils.getAllAndroidDependencies(module, false); + for (AndroidFacet facet : allAndroidDependencies) { + fillModuleResources(facet.getModule(), facet.getModuleResources(true), result); + } + + return result; + } + + private void fillModuleResources(@NotNull Module module, + @Nullable LocalResourceRepository repository, + @NotNull List<Pair<StyleResourceValue, Module>> sink) { if (repository == null) { - return Collections.emptyList(); + return; } - Map<ResourceType, Map<String, ResourceValue>> resources = repository.getConfiguredResources(myConfiguration.getFullConfig()); - return getThemes(myConfiguration, resources, false /*isFramework*/); + for (StyleResourceValue value : getThemes(repository.getConfiguredResources(ResourceType.STYLE, myConfiguration.getFullConfig()), false)) { + sink.add(Pair.create(value, module)); + } } @NotNull - private static List<StyleResourceValue> getThemes(@NotNull Configuration configuration, - @NotNull Map<ResourceType, Map<String, ResourceValue>> resources, - boolean isFramework) { - // get the styles. - Map<String, ResourceValue> styles = resources.get(ResourceType.STYLE); - + private List<StyleResourceValue> getThemes(@NotNull Map<String, ResourceValue> styles, + boolean isFramework) { // Collect the themes out of all the styles. Collection<ResourceValue> values = styles.values(); List<StyleResourceValue> themes = new ArrayList<StyleResourceValue>(values.size()); if (!isFramework) { - // Try a little harder to see if the user has themes that don't have the normal naming convention - ResourceResolver resolver = configuration.getResourceResolver(); - if (resolver != null) { - Map<ResourceValue, Boolean> cache = Maps.newHashMapWithExpectedSize(values.size()); - for (ResourceValue value : values) { - if (value instanceof StyleResourceValue) { - StyleResourceValue styleValue = (StyleResourceValue)value; - if (resolver.isTheme(styleValue, cache)) { - themes.add(styleValue); - } + Map<ResourceValue, Boolean> cache = Maps.newHashMapWithExpectedSize(values.size()); + for (ResourceValue value : values) { + if (value instanceof StyleResourceValue) { + StyleResourceValue styleValue = (StyleResourceValue)value; + if (myResolver.isTheme(styleValue, cache)) { + themes.add(styleValue); } } - return themes; } + return themes; } - // For the framework (and projects if resolver can't be computed) the computation is easier + // For the framework themes the computation is easier for (ResourceValue value : values) { String name = value.getName(); if (name.startsWith(THEME_NAME_DOT) || name.equals(THEME_NAME)) { @@ -199,39 +185,34 @@ public class ThemeResolver { @Nullable public ThemeEditorStyle getTheme(@NotNull String themeName) { - if (myThemeNames.contains(themeName)) { - return ResolutionUtils.getStyle(myConfiguration, themeName); - } - - return null; + return myThemeByName.get(themeName); } /** - * Returns the list of themes declared by the project. + * Returns the list of themes available from the module passed Configuration comes from and all its dependencies. */ @NotNull - public Collection<ThemeEditorStyle> getLocalThemes() { - return Collections.unmodifiableList(myProjectLocalThemes); + public ImmutableList<ThemeEditorStyle> getLocalThemes() { + return myLocalThemes; } /** - * Returns the list of themes declared by the project and its dependencies. + * Returns the list of themes that come from external libraries (e.g. AppCompat) */ @NotNull - public Collection<ThemeEditorStyle> getProjectThemes() { - return Collections.unmodifiableList(myProjectThemes); + public ImmutableList<ThemeEditorStyle> getExternalLibraryThemes() { + return myExternalLibraryThemes; } /** - * Returns the list of themes declared by the project and its dependencies. + * Returns the list of available framework themes. */ @NotNull - public Collection<ThemeEditorStyle> getFrameworkThemes() { - return Collections.unmodifiableList(myFrameworkThemes); + public ImmutableList<ThemeEditorStyle> getFrameworkThemes() { + return myFrameworkThemes; } - @NotNull - Collection<ThemeEditorStyle> getThemes() { - return Collections.unmodifiableCollection(myAllThemes); + public int getThemesCount() { + return myFrameworkThemes.size() + myExternalLibraryThemes.size() + myLocalThemes.size(); } } diff --git a/android/src/com/android/tools/idea/editors/theme/attributes/AttributesModelColorPaletteModel.java b/android/src/com/android/tools/idea/editors/theme/attributes/AttributesModelColorPaletteModel.java index 354adc4c0f6..a73a1426e81 100644 --- a/android/src/com/android/tools/idea/editors/theme/attributes/AttributesModelColorPaletteModel.java +++ b/android/src/com/android/tools/idea/editors/theme/attributes/AttributesModelColorPaletteModel.java @@ -92,7 +92,7 @@ public class AttributesModelColorPaletteModel implements ColorPalette.ColorPalet } EditedStyleItem item = (EditedStyleItem)myModel.getValueAt(i, 0); - for (Color color : ResourceHelper.resolveMultipleColors(myResourceResolver, item.getItemResourceValue())) { + for (Color color : ResourceHelper.resolveMultipleColors(myResourceResolver, item.getSelectedValue())) { myColorReferences.put(color, item); colorSet.add(color); } diff --git a/android/src/com/android/tools/idea/editors/theme/attributes/AttributesTableModel.java b/android/src/com/android/tools/idea/editors/theme/attributes/AttributesTableModel.java index 3d04086f0a1..6b825fcc90c 100644 --- a/android/src/com/android/tools/idea/editors/theme/attributes/AttributesTableModel.java +++ b/android/src/com/android/tools/idea/editors/theme/attributes/AttributesTableModel.java @@ -389,7 +389,7 @@ public class AttributesTableModel extends AbstractTableModel implements CellSpan public Class<?> getCellClass(int column) { EditedStyleItem item = myAttributes.get(myRowIndex); - ResourceValue resourceValue = mySelectedStyle.getConfiguration().getResourceResolver().resolveResValue(item.getItemResourceValue()); + ResourceValue resourceValue = mySelectedStyle.getConfiguration().getResourceResolver().resolveResValue(item.getSelectedValue()); if (resourceValue == null) { LOG.error("Unable to resolve " + item.getValue()); return null; @@ -403,7 +403,7 @@ public class AttributesTableModel extends AbstractTableModel implements CellSpan } AttributeDefinition attrDefinition = - ResolutionUtils.getAttributeDefinition(mySelectedStyle.getConfiguration(), item.getItemResourceValue()); + ResolutionUtils.getAttributeDefinition(mySelectedStyle.getConfiguration(), item.getSelectedValue()); if (urlType == ResourceType.COLOR || (value != null && value.startsWith("#") && ThemeEditorUtils.acceptsFormat(attrDefinition, AttributeFormat.Color))) { @@ -484,7 +484,7 @@ public class AttributesTableModel extends AbstractTableModel implements CellSpan } VirtualFileManager manager = VirtualFileManager.getInstance(); - ResourceValue resourceValue = myResourceResolver.resolveResValue(item.getItemResourceValue()); + ResourceValue resourceValue = myResourceResolver.resolveResValue(item.getSelectedValue()); final File file = new File(resourceValue.getValue()); final VirtualFile virtualFile = file.exists() ? manager.findFileByUrl("file://" + file.getAbsolutePath()) : null; diff --git a/android/src/com/android/tools/idea/editors/theme/attributes/ShowJavadocAction.java b/android/src/com/android/tools/idea/editors/theme/attributes/ShowJavadocAction.java index 26f18958692..c8859bf7660 100644 --- a/android/src/com/android/tools/idea/editors/theme/attributes/ShowJavadocAction.java +++ b/android/src/com/android/tools/idea/editors/theme/attributes/ShowJavadocAction.java @@ -64,7 +64,7 @@ public class ShowJavadocAction extends AnAction { Project project = e.getProject(); DocumentationManager documentationManager = DocumentationManager.getInstance(project); final DocumentationComponent docComponent = new DocumentationComponent(documentationManager); - String tooltip = ThemeEditorUtils.generateToolTipText(item.getItemResourceValue(), myContext.getCurrentThemeModule(), myContext.getConfiguration()); + String tooltip = ThemeEditorUtils.generateToolTipText(item.getSelectedValue(), myContext.getCurrentContextModule(), myContext.getConfiguration()); docComponent.setText(tooltip, e.getData(CommonDataKeys.PSI_FILE), true); JBPopup hint = JBPopupFactory.getInstance().createComponentPopupBuilder(docComponent, docComponent).setProject(project) diff --git a/android/src/com/android/tools/idea/editors/theme/attributes/editors/BooleanRendererEditor.java b/android/src/com/android/tools/idea/editors/theme/attributes/editors/BooleanRendererEditor.java index f62c5224221..238a100502f 100644 --- a/android/src/com/android/tools/idea/editors/theme/attributes/editors/BooleanRendererEditor.java +++ b/android/src/com/android/tools/idea/editors/theme/attributes/editors/BooleanRendererEditor.java @@ -25,9 +25,10 @@ import org.jetbrains.android.uipreview.ChooseResourceDialog; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.NotNull; -import javax.swing.*; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JTable; import javax.swing.table.TableCellRenderer; -import java.awt.*; +import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -95,7 +96,7 @@ public class BooleanRendererEditor extends TypedCellEditor<EditedStyleItem, Stri String selectedValue = (String) myComboBox.getSelectedItem(); if (USE_REFERENCE.equals(selectedValue)) { myComboBox.hidePopup(); - final ChooseResourceDialog dialog = new ChooseResourceDialog(myContext.getCurrentThemeModule(), BOOLEAN_TYPE, myEditedItemValue, null); + final ChooseResourceDialog dialog = new ChooseResourceDialog(myContext.getModuleForResources(), BOOLEAN_TYPE, myEditedItemValue, null); dialog.show(); diff --git a/android/src/com/android/tools/idea/editors/theme/attributes/editors/ColorRendererEditor.java b/android/src/com/android/tools/idea/editors/theme/attributes/editors/ColorRendererEditor.java index 72dc71e1988..a8a26a03a92 100644 --- a/android/src/com/android/tools/idea/editors/theme/attributes/editors/ColorRendererEditor.java +++ b/android/src/com/android/tools/idea/editors/theme/attributes/editors/ColorRendererEditor.java @@ -18,14 +18,17 @@ package com.android.tools.idea.editors.theme.attributes.editors; import com.android.SdkConstants; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.resources.ResourceResolver; +import com.android.tools.idea.configurations.Configuration; import com.android.tools.idea.editors.theme.StateListPicker; import com.android.tools.idea.editors.theme.ThemeEditorConstants; import com.android.tools.idea.editors.theme.ThemeEditorContext; +import com.android.tools.idea.editors.theme.ThemeEditorUtils; import com.android.tools.idea.editors.theme.datamodels.EditedStyleItem; import com.android.tools.idea.editors.theme.preview.AndroidThemePreviewPanel; import com.android.tools.idea.editors.theme.ui.ResourceComponent; import com.android.tools.idea.rendering.ResourceHelper; import com.android.tools.swing.ui.SwatchComponent; +import com.intellij.openapi.module.Module; import org.jetbrains.android.uipreview.ChooseResourceDialog; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -60,7 +63,7 @@ public class ColorRendererEditor extends GraphicalResourceRendererEditor { myItem = item; - final List<Color> colors = ResourceHelper.resolveMultipleColors(context.getResourceResolver(), item.getItemResourceValue()); + final List<Color> colors = ResourceHelper.resolveMultipleColors(context.getResourceResolver(), item.getSelectedValue()); String colorText = colors.isEmpty() ? LABEL_EMPTY : ResourceHelper.colorToString(colors.get(0)); component.setSwatchIcons(SwatchComponent.colorListOf(colors)); component.setNameText(String.format(LABEL_TEMPLATE, ThemeEditorConstants.RESOURCE_ITEM_COLOR.toString(), item.getName(), colorText)); @@ -84,28 +87,31 @@ public class ColorRendererEditor extends GraphicalResourceRendererEditor { colorName = myItem.getName(); } + Module module = myContext.getModuleForResources(); + final Configuration configuration = ThemeEditorUtils.getConfigurationForModule(module); + // TODO we need to handle color state lists correctly here. - ResourceResolver resourceResolver = myContext.getResourceResolver(); + ResourceResolver resourceResolver = configuration.getResourceResolver(); assert resourceResolver != null; ChooseResourceDialog dialog; - List<StateListPicker.StateListState> colorStates = ResourceHelper.resolveStateList(resourceResolver, myItem.getItemResourceValue()); + List<StateListPicker.StateListState> colorStates = ResourceHelper.resolveStateList(resourceResolver, myItem.getSelectedValue()); if (!colorStates.isEmpty()) { - dialog = new ChooseResourceDialog(myContext.getCurrentThemeModule(), myContext.getConfiguration(), ChooseResourceDialog.COLOR_TYPES, + dialog = new ChooseResourceDialog(module, configuration, ChooseResourceDialog.COLOR_TYPES, colorStates, ChooseResourceDialog.ResourceNameVisibility.FORCE, colorName); } else { - String resolvedColor = ResourceHelper.colorToString(ResourceHelper.resolveColor(resourceResolver, myItem.getItemResourceValue())); - dialog = new ChooseResourceDialog(myContext.getCurrentThemeModule(), ChooseResourceDialog.COLOR_TYPES, resolvedColor, + String resolvedColor = ResourceHelper.colorToString(ResourceHelper.resolveColor(resourceResolver, myItem.getSelectedValue())); + dialog = new ChooseResourceDialog(module, ChooseResourceDialog.COLOR_TYPES, resolvedColor, null, ChooseResourceDialog.ResourceNameVisibility.FORCE, colorName); } - final String oldValue = myItem.getItemResourceValue().getValue(); + final String oldValue = myItem.getSelectedValue().getValue(); dialog.setResourcePickerListener(new ChooseResourceDialog.ResourcePickerListener() { @Override public void resourceChanged(final @Nullable String resource) { - myItem.getItemResourceValue().setValue(resource == null ? oldValue : resource); + myItem.getSelectedValue().setValue(resource == null ? oldValue : resource); myPreviewPanel.invalidateGraphicsRenderer(); } }); @@ -113,7 +119,7 @@ public class ColorRendererEditor extends GraphicalResourceRendererEditor { dialog.show(); // Restore the old value in the properties model - myItem.getItemResourceValue().setValue(oldValue); + myItem.getSelectedValue().setValue(oldValue); myEditorValue = null; if (dialog.isOK()) { diff --git a/android/src/com/android/tools/idea/editors/theme/attributes/editors/DelegatingCellEditor.java b/android/src/com/android/tools/idea/editors/theme/attributes/editors/DelegatingCellEditor.java index 5a08d6da18d..36c1f668cea 100644 --- a/android/src/com/android/tools/idea/editors/theme/attributes/editors/DelegatingCellEditor.java +++ b/android/src/com/android/tools/idea/editors/theme/attributes/editors/DelegatingCellEditor.java @@ -61,7 +61,7 @@ public class DelegatingCellEditor implements TableCellEditor { final Font font; if (value instanceof EditedStyleItem) { final EditedStyleItem item = (EditedStyleItem) value; - tooltipText = ThemeEditorUtils.generateToolTipText(item.getItemResourceValue(), myContext.getCurrentThemeModule(), myContext.getConfiguration()); + tooltipText = ThemeEditorUtils.generateToolTipText(item.getSelectedValue(), myContext.getCurrentContextModule(), myContext.getConfiguration()); stringValue = ThemeEditorUtils.extractRealValue(item, model.getCellClass(row, column)); ThemeEditorStyle selectedStyle = ((AttributesTableModel)table.getModel()).getSelectedStyle(); // Displays in bold attributes that are overriding their inherited value diff --git a/android/src/com/android/tools/idea/editors/theme/attributes/editors/DelegatingCellRenderer.java b/android/src/com/android/tools/idea/editors/theme/attributes/editors/DelegatingCellRenderer.java index ed873de661d..df9a1a84fd2 100644 --- a/android/src/com/android/tools/idea/editors/theme/attributes/editors/DelegatingCellRenderer.java +++ b/android/src/com/android/tools/idea/editors/theme/attributes/editors/DelegatingCellRenderer.java @@ -65,7 +65,7 @@ public class DelegatingCellRenderer implements TableCellRenderer { Point mousePos = table.getMousePosition(); if (mousePos != null && item != null) { if (table.getCellRect(row, column, true).contains(mousePos)) { - final ItemResourceValue resValue = ((EditedStyleItem)value).getItemResourceValue(); + final ItemResourceValue resValue = ((EditedStyleItem)value).getSelectedValue(); Configuration configuration = item.getSourceStyle().getConfiguration(); String toolTipText = ThemeEditorUtils.generateToolTipText(resValue, configuration.getModule(), configuration); jComponent.setToolTipText(toolTipText); diff --git a/android/src/com/android/tools/idea/editors/theme/attributes/editors/DrawableRendererEditor.java b/android/src/com/android/tools/idea/editors/theme/attributes/editors/DrawableRendererEditor.java index e2a267cbfbf..4acc8baad21 100644 --- a/android/src/com/android/tools/idea/editors/theme/attributes/editors/DrawableRendererEditor.java +++ b/android/src/com/android/tools/idea/editors/theme/attributes/editors/DrawableRendererEditor.java @@ -58,11 +58,11 @@ public class DrawableRendererEditor extends GraphicalResourceRendererEditor { @Nullable public static RenderTask configureRenderTask(@NotNull final ThemeEditorContext context) { RenderTask result = null; - AndroidFacet facet = AndroidFacet.getInstance(context.getCurrentThemeModule()); + AndroidFacet facet = AndroidFacet.getInstance(context.getCurrentContextModule()); if (facet != null) { final RenderService service = RenderService.get(facet); result = - service.createTask(null, context.getConfiguration(), new RenderLogger("ThemeEditorLogger", context.getCurrentThemeModule()), null); + service.createTask(null, context.getConfiguration(), new RenderLogger("ThemeEditorLogger", context.getCurrentContextModule()), null); } return result; @@ -75,7 +75,7 @@ public class DrawableRendererEditor extends GraphicalResourceRendererEditor { myItem = item; if (myRenderTask != null) { - component.setSwatchIcons(SwatchComponent.imageListOf(myRenderTask.renderDrawableAllStates(item.getItemResourceValue()))); + component.setSwatchIcons(SwatchComponent.imageListOf(myRenderTask.renderDrawableAllStates(item.getSelectedValue()))); } String nameText = @@ -88,7 +88,7 @@ public class DrawableRendererEditor extends GraphicalResourceRendererEditor { @Override public void actionPerformed(ActionEvent e) { final ChooseResourceDialog dialog = - new ChooseResourceDialog(myContext.getCurrentThemeModule(), DRAWABLE_TYPE, myItem.getValue(), null); + new ChooseResourceDialog(myContext.getModuleForResources(), DRAWABLE_TYPE, myItem.getValue(), null); dialog.show(); diff --git a/android/src/com/android/tools/idea/editors/theme/attributes/editors/EnumRendererEditor.java b/android/src/com/android/tools/idea/editors/theme/attributes/editors/EnumRendererEditor.java index 274eab2fdff..a777d3c646d 100644 --- a/android/src/com/android/tools/idea/editors/theme/attributes/editors/EnumRendererEditor.java +++ b/android/src/com/android/tools/idea/editors/theme/attributes/editors/EnumRendererEditor.java @@ -65,7 +65,7 @@ public class EnumRendererEditor extends TypedCellEditor<EditedStyleItem, String> @Override public Component getEditorComponent(JTable table, EditedStyleItem value, boolean isSelected, int row, int column) { AttributeDefinition attrDefinition = - ResolutionUtils.getAttributeDefinition(value.getSourceStyle().getConfiguration(), value.getItemResourceValue()); + ResolutionUtils.getAttributeDefinition(value.getSourceStyle().getConfiguration(), value.getSelectedValue()); if (attrDefinition != null) { if (attrDefinition.getFormats().size() > 1) { myComboBox.setEditable(true); // makes the box editable for items that can take values outside of the choices diff --git a/android/src/com/android/tools/idea/editors/theme/attributes/editors/FlagRendererEditor.java b/android/src/com/android/tools/idea/editors/theme/attributes/editors/FlagRendererEditor.java index 584bb673bf2..5946ab68d97 100644 --- a/android/src/com/android/tools/idea/editors/theme/attributes/editors/FlagRendererEditor.java +++ b/android/src/com/android/tools/idea/editors/theme/attributes/editors/FlagRendererEditor.java @@ -194,7 +194,7 @@ public class FlagRendererEditor extends TypedCellEditor<EditedStyleItem, String> protected JComponent createCenterPanel() { Box box = new Box(BoxLayout.PAGE_AXIS); AttributeDefinition attrDefinition = - ResolutionUtils.getAttributeDefinition(myItem.getSourceStyle().getConfiguration(), myItem.getItemResourceValue()); + ResolutionUtils.getAttributeDefinition(myItem.getSourceStyle().getConfiguration(), myItem.getSelectedValue()); if (attrDefinition != null) { String[] flagNames = attrDefinition.getValues(); for (String flagName : flagNames) { diff --git a/android/src/com/android/tools/idea/editors/theme/attributes/editors/StyleListCellRenderer.java b/android/src/com/android/tools/idea/editors/theme/attributes/editors/StyleListCellRenderer.java index 7ef28dbdb33..95d0f6af200 100644 --- a/android/src/com/android/tools/idea/editors/theme/attributes/editors/StyleListCellRenderer.java +++ b/android/src/com/android/tools/idea/editors/theme/attributes/editors/StyleListCellRenderer.java @@ -86,7 +86,7 @@ public class StyleListCellRenderer extends JPanel implements ListCellRenderer { String parentName = parent != null ? parent.getName() : null; String defaultAppTheme = null; - final AndroidFacet facet = AndroidFacet.getInstance(myContext.getCurrentThemeModule()); + final AndroidFacet facet = AndroidFacet.getInstance(myContext.getCurrentContextModule()); if (facet != null) { Manifest manifest = facet.getManifest(); if (manifest != null && manifest.getApplication() != null && manifest.getApplication().getXmlTag() != null) { diff --git a/android/src/com/android/tools/idea/editors/theme/datamodels/EditedStyleItem.java b/android/src/com/android/tools/idea/editors/theme/datamodels/EditedStyleItem.java index 3a0a980a7aa..2a1b9d695e5 100644 --- a/android/src/com/android/tools/idea/editors/theme/datamodels/EditedStyleItem.java +++ b/android/src/com/android/tools/idea/editors/theme/datamodels/EditedStyleItem.java @@ -83,7 +83,7 @@ public class EditedStyleItem { this(new ConfiguredItemResourceValue(DEFAULT_CONFIGURATION, itemResourceValue), sourceTheme); } - private ItemResourceValue getSelectedValue() { + public ItemResourceValue getSelectedValue() { return mySelectedValue.myValue; } @@ -111,12 +111,6 @@ public class EditedStyleItem { return mySourceTheme; } - // TODO: Remove this method and replace directly with getSelectedValue - @NotNull - public ItemResourceValue getItemResourceValue() { - return getSelectedValue(); - } - /** * Returns the {@link FolderConfiguration} associated to the {@link #getValue} call. * <p/> diff --git a/android/src/com/android/tools/idea/editors/theme/datamodels/ThemeEditorStyle.java b/android/src/com/android/tools/idea/editors/theme/datamodels/ThemeEditorStyle.java index 096c5f70649..cd7206084bd 100644 --- a/android/src/com/android/tools/idea/editors/theme/datamodels/ThemeEditorStyle.java +++ b/android/src/com/android/tools/idea/editors/theme/datamodels/ThemeEditorStyle.java @@ -28,7 +28,9 @@ import com.android.resources.ResourceType; import com.android.sdklib.IAndroidTarget; import com.android.tools.idea.configurations.Configuration; import com.android.tools.idea.editors.theme.ResolutionUtils; +import com.android.tools.idea.editors.theme.ThemeEditorContext; import com.android.tools.idea.editors.theme.ThemeEditorUtils; +import com.android.tools.idea.editors.theme.ThemeResolver; import com.android.tools.idea.rendering.AppResourceRepository; import com.android.tools.idea.rendering.LocalResourceRepository; import com.android.tools.idea.rendering.ProjectResourceRepository; @@ -41,6 +43,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; @@ -70,11 +73,19 @@ public class ThemeEditorStyle { private final @NotNull Configuration myConfiguration; private final Project myProject; + /** + * Source module of the theme, set to null if the theme comes from external libraries or the framework. + * For currently edited theme stored in {@link ThemeEditorContext#mySelectedStyleSourceModule}. + */ + private final @Nullable Module mySourceModule; + public ThemeEditorStyle(final @NotNull Configuration configuration, - final @NotNull StyleResourceValue styleResourceValue) { + final @NotNull StyleResourceValue styleResourceValue, + final @Nullable Module sourceModule) { myStyleResourceValue = styleResourceValue; myConfiguration = configuration; myProject = configuration.getModule().getProject(); + mySourceModule = sourceModule; } public boolean isProjectStyle() { @@ -103,8 +114,9 @@ public class ThemeEditorStyle { assert !myStyleResourceValue.isFramework(); final ImmutableList.Builder<ResourceItem> resourceItems = ImmutableList.builder(); - AndroidFacet facet = AndroidFacet.getInstance(myConfiguration.getModule()); - assert facet != null : myConfiguration.getModule().getName() + " module doesn't have AndroidFacet"; + final Module module = getModuleForAcquiringResources(); + AndroidFacet facet = AndroidFacet.getInstance(module); + assert facet != null : module.getName() + " module doesn't have AndroidFacet"; ThemeEditorUtils.acceptResourceResolverVisitor(facet, new ThemeEditorUtils.ResourceFolderVisitor() { @Override public void visitResourceFolder(@NotNull LocalResourceRepository resources, @NotNull String variantName, boolean isSourceSelected) { @@ -125,6 +137,15 @@ public class ThemeEditorStyle { } /** + * Get a Module instance that should be used for resolving all possible resources that could constitute values + * of this theme's attributes. Returns source module of a theme if it's available and rendering context module + * otherwise. + */ + private Module getModuleForAcquiringResources() { + return mySourceModule != null ? mySourceModule : myConfiguration.getModule(); + } + + /** * Returns whether this style is editable. */ public boolean isReadOnly() { @@ -180,8 +201,7 @@ public class ThemeEditorStyle { } } else { - // TODO: Use something else instead of myConfiguration.getModule? - LocalResourceRepository repository = AppResourceRepository.getAppResources(myConfiguration.getModule(), true); + LocalResourceRepository repository = AppResourceRepository.getAppResources(getModuleForAcquiringResources(), true); assert repository != null; // Find every definition of this style and get all the attributes defined List<ResourceItem> styleDefinitions = repository.getResourceItem(ResourceType.STYLE, myStyleResourceValue.getName()); @@ -233,10 +253,20 @@ public class ThemeEditorStyle { } /** + * See {@link #getParent(ThemeResolver)} + */ + public ThemeEditorStyle getParent() { + return getParent(null); + } + + /** * Returns the style parent or null if this is a root style. + * + * @param themeResolver theme resolver that would be used to look up parent theme by name + * Pass null if you don't care about resulting ThemeEditorStyle source module (which would be null in that case) */ @Nullable - public ThemeEditorStyle getParent() { + public ThemeEditorStyle getParent(@Nullable ThemeResolver themeResolver) { ResourceResolver resolver = myConfiguration.getResourceResolver(); assert resolver != null; @@ -245,7 +275,12 @@ public class ThemeEditorStyle { return null; } - return ResolutionUtils.getStyle(myConfiguration, ResolutionUtils.getQualifiedStyleName(parent)); + if (themeResolver == null) { + return ResolutionUtils.getStyle(myConfiguration, ResolutionUtils.getQualifiedStyleName(parent), null); + } + else { + return themeResolver.getTheme(ResolutionUtils.getQualifiedStyleName(parent)); + } } /** @@ -328,7 +363,7 @@ public class ThemeEditorStyle { // copy this theme at the minimum api level for this attribute ThemeEditorUtils.copyTheme(minAcceptableApi, apiInformation.toBeCopied); - AndroidFacet facet = AndroidFacet.getInstance(myConfiguration.getModule()); + AndroidFacet facet = AndroidFacet.getInstance(getModuleForAcquiringResources()); if (facet != null) { facet.refreshResources(); } @@ -490,6 +525,14 @@ public class ThemeEditorStyle { } /** + * Plain getter, see {@link #mySourceModule} for field description. + */ + @Nullable + public Module getSourceModule() { + return mySourceModule; + } + + /** * Class containing all the information needed to correctly set attributes with respect to api levels */ private class ApiInformation { @@ -499,7 +542,7 @@ public class ThemeEditorStyle { private XmlTag toBeCopied = null; private ApiInformation(int minAcceptableApi) { - int minApiLevel = ThemeEditorUtils.getMinApiLevel(myConfiguration.getModule()); + int minApiLevel = ThemeEditorUtils.getMinApiLevel(getModuleForAcquiringResources()); int closestNonAllowedApi = 0; boolean createNewTheme = true; diff --git a/android/src/com/android/tools/idea/editors/vmtrace/VmTraceFileType.java b/android/src/com/android/tools/idea/editors/vmtrace/VmTraceFileType.java new file mode 100644 index 00000000000..056e9504c14 --- /dev/null +++ b/android/src/com/android/tools/idea/editors/vmtrace/VmTraceFileType.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tools.idea.editors.vmtrace; + +import com.android.ddmlib.DdmConstants; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.vfs.VirtualFile; +import icons.AndroidIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class VmTraceFileType implements FileType { + public static final VmTraceFileType INSTANCE = new VmTraceFileType(); + + @NotNull + @Override + public String getName() { + return "VmTrace"; + } + + @NotNull + @Override + public String getDescription() { + return "Method tracing file"; + } + + @NotNull + @Override + public String getDefaultExtension() { + return DdmConstants.EXTENSION; + } + + @Nullable + @Override + public Icon getIcon() { + return AndroidIcons.Ddms.StartMethodProfiling; + } + + @Override + public boolean isBinary() { + return true; + } + + @Override + public boolean isReadOnly() { + return true; + } + + @Nullable + @Override + public String getCharset(@NotNull VirtualFile file, @NotNull byte[] content) { + return null; + } +} diff --git a/android/src/com/android/tools/idea/editors/vmtrace/VmTraceFileTypeFactory.java b/android/src/com/android/tools/idea/editors/vmtrace/VmTraceFileTypeFactory.java new file mode 100644 index 00000000000..1216760ba34 --- /dev/null +++ b/android/src/com/android/tools/idea/editors/vmtrace/VmTraceFileTypeFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tools.idea.editors.vmtrace; + +import com.intellij.openapi.fileTypes.FileTypeConsumer; +import com.intellij.openapi.fileTypes.FileTypeFactory; +import org.jetbrains.annotations.NotNull; + +public class VmTraceFileTypeFactory extends FileTypeFactory { + @Override + public void createFileTypes(@NotNull FileTypeConsumer consumer) { + consumer.consume(VmTraceFileType.INSTANCE, VmTraceFileType.INSTANCE.getDefaultExtension()); + } +} diff --git a/android/src/com/android/tools/idea/profiling/capture/FileCaptureType.java b/android/src/com/android/tools/idea/profiling/capture/FileCaptureType.java index 6cb4a5d160b..52285e6114e 100644 --- a/android/src/com/android/tools/idea/profiling/capture/FileCaptureType.java +++ b/android/src/com/android/tools/idea/profiling/capture/FileCaptureType.java @@ -50,7 +50,11 @@ public abstract class FileCaptureType extends CaptureType { @Override public boolean isValidCapture(@NotNull VirtualFile file) { - return SdkUtils.endsWithIgnoreCase(file.getPath(), myFileNameExtension); + return isValidCapture(file.getPath()); + } + + public boolean isValidCapture(@NotNull String filePath) { + return SdkUtils.endsWithIgnoreCase(filePath, myFileNameExtension); } @NotNull diff --git a/android/src/com/android/tools/idea/profiling/view/CapturesToolWindow.java b/android/src/com/android/tools/idea/profiling/view/CapturesToolWindow.java index d48d27f2629..785ea2b4f40 100644 --- a/android/src/com/android/tools/idea/profiling/view/CapturesToolWindow.java +++ b/android/src/com/android/tools/idea/profiling/view/CapturesToolWindow.java @@ -56,6 +56,7 @@ import java.util.List; public class CapturesToolWindow extends BulkFileListener.Adapter implements Disposable, HierarchyListener, CaptureService.CaptureListener, DataProvider, DeleteProvider { + public static final String TREE_NAME = "CapturesPaneTree"; @NotNull public static final DataKey<Capture[]> CAPTURE_ARRAY = DataKey.create("CaptureArray"); @@ -73,6 +74,7 @@ public class CapturesToolWindow extends BulkFileListener.Adapter myProject = project; DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode()); myTree = new SimpleTree(model); + myTree.setName(TREE_NAME); myTree.setRootVisible(false); myComponent = ScrollPaneFactory.createScrollPane(myTree); diff --git a/android/src/com/android/tools/idea/profiling/view/CapturesToolWindowFactory.java b/android/src/com/android/tools/idea/profiling/view/CapturesToolWindowFactory.java index 9b4446b2e1b..f6f82a58648 100644 --- a/android/src/com/android/tools/idea/profiling/view/CapturesToolWindowFactory.java +++ b/android/src/com/android/tools/idea/profiling/view/CapturesToolWindowFactory.java @@ -21,9 +21,11 @@ import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowFactory; import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentFactory; +import org.jetbrains.android.util.AndroidBundle; import org.jetbrains.annotations.NotNull; public class CapturesToolWindowFactory implements ToolWindowFactory, DumbAware { + public static final String TOOL_WINDOW_ID = AndroidBundle.message("android.captures.title"); @Override public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { @@ -32,6 +34,6 @@ public class CapturesToolWindowFactory implements ToolWindowFactory, DumbAware { ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); Content content = contentFactory.createContent(view.getComponent(), "", false); toolWindow.getContentManager().addContent(content); + toolWindow.setTitle(TOOL_WINDOW_ID); } } - diff --git a/android/src/com/android/tools/idea/rendering/AarResourceClassRegistry.java b/android/src/com/android/tools/idea/rendering/AarResourceClassRegistry.java index c780fa4ecea..82e981b6e0b 100644 --- a/android/src/com/android/tools/idea/rendering/AarResourceClassRegistry.java +++ b/android/src/com/android/tools/idea/rendering/AarResourceClassRegistry.java @@ -16,8 +16,6 @@ package com.android.tools.idea.rendering; import com.android.io.FileWrapper; -import com.android.tools.idea.gradle.project.GradleBuildListener; -import com.android.tools.idea.gradle.util.BuildMode; import com.android.xml.AndroidManifest; import com.google.common.collect.Maps; import com.intellij.openapi.components.ProjectComponent; @@ -25,8 +23,6 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.util.containers.HashSet; -import com.intellij.util.messages.MessageBusConnection; -import org.jetbrains.android.uipreview.ModuleClassLoader; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -36,7 +32,6 @@ import java.util.Map; import static com.android.SdkConstants.ANDROID_MANIFEST_XML; import static com.android.SdkConstants.DOT_AAR; -import static com.android.tools.idea.gradle.compiler.PostProjectBuildTasksExecutor.GRADLE_BUILD_TOPIC; import static org.jetbrains.android.facet.ResourceFolderManager.EXPLODED_AAR; /** @@ -46,7 +41,6 @@ public class AarResourceClassRegistry implements ProjectComponent { private final Map<AppResourceRepository, AarResourceClassGenerator> myGeneratorMap = Maps.newHashMap(); private final Project myProject; - private GradleBuildListener myBuildCompleteListener; private Collection<String> myPackages; @SuppressWarnings("WeakerAccess") // Accessed via reflection. @@ -101,7 +95,6 @@ public class AarResourceClassRegistry implements ProjectComponent { if (myPackages != null && myPackages.contains(pkg)) { AarResourceClassGenerator generator = myGeneratorMap.get(appRepo); if (generator != null) { - registerSyncListenerIfNecessary(); return generator.generate(name); } } @@ -110,36 +103,6 @@ public class AarResourceClassRegistry implements ProjectComponent { } /** - * There's a bug in the ModuleClassLoader's cache implementation, which results in crashes during preview rendering. The workaround is - * to clear the cache on each build. This registers a build complete listener to trigger the cache refresh. - */ - private void registerSyncListenerIfNecessary() { - if (myBuildCompleteListener != null) { - return; - } - myBuildCompleteListener = new GradleBuildListener() { - @Override - public void buildFinished(@NotNull Project builtProject, @Nullable BuildMode mode) { - if (mode == null || builtProject != myProject) { - return; - } - switch (mode) { - case CLEAN: - case ASSEMBLE: - case COMPILE_JAVA: - case REBUILD: - ModuleClassLoader.clearCache(); - clearCache(); - case SOURCE_GEN: - case ASSEMBLE_TRANSLATE: - } - } - }; - MessageBusConnection connection = myProject.getMessageBus().connect(myProject); - connection.subscribe(GRADLE_BUILD_TOPIC, myBuildCompleteListener); - } - - /** * Ideally, this method will not exist. But there are potential bugs in the caching mechanism. * So, the method should be called when rendering fails due to hard to explain causes: like * NoSuchFieldError. The method also resets the dynamic ids generated in {@link AppResourceRepository}. diff --git a/android/src/com/android/tools/idea/rendering/AppResourceRepository.java b/android/src/com/android/tools/idea/rendering/AppResourceRepository.java index 675923df6a1..8c679399cba 100644 --- a/android/src/com/android/tools/idea/rendering/AppResourceRepository.java +++ b/android/src/com/android/tools/idea/rendering/AppResourceRepository.java @@ -35,6 +35,7 @@ import com.intellij.openapi.project.Project; import gnu.trove.TIntObjectHashMap; import gnu.trove.TObjectIntHashMap; import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.uipreview.ModuleClassLoader; import org.jetbrains.android.util.AndroidUtils; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -348,6 +349,10 @@ public class AppResourceRepository extends MultiResourceRepository { } } setChildren(resources); + + // Clear the fake R class cache and the ModuleClassLoader cache. + resetDynamicIds(true); + ModuleClassLoader.clearCache(myFacet.getModule()); } @VisibleForTesting diff --git a/android/src/com/android/tools/idea/rendering/FlagManager.java b/android/src/com/android/tools/idea/rendering/FlagManager.java index a09185eb55f..a52eac2d09c 100644 --- a/android/src/com/android/tools/idea/rendering/FlagManager.java +++ b/android/src/com/android/tools/idea/rendering/FlagManager.java @@ -21,6 +21,7 @@ import com.android.ide.common.resources.LocaleManager; import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.ide.common.resources.configuration.LocaleQualifier; import com.google.common.collect.Maps; +import com.intellij.ide.ui.UISettings; import com.intellij.openapi.util.IconLoader; import com.intellij.ui.ColoredListCellRenderer; import com.intellij.util.Function; @@ -28,6 +29,7 @@ import icons.AndroidIcons; import org.jetbrains.annotations.NotNull; import javax.swing.*; +import java.lang.reflect.Field; import java.util.Map; /** @@ -86,6 +88,10 @@ public class FlagManager { // Look up the region for a given language assert language != null; + if (!showFlagsForLanguages()) { + return null; + } + // Special cases where we have a dedicated flag available: if (language.equals("ca")) { //$NON-NLS-1$ return getIcon("catalonia"); //$NON-NLS-1$ @@ -110,6 +116,25 @@ public class FlagManager { return getIcon(region); } + private static boolean ourFlagSettingAvailable = true; + private static Field ourLanguageFlagField; + + /** Whether users want to use flags to represent languages when possible */ + private static boolean showFlagsForLanguages() { + if (ourFlagSettingAvailable) { + try { + if (ourLanguageFlagField == null) { + ourLanguageFlagField = UISettings.class.getDeclaredField("LANGUAGE_FLAGS"); + } + return ourLanguageFlagField.getBoolean(UISettings.getInstance()); + } catch (Throwable t) { + ourFlagSettingAvailable = false; + return true; + } + } + return true; + } + /** * Returns the flag for the given language and region. * diff --git a/android/src/com/android/tools/idea/rendering/RenderLogger.java b/android/src/com/android/tools/idea/rendering/RenderLogger.java index 15b43db4fd3..2f6607635ab 100644 --- a/android/src/com/android/tools/idea/rendering/RenderLogger.java +++ b/android/src/com/android/tools/idea/rendering/RenderLogger.java @@ -406,6 +406,10 @@ public class RenderLogger extends LayoutLog { public void warning(@Nullable String tag, @NotNull String message, @Nullable Object data) { String description = describe(message); + if (TAG_INFO.equals(tag)) { + Logger.getInstance(getClass()).info(description); + return; + } if (TAG_RESOURCES_FORMAT.equals(tag)) { // TODO: Accumulate multiple hits of this form and synthesize into one if (description.equals("You must supply a layout_width attribute.") //$NON-NLS-1$ diff --git a/android/src/com/android/tools/idea/rendering/ResourceHelper.java b/android/src/com/android/tools/idea/rendering/ResourceHelper.java index 9ca56c29c60..ee77621b950 100644 --- a/android/src/com/android/tools/idea/rendering/ResourceHelper.java +++ b/android/src/com/android/tools/idea/rendering/ResourceHelper.java @@ -546,7 +546,7 @@ public class ResourceHelper { List<StateListPicker.StateListState> stateList = new ArrayList<StateListPicker.StateListState>(); NodeList items = document.getElementsByTagName(TAG_ITEM); for (int i = 0; i < items.getLength(); i++) { - stateList.add(createStateListState(items.item(i))); + stateList.add(createStateListState(items.item(i), value.isFramework())); } return stateList; } @@ -562,7 +562,7 @@ public class ResourceHelper { * Returns a StateListState representing the state in item. */ @NotNull - private static StateListPicker.StateListState createStateListState(Node item) { + private static StateListPicker.StateListState createStateListState(Node item, boolean isFramework) { StateListPicker.StateListState state = new StateListPicker.StateListState(); NamedNodeMap attributes = item.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { @@ -570,7 +570,13 @@ public class ResourceHelper { String name = attr.getLocalName(); String value = attr.getNodeValue(); if (SdkConstants.ATTR_COLOR.equals(name)) { - state.setColor(value); + ResourceUrl url = ResourceUrl.parse(value, isFramework); + if (url != null) { + state.setColor(url.toString()); + } + else { + state.setColor(value); + } } else if (name != null && name.startsWith(STATE_NAME_PREFIX)) { state.addAttribute(name, Boolean.valueOf(value)); diff --git a/android/src/com/android/tools/idea/sdk/SdkState.java b/android/src/com/android/tools/idea/sdk/SdkState.java index 2f6fa31eb6d..cc31a3f81a0 100755 --- a/android/src/com/android/tools/idea/sdk/SdkState.java +++ b/android/src/com/android/tools/idea/sdk/SdkState.java @@ -27,6 +27,7 @@ import com.google.common.base.Objects; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.*; import com.intellij.openapi.progress.util.ProgressWindow; @@ -48,7 +49,6 @@ public class SdkState { private static final Logger LOG = Logger.getInstance("#com.android.tools.idea.sdk.SdkState"); @GuardedBy(value = "sSdkStates") private static final Set<SoftReference<SdkState>> sSdkStates = new HashSet<SoftReference<SdkState>>(); - @Nullable private final AndroidSdkData mySdkData; private final RemoteSdk myRemoteSdk; private SdkPackages myPackages = new SdkPackages(); @@ -63,7 +63,7 @@ public class SdkState { if (mySdkData == null) { myPackages = new SdkPackages(); } - myRemoteSdk = new RemoteSdk(new LogWrapper(Logger.getInstance(SdkState.class))); + myRemoteSdk = new RemoteSdk(); } /** @@ -229,7 +229,6 @@ public class SdkState { return Lists.newArrayList(r); } - // ----- private static class IndicatorLogger implements ILogger { diff --git a/android/src/com/android/tools/idea/sdk/remote/RemoteSdk.java b/android/src/com/android/tools/idea/sdk/remote/RemoteSdk.java index 934c90ece52..decbd89060c 100755 --- a/android/src/com/android/tools/idea/sdk/remote/RemoteSdk.java +++ b/android/src/com/android/tools/idea/sdk/remote/RemoteSdk.java @@ -50,12 +50,8 @@ public class RemoteSdk { private long mSdkSourceTS; private DownloadCache mDownloadCache; - public RemoteSdk(ILogger logger) { - this(new SettingsController(logger)); - } - - public RemoteSdk(SettingsController settingsController) { - mSettingsController = settingsController; + public RemoteSdk() { + mSettingsController = SettingsController.getInstance(); } /** @@ -77,7 +73,7 @@ public class RemoteSdk { public Multimap<PkgType, RemotePkgInfo> fetch(@NonNull SdkSources sources, @NonNull ILogger logger) { Multimap<PkgType, RemotePkgInfo> remotes = HashMultimap.create(); - boolean forceHttp = mSettingsController.getSettings().getForceHttp(); + boolean forceHttp = mSettingsController.getForceHttp(); // Implementation detail: right now this reuses the SdkSource(s) classes // from the sdk-repository v2. The problem with that is that the sources are @@ -159,7 +155,7 @@ public class RemoteSdk { } } - if (mSettingsController.getSettings().getForceHttp()) { + if (mSettingsController.getForceHttp()) { url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$ } @@ -201,7 +197,7 @@ public class RemoteSdk { protected DownloadCache getDownloadCache() { if (mDownloadCache == null) { mDownloadCache = new DownloadCache( - mSettingsController.getSettings().getUseDownloadCache() ? DownloadCache.Strategy.FRESH_CACHE : DownloadCache.Strategy.DIRECT); + mSettingsController.getUseDownloadCache() ? DownloadCache.Strategy.FRESH_CACHE : DownloadCache.Strategy.DIRECT); } return mDownloadCache; } diff --git a/android/src/com/android/tools/idea/sdk/remote/internal/AddonsListFetcher.java b/android/src/com/android/tools/idea/sdk/remote/internal/AddonsListFetcher.java index ed14f81e83c..25a284297b0 100755 --- a/android/src/com/android/tools/idea/sdk/remote/internal/AddonsListFetcher.java +++ b/android/src/com/android/tools/idea/sdk/remote/internal/AddonsListFetcher.java @@ -253,7 +253,6 @@ public class AddonsListFetcher { * @param monitor {@link ITaskMonitor} related to this URL. * @param outException If non null, where to store any exception that * happens during the fetch. - * @see com.android.tools.idea.sdk.remote.internal.UrlOpener UrlOpener, which handles all URL logic. */ private InputStream fetchXmlUrl(String urlString, DownloadCache cache, diff --git a/android/src/com/android/tools/idea/sdk/remote/internal/CanceledByUserException.java b/android/src/com/android/tools/idea/sdk/remote/internal/CanceledByUserException.java index 400eb0db759..b885fb20472 100755 --- a/android/src/com/android/tools/idea/sdk/remote/internal/CanceledByUserException.java +++ b/android/src/com/android/tools/idea/sdk/remote/internal/CanceledByUserException.java @@ -17,7 +17,7 @@ package com.android.tools.idea.sdk.remote.internal; /** - * Exception thrown by {@link DownloadCache} and {@link com.android.tools.idea.sdk.remote.internal.UrlOpener} when a user + * Exception thrown by {@link DownloadCache} when a user * cancels an HTTP Basic authentication or NTML authentication dialog. */ public class CanceledByUserException extends Exception { diff --git a/android/src/com/android/tools/idea/sdk/remote/internal/DownloadCache.java b/android/src/com/android/tools/idea/sdk/remote/internal/DownloadCache.java index eb66907500b..6be63133140 100755 --- a/android/src/com/android/tools/idea/sdk/remote/internal/DownloadCache.java +++ b/android/src/com/android/tools/idea/sdk/remote/internal/DownloadCache.java @@ -25,14 +25,16 @@ import com.android.prefs.AndroidLocation; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.io.FileOp; import com.android.sdklib.io.IFileOp; +import com.android.tools.idea.sdk.remote.internal.sources.SdkAddonsListConstants; import com.android.utils.Pair; -import org.apache.http.Header; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; +import com.intellij.util.net.HttpConfigurable; +import org.apache.http.*; import org.apache.http.message.BasicHeader; +import org.apache.http.message.BasicHttpResponse; +import org.apache.http.message.BasicStatusLine; import java.io.*; +import java.net.HttpURLConnection; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; @@ -217,30 +219,89 @@ public class DownloadCache { } /** - * Calls {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])} + * Calls {@link HttpConfigurable#openHttpConnection(String)} * to actually perform a download. * <p/> * Isolated so that it can be overridden by unit tests. */ @VisibleForTesting(visibility=Visibility.PRIVATE) @NonNull - protected Pair<InputStream, HttpResponse> openUrl( + protected Pair<InputStream, HttpURLConnection> openUrl( @NonNull String url, boolean needsMarkResetSupport, @NonNull ITaskMonitor monitor, @Nullable Header[] headers) throws IOException, CanceledByUserException { - return com.android.tools.idea.sdk.remote.internal.UrlOpener.openUrl(url, needsMarkResetSupport, monitor, headers); + HttpURLConnection connection = HttpConfigurable.getInstance().openHttpConnection(url); + if (headers != null) { + for (Header header : headers) { + connection.setRequestProperty(header.getName(), header.getValue()); + } + } + connection.connect(); + InputStream is = connection.getInputStream(); + if (needsMarkResetSupport) { + is = ensureMarkReset(is); + } + + return Pair.of(is, connection); + } + + private InputStream ensureMarkReset(InputStream is) { + // If the caller requires an InputStream that supports mark/reset, let's + // make sure we have such a stream. + if (is != null) { + if (!is.markSupported()) { + try { + // Consume the whole input stream and offer a byte array stream instead. + // This can only work sanely if the resource is a small file that can + // fit in memory. It also means the caller has no chance of showing + // a meaningful download progress. + + InputStream is2 = toByteArrayInputStream(is); + if (is2 != null) { + try { + is.close(); + } + catch (Exception ignore) { + } + return is2; + } + } + catch (Exception e3) { + // Ignore. If this can't work, caller will fail later. + } + } + } + return is; + } + + // ByteArrayInputStream is the duct tape of input streams. + private static InputStream toByteArrayInputStream(InputStream is) throws IOException { + int inc = 4096; + int curr = 0; + byte[] result = new byte[inc]; + + int n; + while ((n = is.read(result, curr, result.length - curr)) != -1) { + curr += n; + if (curr == result.length) { + byte[] temp = new byte[curr + inc]; + System.arraycopy(result, 0, temp, 0, curr); + result = temp; + } + } + + return new ByteArrayInputStream(result, 0, curr); } /** - * Does a direct download of the given URL using {@link UrlOpener}. + * Does a direct download of the given URL using {@link HttpConfigurable#openHttpConnection(String)}. * This does not check the download cache and does not attempt to cache the file. * Instead the HttpClient library returns a progressive download stream. * <p/> * For details on realm authentication and user/password handling, - * check the underlying {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])} - * documentation. + * see {@link HttpConfigurable#openHttpConnection(String)}. * <p/> * The resulting input stream may not support mark/reset. * @@ -260,7 +321,7 @@ public class DownloadCache { * authentication dialog. */ @NonNull - public Pair<InputStream, HttpResponse> openDirectUrl( + public Pair<InputStream, HttpURLConnection> openDirectUrl( @NonNull String urlString, @Nullable Header[] headers, @NonNull ITaskMonitor monitor) @@ -311,12 +372,12 @@ public class DownloadCache { if (DEBUG) { System.out.println(String.format("%s : Direct download", urlString)); //$NON-NLS-1$ } - Pair<InputStream, HttpResponse> result = openUrl( + Pair<InputStream, HttpURLConnection> result = openUrl( urlString, false /*needsMarkResetSupport*/, monitor, null /*headers*/); - return Pair.of(result.getFirst(), result.getSecond().getStatusLine().getStatusCode()); + return Pair.of(result.getFirst(), result.getSecond().getResponseCode()); } /** @@ -328,8 +389,7 @@ public class DownloadCache { * cache and instead use the {@link #openDirectUrl} method. * <p/> * For details on realm authentication and user/password handling, - * check the underlying {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])} - * documentation. + * see {@link HttpConfigurable#openHttpConnection(String)}. * * @param urlString the URL string to be opened. * @param monitor {@link ITaskMonitor} which is related to this URL @@ -347,7 +407,7 @@ public class DownloadCache { throws IOException, CanceledByUserException { // Don't cache in direct mode. if (mStrategy == Strategy.DIRECT) { - Pair<InputStream, HttpResponse> result = openUrl( + Pair<InputStream, HttpURLConnection> result = openUrl( urlString, true /*needsMarkResetSupport*/, monitor, @@ -612,20 +672,19 @@ public class DownloadCache { byte[] result = new byte[inc]; try { - Pair<InputStream, HttpResponse> r = + Pair<InputStream, HttpURLConnection> r = openUrl(urlString, true /*needsMarkResetSupport*/, monitor, headers); is = r.getFirst(); - HttpResponse response = r.getSecond(); + HttpURLConnection connection = r.getSecond(); if (DEBUG) { System.out.println(String.format("%s : fetch: %s => %s", //$NON-NLS-1$ - urlString, - headers == null ? "" : Arrays.toString(headers), //$NON-NLS-1$ - response.getStatusLine())); + urlString, headers == null ? "" : Arrays.toString(headers), //$NON-NLS-1$ + connection.getResponseMessage())); } - int code = response.getStatusLine().getStatusCode(); + int code = connection.getResponseCode(); if (outStatusCode != null) { outStatusCode.set(code); @@ -674,7 +733,7 @@ public class DownloadCache { os.close(); os = null; - saveInfo(urlString, response, info); + saveInfo(urlString, connection, info); } catch (IOException ignore) {} } @@ -706,7 +765,7 @@ public class DownloadCache { */ private void saveInfo( @NonNull String urlString, - @NonNull HttpResponse response, + @NonNull HttpURLConnection connection, @NonNull File info) throws IOException { Properties props = new Properties(); @@ -714,12 +773,12 @@ public class DownloadCache { // Save it in case we want to have it later (e.g. to differentiate 200 and 404.) props.setProperty(KEY_URL, urlString); props.setProperty(KEY_STATUS_CODE, - Integer.toString(response.getStatusLine().getStatusCode())); + Integer.toString(connection.getResponseCode())); for (String name : INFO_HTTP_HEADERS) { - Header h = response.getFirstHeader(name); + String h = connection.getHeaderField(name); if (h != null) { - props.setProperty(name, h.getValue()); + props.setProperty(name, h); } } diff --git a/android/src/com/android/tools/idea/sdk/remote/internal/UrlOpener.java b/android/src/com/android/tools/idea/sdk/remote/internal/UrlOpener.java deleted file mode 100644 index 6744de31ed2..00000000000 --- a/android/src/com/android/tools/idea/sdk/remote/internal/UrlOpener.java +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tools.idea.sdk.remote.internal; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.utils.Pair; -import org.apache.http.*; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.AuthState; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.NTCredentials; -import org.apache.http.auth.params.AuthPNames; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.params.AuthPolicy; -import org.apache.http.client.protocol.ClientContext; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.ProxySelectorRoutePlanner; -import org.apache.http.message.BasicHttpResponse; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; -import org.apache.http.protocol.BasicHttpContext; -import org.apache.http.protocol.HttpContext; - -import java.io.*; -import java.net.*; -import java.util.*; -import java.util.Map.Entry; - -/** - * This class holds static methods for downloading URL resources. - * - * @see #openUrl(String, boolean, ITaskMonitor, Header[]) - * <p/> - * Implementation detail: callers should use {@link DownloadCache} instead of this class. - * {@link DownloadCache#openDirectUrl} is a direct pass-through to {@link UrlOpener} since - * there's no caching. However from an implementation perspective it's still recommended - * to pass down a {@link DownloadCache} instance, which will let us override the implementation - * later on (for testing, for example.) - */ -class UrlOpener { - - private static final boolean DEBUG = System.getenv("ANDROID_DEBUG_URL_OPENER") != null; //$NON-NLS-1$ - - private static Map<String, UserCredentials> sRealmCache = new HashMap<String, UserCredentials>(); - - /** - * Timeout to establish a connection, in milliseconds. - */ - private static int sConnectionTimeoutMs; - /** - * Timeout waiting for data on a socket, in milliseconds. - */ - private static int sSocketTimeoutMs; - - static { - if (DEBUG) { - Properties props = System.getProperties(); - for (String key : new String[]{"http.proxyHost", //$NON-NLS-1$ - "http.proxyPort", //$NON-NLS-1$ - "https.proxyHost", //$NON-NLS-1$ - "https.proxyPort"}) { //$NON-NLS-1$ - String prop = props.getProperty(key); - if (prop != null) { - System.out.printf("SdkLib.UrlOpener Java.Prop %s='%s'\n", //$NON-NLS-1$ - key, prop); - } - } - } - - try { - sConnectionTimeoutMs = Integer.parseInt(System.getenv("ANDROID_SDKMAN_CONN_TIMEOUT")); - } - catch (Exception ignore) { - sConnectionTimeoutMs = 2 * 60 * 1000; - } - - try { - sSocketTimeoutMs = Integer.parseInt(System.getenv("ANDROID_SDKMAN_READ_TIMEOUT")); - } - catch (Exception ignore) { - sSocketTimeoutMs = 1 * 60 * 1000; - } - } - - /** - * This class cannot be instantiated. - * - * @see #openUrl(String, boolean, ITaskMonitor, Header[]) - */ - private UrlOpener() { - } - - /** - * Opens a URL. It can be a simple URL or one which requires basic - * authentication. - * <p/> - * Tries to access the given URL. If http response is either - * {@code HttpStatus.SC_UNAUTHORIZED} or - * {@code HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED}, asks for - * login/password and tries to authenticate into proxy server and/or URL. - * <p/> - * This implementation relies on the Apache Http Client due to its - * capabilities of proxy/http authentication. <br/> - * Proxy configuration is determined by {@link ProxySelectorRoutePlanner} using the JVM proxy - * settings by default. - * <p/> - * For more information see: <br/> - * - {@code http://hc.apache.org/httpcomponents-client-ga/} <br/> - * - {@code http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/conn/ProxySelectorRoutePlanner.html} - * <p/> - * There's a very simple realm cache implementation. - * Login/Password for each realm are stored in a static {@link Map}. - * Before asking the user the method verifies if the information is already - * available in the memory cache. - * - * @param url the URL string to be opened. - * @param needsMarkResetSupport Indicates the caller <em>must</em> have an input stream that - * supports the mark/reset operations (as indicated by {@link InputStream#markSupported()}. - * Implementation detail: If the original stream does not, it will be fetched and wrapped - * into a {@link ByteArrayInputStream}. This can only work sanely if the resource is a - * small file that can fit in memory. It also means the caller has no chance of showing - * a meaningful download progress. If unsure, callers should set this to false. - * @param monitor {@link ITaskMonitor} to output status. - * @param headers An optional array of HTTP headers to use in the GET request. - * @return Returns a {@link Pair} with {@code first} holding an {@link InputStream} - * and {@code second} holding an {@link HttpResponse}. - * The returned pair is never null and contains - * at least a code; for http requests that provide them the response - * also contains locale, headers and an status line. - * The input stream can be null, especially in case of error. - * The caller must only accept the stream if the response code is 200 or similar. - * @throws IOException Exception thrown when there are problems retrieving - * the URL or its content. - * @throws CanceledByUserException Exception thrown if the user cancels the - * authentication dialog. - */ - @NonNull - static Pair<InputStream, HttpResponse> openUrl(@NonNull String url, - boolean needsMarkResetSupport, - @NonNull ITaskMonitor monitor, - @Nullable Header[] headers) throws IOException, CanceledByUserException { - - Exception fallbackOnJavaUrlConnect = null; - Pair<InputStream, HttpResponse> result = null; - - try { - result = openWithHttpClient(url, monitor, headers); - - } - catch (UnknownHostException e) { - // Host in unknown. No need to even retry with the Url object, - // if it's broken, it's broken. It's already an IOException but - // it could use a better message. - throw new IOException("Unknown Host " + e.getMessage(), e); - - } - catch (ClientProtocolException e) { - // We get this when HttpClient fails to accept the current protocol, - // e.g. when processing file:// URLs. - fallbackOnJavaUrlConnect = e; - - } - catch (IOException e) { - throw e; - - } - catch (CanceledByUserException e) { - // HTTP Basic Auth or NTLM login was canceled by user. - throw e; - - } - catch (Exception e) { - if (DEBUG) { - System.out.printf("[HttpClient Error] %s : %s\n", url, e.toString()); - } - - fallbackOnJavaUrlConnect = e; - } - - if (fallbackOnJavaUrlConnect != null) { - // If the protocol is not supported by HttpClient (e.g. file:///), - // revert to the standard java.net.Url.open. - - try { - result = openWithUrl(url, headers); - } - catch (IOException e) { - throw e; - } - catch (Exception e) { - if (DEBUG && !fallbackOnJavaUrlConnect.equals(e)) { - System.out.printf("[Url Error] %s : %s\n", url, e.toString()); - } - } - } - - // If the caller requires an InputStream that supports mark/reset, let's - // make sure we have such a stream. - if (result != null && needsMarkResetSupport) { - InputStream is = result.getFirst(); - if (is != null) { - if (!is.markSupported()) { - try { - // Consume the whole input stream and offer a byte array stream instead. - // This can only work sanely if the resource is a small file that can - // fit in memory. It also means the caller has no chance of showing - // a meaningful download progress. - InputStream is2 = toByteArrayInputStream(is); - if (is2 != null) { - result = Pair.of(is2, result.getSecond()); - try { - is.close(); - } - catch (Exception ignore) { - } - } - } - catch (Exception e3) { - // Ignore. If this can't work, caller will fail later. - } - } - } - } - - if (result == null) { - // Make up an error code if we don't have one already. - HttpResponse outResponse = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 0), //$NON-NLS-1$ - HttpStatus.SC_METHOD_FAILURE, ""); //$NON-NLS-1$; // 420=Method Failure - result = Pair.of(null, outResponse); - } - - return result; - } - - // ByteArrayInputStream is the duct tape of input streams. - private static InputStream toByteArrayInputStream(InputStream is) throws IOException { - int inc = 4096; - int curr = 0; - byte[] result = new byte[inc]; - - int n; - while ((n = is.read(result, curr, result.length - curr)) != -1) { - curr += n; - if (curr == result.length) { - byte[] temp = new byte[curr + inc]; - System.arraycopy(result, 0, temp, 0, curr); - result = temp; - } - } - - return new ByteArrayInputStream(result, 0, curr); - } - - private static Pair<InputStream, HttpResponse> openWithUrl(String url, Header[] inHeaders) throws IOException { - URL u = new URL(url); - - URLConnection c = u.openConnection(); - - c.setConnectTimeout(sConnectionTimeoutMs); - c.setReadTimeout(sSocketTimeoutMs); - - if (inHeaders != null) { - for (Header header : inHeaders) { - c.setRequestProperty(header.getName(), header.getValue()); - } - } - - // Trigger the access to the resource - // (at which point setRequestProperty can't be used anymore.) - int code = 200; - - if (c instanceof HttpURLConnection) { - code = ((HttpURLConnection)c).getResponseCode(); - } - - // Get the input stream. That can fail for a file:// that doesn't exist - // in which case we set the response code to 404. - // Also we need a buffered input stream since the caller need to use is.reset(). - InputStream is = null; - try { - is = new BufferedInputStream(c.getInputStream()); - } - catch (Exception ignore) { - if (is == null && code == 200) { - code = 404; - } - } - - HttpResponse outResponse = new BasicHttpResponse(new ProtocolVersion(u.getProtocol(), 1, 0), // make up the protocol version - code, ""); //$NON-NLS-1$; - - Map<String, List<String>> outHeaderMap = c.getHeaderFields(); - - for (Entry<String, List<String>> entry : outHeaderMap.entrySet()) { - String name = entry.getKey(); - if (name != null) { - List<String> values = entry.getValue(); - if (!values.isEmpty()) { - outResponse.setHeader(name, values.get(0)); - } - } - } - - return Pair.of(is, outResponse); - } - - @NonNull - private static Pair<InputStream, HttpResponse> openWithHttpClient(@NonNull String url, @NonNull ITaskMonitor monitor, Header[] inHeaders) - throws IOException, CanceledByUserException { - UserCredentials result = null; - String realm = null; - - HttpParams params = new BasicHttpParams(); - HttpConnectionParams.setConnectionTimeout(params, sConnectionTimeoutMs); - HttpConnectionParams.setSoTimeout(params, sSocketTimeoutMs); - - // use the simple one - final DefaultHttpClient httpClient = new DefaultHttpClient(params); - - - // create local execution context - HttpContext localContext = new BasicHttpContext(); - final HttpGet httpGet = new HttpGet(url); - if (inHeaders != null) { - for (Header header : inHeaders) { - httpGet.addHeader(header); - } - } - - // retrieve local java configured network in case there is the need to - // authenticate a proxy - ProxySelectorRoutePlanner routePlanner = - new ProxySelectorRoutePlanner(httpClient.getConnectionManager().getSchemeRegistry(), ProxySelector.getDefault()); - httpClient.setRoutePlanner(routePlanner); - - // Set preference order for authentication options. - // In particular, we don't add AuthPolicy.SPNEGO, which is given preference over NTLM in - // servers that support both, as it is more secure. However, we don't seem to handle it - // very well, so we leave it off the list. - // See http://hc.apache.org/httpcomponents-client-ga/tutorial/html/authentication.html for - // more info. - List<String> authpref = new ArrayList<String>(); - authpref.add(AuthPolicy.BASIC); - authpref.add(AuthPolicy.DIGEST); - authpref.add(AuthPolicy.NTLM); - httpClient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF, authpref); - httpClient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF, authpref); - - if (DEBUG) { - try { - URI uri = new URI(url); - ProxySelector sel = routePlanner.getProxySelector(); - if (sel != null && uri.getScheme().startsWith("httP")) { //$NON-NLS-1$ - List<Proxy> list = sel.select(uri); - System.out.printf("SdkLib.UrlOpener:\n Connect to: %s\n Proxy List: %s\n", //$NON-NLS-1$ - url, list == null ? "(null)" : Arrays.toString(list.toArray()));//$NON-NLS-1$ - } - } - catch (Exception e) { - System.out.printf("SdkLib.UrlOpener: Failed to get proxy info for %s: %s\n", //$NON-NLS-1$ - url, e.toString()); - } - } - - boolean trying = true; - // loop while the response is being fetched - while (trying) { - // connect and get status code - HttpResponse response = httpClient.execute(httpGet, localContext); - int statusCode = response.getStatusLine().getStatusCode(); - - if (DEBUG) { - System.out.printf(" Status: %d\n", statusCode); //$NON-NLS-1$ - } - - // check whether any authentication is required - AuthState authenticationState = null; - if (statusCode == HttpStatus.SC_UNAUTHORIZED) { - // Target host authentication required - authenticationState = (AuthState)localContext.getAttribute(ClientContext.TARGET_AUTH_STATE); - } - if (statusCode == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) { - // Proxy authentication required - authenticationState = (AuthState)localContext.getAttribute(ClientContext.PROXY_AUTH_STATE); - } - if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NOT_MODIFIED) { - // in case the status is OK and there is a realm and result, - // cache it - if (realm != null && result != null) { - sRealmCache.put(realm, result); - } - } - - // there is the need for authentication - if (authenticationState != null) { - - // get scope and realm - AuthScope authScope = authenticationState.getAuthScope(); - - // If the current realm is different from the last one it means - // a pass was performed successfully to the last URL, therefore - // cache the last realm - if (realm != null && !realm.equals(authScope.getRealm())) { - sRealmCache.put(realm, result); - } - - realm = authScope.getRealm(); - - // in case there is cache for this Realm, use it to authenticate - if (sRealmCache.containsKey(realm)) { - result = sRealmCache.get(realm); - } - else { - // since there is no cache, request for login and password - result = monitor.displayLoginCredentialsPrompt("Site Authentication", "Please login to the following domain: " + - realm + - "\n\nServer requiring authentication:\n" + - authScope.getHost()); - if (result == null) { - throw new CanceledByUserException("User canceled login dialog."); - } - } - - // retrieve authentication data - String user = result.getUserName(); - String password = result.getPassword(); - String workstation = result.getWorkstation(); - String domain = result.getDomain(); - - // proceed in case there is indeed a user - if (user != null && user.length() > 0) { - Credentials credentials = new NTCredentials(user, password, workstation, domain); - httpClient.getCredentialsProvider().setCredentials(authScope, credentials); - trying = true; - } - else { - trying = false; - } - } - else { - trying = false; - } - - HttpEntity entity = response.getEntity(); - - if (entity != null) { - if (trying) { - // in case another pass to the Http Client will be performed, close the entity. - entity.getContent().close(); - } - else { - // since no pass to the Http Client is needed, retrieve the - // entity's content. - - // Note: don't use something like a BufferedHttpEntity since it would consume - // all content and store it in memory, resulting in an OutOfMemory exception - // on a large download. - InputStream is = new FilterInputStream(entity.getContent()) { - @Override - public void close() throws IOException { - // Since Http Client is no longer needed, close it. - - // Bug #21167: we need to tell http client to shutdown - // first, otherwise the super.close() would continue - // downloading and not return till complete. - - httpClient.getConnectionManager().shutdown(); - super.close(); - } - }; - - HttpResponse outResponse = new BasicHttpResponse(response.getStatusLine()); - outResponse.setHeaders(response.getAllHeaders()); - outResponse.setLocale(response.getLocale()); - - return Pair.of(is, outResponse); - } - } - else if (statusCode == HttpStatus.SC_NOT_MODIFIED) { - // It's ok to not have an entity (e.g. nothing to download) for a 304 - HttpResponse outResponse = new BasicHttpResponse(response.getStatusLine()); - outResponse.setHeaders(response.getAllHeaders()); - outResponse.setLocale(response.getLocale()); - - return Pair.of(null, outResponse); - } - } - - // We get here if we did not succeed. Callers do not expect a null result. - httpClient.getConnectionManager().shutdown(); - throw new FileNotFoundException(url); - } -} diff --git a/android/src/com/android/tools/idea/sdk/remote/internal/archives/ArchiveInstaller.java b/android/src/com/android/tools/idea/sdk/remote/internal/archives/ArchiveInstaller.java index f68f4f58f34..ab57a3a294e 100755 --- a/android/src/com/android/tools/idea/sdk/remote/internal/archives/ArchiveInstaller.java +++ b/android/src/com/android/tools/idea/sdk/remote/internal/archives/ArchiveInstaller.java @@ -43,6 +43,7 @@ import org.apache.http.HttpStatus; import org.apache.http.message.BasicHeader; import java.io.*; +import java.net.HttpURLConnection; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; @@ -363,11 +364,11 @@ public class ArchiveInstaller { InputStream is = null; int inc_remain = NUM_MONITOR_INC; try { - Pair<InputStream, HttpResponse> result = cache.openDirectUrl(urlString, resumeHeaders, monitor); + Pair<InputStream, HttpURLConnection> result = cache.openDirectUrl(urlString, resumeHeaders, monitor); is = result.getFirst(); - HttpResponse resp = result.getSecond(); - int status = resp.getStatusLine().getStatusCode(); + HttpURLConnection connection = result.getSecond(); + int status = connection.getResponseCode(); if (status == HttpStatus.SC_NOT_FOUND) { throw new Exception("URL not found."); } @@ -378,11 +379,13 @@ public class ArchiveInstaller { Properties props = new Properties(); props.setProperty(PROP_STATUS_CODE, Integer.toString(status)); - if (resp.containsHeader(HttpHeaders.ETAG)) { - props.setProperty(HttpHeaders.ETAG, resp.getFirstHeader(HttpHeaders.ETAG).getValue()); + String etag = connection.getHeaderField(HttpHeaders.ETAG); + if (etag != null) { + props.setProperty(HttpHeaders.ETAG, etag); } - if (resp.containsHeader(HttpHeaders.LAST_MODIFIED)) { - props.setProperty(HttpHeaders.LAST_MODIFIED, resp.getFirstHeader(HttpHeaders.LAST_MODIFIED).getValue()); + String lastModified = connection.getHeaderField(HttpHeaders.LAST_MODIFIED); + if (lastModified != null) { + props.setProperty(HttpHeaders.LAST_MODIFIED, lastModified); } try { diff --git a/android/src/com/android/tools/idea/sdk/remote/internal/updater/SdkUpdaterNoWindow.java b/android/src/com/android/tools/idea/sdk/remote/internal/updater/SdkUpdaterNoWindow.java index 9b322319e13..e4556334638 100755 --- a/android/src/com/android/tools/idea/sdk/remote/internal/updater/SdkUpdaterNoWindow.java +++ b/android/src/com/android/tools/idea/sdk/remote/internal/updater/SdkUpdaterNoWindow.java @@ -27,7 +27,6 @@ import com.android.utils.Pair; import java.io.IOException; import java.util.ArrayList; -import java.util.Properties; /** * Performs an update using only a non-interactive console output with no GUI. @@ -57,7 +56,6 @@ public class SdkUpdaterNoWindow { * @param sdkLog A logger object, that should ideally output to a write-only console. * @param force The reply to any question asked by the update process. Currently this will * be yes/no for ability to replace modified samples or restart ADB. - * @param useHttp True to force using HTTP instead of HTTPS for downloads. * @param proxyPort An optional HTTP/HTTPS proxy port. Can be null. * @param proxyHost An optional HTTP/HTTPS proxy host. Can be null. */ @@ -65,23 +63,12 @@ public class SdkUpdaterNoWindow { SdkManager sdkManager, ILogger sdkLog, boolean force, - boolean useHttp, String proxyHost, String proxyPort) { mSdkLog = sdkLog; mForce = force; mUpdaterData = new UpdaterData(osSdkRoot, sdkLog); - // Read and apply settings from settings file, so that http/https proxy is set - // and let the command line args override them as necessary. - SettingsController settingsController = mUpdaterData.getSettingsController(); - settingsController.loadSettings(); - settingsController.applySettings(); - setupProxy(proxyHost, proxyPort); - - // Change the in-memory settings to force the http/https mode - settingsController.setSetting(SettingsController.KEY_FORCE_HTTP, useHttp); - // Use a factory that only outputs to the given ILogger. mUpdaterData.setTaskFactory(new ConsoleTaskFactory()); @@ -111,31 +98,6 @@ public class SdkUpdaterNoWindow { // ----- /** - * Sets both the HTTP and HTTPS proxy system properties, overriding the ones - * from the settings with these values if they are defined. - */ - private void setupProxy(String proxyHost, String proxyPort) { - - // The system property constants can be found in the Java SE documentation at - // http://download.oracle.com/javase/6/docs/technotes/guides/net/proxies.html - final String JAVA_PROP_HTTP_PROXY_HOST = "http.proxyHost"; //$NON-NLS-1$ - final String JAVA_PROP_HTTP_PROXY_PORT = "http.proxyPort"; //$NON-NLS-1$ - final String JAVA_PROP_HTTPS_PROXY_HOST = "https.proxyHost"; //$NON-NLS-1$ - final String JAVA_PROP_HTTPS_PROXY_PORT = "https.proxyPort"; //$NON-NLS-1$ - - Properties props = System.getProperties(); - - if (proxyHost != null && proxyHost.length() > 0) { - props.setProperty(JAVA_PROP_HTTP_PROXY_HOST, proxyHost); - props.setProperty(JAVA_PROP_HTTPS_PROXY_HOST, proxyHost); - } - if (proxyPort != null && proxyPort.length() > 0) { - props.setProperty(JAVA_PROP_HTTP_PROXY_PORT, proxyPort); - props.setProperty(JAVA_PROP_HTTPS_PROXY_PORT, proxyPort); - } - } - - /** * A custom implementation of {@link ITaskFactory} that * provides {@link ConsoleTaskMonitor} objects. */ diff --git a/android/src/com/android/tools/idea/sdk/remote/internal/updater/SettingsController.java b/android/src/com/android/tools/idea/sdk/remote/internal/updater/SettingsController.java index aeb159626ef..03321d7eb93 100755 --- a/android/src/com/android/tools/idea/sdk/remote/internal/updater/SettingsController.java +++ b/android/src/com/android/tools/idea/sdk/remote/internal/updater/SettingsController.java @@ -24,6 +24,8 @@ import com.android.sdklib.io.FileOp; import com.android.sdklib.io.IFileOp; import com.android.tools.idea.sdk.remote.internal.DownloadCache; import com.android.utils.ILogger; +import com.intellij.openapi.components.*; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.net.URL; @@ -31,243 +33,56 @@ import java.util.Map.Entry; import java.util.Properties; /** - * Controller class to get settings values. Settings are kept in-memory. - * Users of this class must first load the settings before changing them and save - * them when modified. - * <p/> - * Settings are enumerated by constants in {@link SettingsController}. + * Controller class to get settings values using intellij persistent data mechanism. + * Compare to {@link com.android.sdklib.internal.repository.updater.SettingsController} + * which uses a file maintained separately. */ -public class SettingsController { - /** - * Java system setting picked up by {@link URL} for http proxy port. - * Type: String. - */ - public static final String KEY_HTTP_PROXY_PORT = "http.proxyPort"; //$NON-NLS-1$ - - /** - * Java system setting picked up by {@link URL} for http proxy host. - * Type: String. - */ - public static final String KEY_HTTP_PROXY_HOST = "http.proxyHost"; //$NON-NLS-1$ - - /** - * Setting to force using http:// instead of https:// connections. - * Type: Boolean. - * Default: False. - */ - public static final String KEY_FORCE_HTTP = "sdkman.force.http"; //$NON-NLS-1$ - - /** - * Setting to display only packages that are new or updates. - * Type: Boolean. - * Default: True. - */ - public static final String KEY_SHOW_UPDATE_ONLY = "sdkman.show.update.only"; //$NON-NLS-1$ - - /** - * Setting to ask for permission before restarting ADB. - * Type: Boolean. - * Default: False. - */ - public static final String KEY_ASK_ADB_RESTART = "sdkman.ask.adb.restart"; //$NON-NLS-1$ - - /** - * Setting to use the {@link DownloadCache}, for small manifest XML files. - * Type: Boolean. - * Default: True. - */ - public static final String KEY_USE_DOWNLOAD_CACHE = "sdkman.use.dl.cache"; //$NON-NLS-1$ - - private static final String SETTINGS_FILENAME = "androidtool.cfg"; //$NON-NLS-1$ - - private final IFileOp mFileOp; - private final ILogger mSdkLog; - private final Settings mSettings; - - /** - * Constructs a new default {@link SettingsController}. - * - * @param sdkLog A non-null logger to use. - */ - public SettingsController(@NonNull ILogger sdkLog) { - this(new FileOp(), sdkLog); +@State( + name = "SettingsController", + storages = { + @Storage(file = StoragePathMacros.APP_CONFIG + "/remotesdk.xml", roamingType = RoamingType.DISABLED), } +) +public class SettingsController implements PersistentStateComponent<SettingsController.PersistentState> { - /** - * Constructs a new default {@link SettingsController}. - * - * @param fileOp A non-null {@link FileOp} to perform file operations (to load/save settings.) - * @param sdkLog A non-null logger to use. - */ - public SettingsController(@NonNull IFileOp fileOp, @NonNull ILogger sdkLog) { - this(fileOp, sdkLog, new Settings()); - } + private PersistentState myState = new PersistentState(); - /** - * Specialized constructor that wraps an existing {@link Settings} instance. - * This is mostly used in unit-tests to override settings that are being used. - * Normal usage should NOT need to call this constructor. - * - * @param fileOp A non-null {@link FileOp} to perform file operations (to load/save settings) - * @param sdkLog A non-null logger to use. - * @param settings A non-null {@link Settings} to use as-is. It is not duplicated. - */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - protected SettingsController(@NonNull IFileOp fileOp, @NonNull ILogger sdkLog, @NonNull Settings settings) { - mFileOp = fileOp; - mSdkLog = sdkLog; - mSettings = settings; + public boolean getForceHttp() { + return myState.myForceHttp; } - @NonNull - public Settings getSettings() { - return mSettings; + public boolean getAskBeforeAdbRestart() { + return myState.myAskBeforeAdbRestart; } - //--- Access to settings ------------ - - - public static class Settings { - private final Properties mProperties; - - /** - * Initialize an empty set of settings. - */ - public Settings() { - mProperties = new Properties(); - } - - /** - * Duplicates a set of settings. - */ - public Settings(@NonNull Settings settings) { - this(); - for (Entry<Object, Object> entry : settings.mProperties.entrySet()) { - mProperties.put(entry.getKey(), entry.getValue()); - } - } - - /** - * Specialized constructor for unit-tests that wraps an existing - * {@link Properties} instance. The properties instance is not duplicated, - * it's merely used as-is and changes will be reflected directly. - */ - protected Settings(@NonNull Properties properties) { - mProperties = properties; - } - - /** - * Returns the value of the {@link KEY_FORCE_HTTP} setting. - * - * @see KEY_FORCE_HTTP - */ - public boolean getForceHttp() { - return Boolean.parseBoolean(mProperties.getProperty(KEY_FORCE_HTTP)); - } - - /** - * Returns the value of the {@link KEY_ASK_ADB_RESTART} setting. - * - * @see KEY_ASK_ADB_RESTART - */ - public boolean getAskBeforeAdbRestart() { - return Boolean.parseBoolean(mProperties.getProperty(KEY_ASK_ADB_RESTART)); - } - - /** - * Returns the value of the {@link KEY_USE_DOWNLOAD_CACHE} setting. - * - * @see KEY_USE_DOWNLOAD_CACHE - */ - public boolean getUseDownloadCache() { - return Boolean.parseBoolean(mProperties.getProperty(KEY_USE_DOWNLOAD_CACHE, Boolean.TRUE.toString())); - } - - /** - * Returns the value of the {@link KEY_SHOW_UPDATE_ONLY} setting. - * - * @see KEY_SHOW_UPDATE_ONLY - */ - public boolean getShowUpdateOnly() { - return Boolean.parseBoolean(mProperties.getProperty(KEY_SHOW_UPDATE_ONLY, Boolean.TRUE.toString())); - } + public boolean getUseDownloadCache() { + return myState.myAskBeforeAdbRestart; } - /** - * Sets the value of the {@link ISettingsPage#KEY_SHOW_UPDATE_ONLY} setting. - * - * @param enabled True if only compatible non-obsolete update items should be shown. - * @see ISettingsPage#KEY_SHOW_UPDATE_ONLY - */ - public void setShowUpdateOnly(boolean enabled) { - setSetting(KEY_SHOW_UPDATE_ONLY, enabled); + public void setForceHttp(boolean forceHttp) { + myState.myForceHttp = forceHttp; } - /** - * Internal helper to set a boolean setting. - */ - void setSetting(@NonNull String key, boolean value) { - mSettings.mProperties.setProperty(key, Boolean.toString(value)); + @Nullable + @Override + public PersistentState getState() { + return myState; } - //--- Controller methods ------------- - - /** - * Load settings from the settings file. - */ - public void loadSettings() { - - String path = null; - try { - String folder = AndroidLocation.getFolder(); - File f = new File(folder, SETTINGS_FILENAME); - path = f.getPath(); - - Properties props = mFileOp.loadProperties(f); - mSettings.mProperties.clear(); - mSettings.mProperties.putAll(props); - - // Properly reformat some settings to enforce their default value when missing. - setShowUpdateOnly(mSettings.getShowUpdateOnly()); - setSetting(KEY_ASK_ADB_RESTART, mSettings.getAskBeforeAdbRestart()); - setSetting(KEY_USE_DOWNLOAD_CACHE, mSettings.getUseDownloadCache()); - - } - catch (Exception e) { - if (mSdkLog != null) { - mSdkLog.error(e, "Failed to load settings from .android folder. Path is '%1$s'.", path); - } - } + @Override + public void loadState(PersistentState state) { + myState = state; } - /** - * Applies the current settings. - */ - public void applySettings() { - Properties props = System.getProperties(); - - // Get the configured HTTP proxy settings - String proxyHost = mSettings.mProperties.getProperty(KEY_HTTP_PROXY_HOST, ""); //$NON-NLS-1$ - String proxyPort = mSettings.mProperties.getProperty(KEY_HTTP_PROXY_PORT, ""); //$NON-NLS-1$ - - // Set both the HTTP and HTTPS proxy system properties. - // The system property constants can be found in the Java SE documentation at - // http://download.oracle.com/javase/6/docs/technotes/guides/net/proxies.html - final String JAVA_PROP_HTTP_PROXY_HOST = "http.proxyHost"; //$NON-NLS-1$ - final String JAVA_PROP_HTTP_PROXY_PORT = "http.proxyPort"; //$NON-NLS-1$ - final String JAVA_PROP_HTTPS_PROXY_HOST = "https.proxyHost"; //$NON-NLS-1$ - final String JAVA_PROP_HTTPS_PROXY_PORT = "https.proxyPort"; //$NON-NLS-1$ + public static SettingsController getInstance() { + return ServiceManager.getService(SettingsController.class); + } - // Only change the proxy if have something in the preferences. - // Do not erase the default settings by empty values. - if (proxyHost != null && proxyHost.length() > 0) { - props.setProperty(JAVA_PROP_HTTP_PROXY_HOST, proxyHost); - props.setProperty(JAVA_PROP_HTTPS_PROXY_HOST, proxyHost); - } - if (proxyPort != null && proxyPort.length() > 0) { - props.setProperty(JAVA_PROP_HTTP_PROXY_PORT, proxyPort); - props.setProperty(JAVA_PROP_HTTPS_PROXY_PORT, proxyPort); - } + public static class PersistentState { + public boolean myForceHttp; + public boolean myAskBeforeAdbRestart; + public boolean myUseDownloadCache; } + private SettingsController() {} } diff --git a/android/src/com/android/tools/idea/sdk/remote/internal/updater/UpdaterData.java b/android/src/com/android/tools/idea/sdk/remote/internal/updater/UpdaterData.java index fddb5ceac66..7aba9e402f8 100755 --- a/android/src/com/android/tools/idea/sdk/remote/internal/updater/UpdaterData.java +++ b/android/src/com/android/tools/idea/sdk/remote/internal/updater/UpdaterData.java @@ -54,7 +54,7 @@ import java.util.*; /** * Data shared by the SDK Manager updaters. */ -public class UpdaterData implements IUpdaterData { +public class UpdaterData { public static final int NO_TOOLS_MSG = 0; public static final int TOOLS_MSG_UPDATED_FROM_ADT = 1; @@ -67,11 +67,6 @@ public class UpdaterData implements IUpdaterData { * Instead use {@link #getSources()} so that unit tests can override this as needed. */ private final SdkSources mSources = new SdkSources(); - /** - * Holds settings. Do not use this directly. - * Instead use {@link #getSettingsController()} so that unit tests can override this. - */ - private final SettingsController mSettingsController; private final ArrayList<ISdkChangeListener> mListeners = new ArrayList<ISdkChangeListener>(); private final ILogger mSdkLog; private ITaskFactory mTaskFactory; @@ -93,7 +88,6 @@ public class UpdaterData implements IUpdaterData { mOsSdkRoot = osSdkRoot; mSdkLog = sdkLog; - mSettingsController = initSettingsController(); initSdk(); } @@ -103,11 +97,10 @@ public class UpdaterData implements IUpdaterData { return mOsSdkRoot; } - @Override public DownloadCache getDownloadCache() { if (mDownloadCache == null) { mDownloadCache = new DownloadCache( - getSettingsController().getSettings().getUseDownloadCache() ? DownloadCache.Strategy.FRESH_CACHE : DownloadCache.Strategy.DIRECT); + SettingsController.getInstance().getUseDownloadCache() ? DownloadCache.Strategy.FRESH_CACHE : DownloadCache.Strategy.DIRECT); } return mDownloadCache; } @@ -116,30 +109,10 @@ public class UpdaterData implements IUpdaterData { mTaskFactory = taskFactory; } - @Override - public ITaskFactory getTaskFactory() { - return mTaskFactory; - } - - public SdkSources getSources() { - return mSources; - } - - @Override - public ILogger getSdkLog() { - return mSdkLog; - } - - @Override public SdkManager getSdkManager() { return mSdkManager; } - @Override - public SettingsController getSettingsController() { - return mSettingsController; - } - /** * Removes a listener ({@link ISdkChangeListener}) that is notified when the SDK is reloaded. */ @@ -147,10 +120,6 @@ public class UpdaterData implements IUpdaterData { mListeners.remove(listener); } - protected void displayInitError(String error) { - mSdkLog.error(null /* Throwable */, "%s", error); //$NON-NLS-1$ - } - // ----- /** @@ -174,16 +143,6 @@ public class UpdaterData implements IUpdaterData { broadcastOnSdkReload(); } - /** - * Initializes the {@link SettingsController} - * Extracted so that we can override this in unit tests. - */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - protected SettingsController initSettingsController() { - SettingsController settingsController = new SettingsController(mSdkLog); - return settingsController; - } - @VisibleForTesting(visibility = Visibility.PRIVATE) protected void setSdkManager(SdkManager sdkManager) { mSdkManager = sdkManager; @@ -212,8 +171,6 @@ public class UpdaterData implements IUpdaterData { * - and finally the extra user repo URLs from the environment. */ public void setupDefaultSources() { - SdkSources sources = getSources(); - // Load the conventional sources. // For testing, the env var can be set to replace the default root download URL. // It must end with a / and its the location where the updater will look for @@ -224,11 +181,11 @@ public class UpdaterData implements IUpdaterData { baseUrl = SdkRepoConstants.URL_GOOGLE_SDK_SITE; } - sources.add(SdkSourceCategory.ANDROID_REPO, new SdkRepoSource(baseUrl, SdkSourceCategory.ANDROID_REPO.getUiName())); + mSources.add(SdkSourceCategory.ANDROID_REPO, new SdkRepoSource(baseUrl, SdkSourceCategory.ANDROID_REPO.getUiName())); // Load user sources (this will also notify change listeners but this operation is // done early enough that there shouldn't be any anyway.) - sources.loadUserAddons(getSdkLog()); + mSources.loadUserAddons(mSdkLog); } /** @@ -248,7 +205,7 @@ public class UpdaterData implements IUpdaterData { // this will accumulate all the packages installed. final List<Archive> newlyInstalledArchives = new ArrayList<Archive>(); - final boolean forceHttp = getSettingsController().getSettings().getForceHttp(); + final boolean forceHttp = SettingsController.getInstance().getForceHttp(); // sort all archives based on their dependency level. Collections.sort(archives, new InstallOrderComparator()); @@ -449,7 +406,7 @@ public class UpdaterData implements IUpdaterData { */ protected void askForAdbRestart(ITaskMonitor monitor) { // Restart ADB if we don't need to ask. - if (!getSettingsController().getSettings().getAskBeforeAdbRestart()) { + if (!SettingsController.getInstance().getAskBeforeAdbRestart()) { AdbWrapper adb = new AdbWrapper(getOsSdkRoot(), monitor); adb.stopAdb(); adb.startAdb(); diff --git a/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.java b/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.java index 9368c1c9b32..e8c829af58a 100755 --- a/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.java +++ b/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.java @@ -213,7 +213,6 @@ public class SmwOldApiDirectInstall extends DynamicWizardStepWithDescription { myLogger, false, // force -- The reply to any question asked by the update process. // Currently this will be yes/no for ability to replace modified samples, restart ADB, restart on locked win folder. - false, // useHttp -- True to force using HTTP instead of HTTPS for downloads. null, // proxyPort -- An optional HTTP/HTTPS proxy port. Can be null. -- Can we get it from Studio? null); // proxyHost -- An optional HTTP/HTTPS proxy host. Can be null. -- Can we get it from Studio? diff --git a/android/src/com/android/tools/idea/structure/services/ServiceXmlParser.java b/android/src/com/android/tools/idea/structure/services/ServiceXmlParser.java index 132dd62720c..b96f0c14364 100644 --- a/android/src/com/android/tools/idea/structure/services/ServiceXmlParser.java +++ b/android/src/com/android/tools/idea/structure/services/ServiceXmlParser.java @@ -153,8 +153,23 @@ import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; else if (tagName.equals(Schema.UiGrid.TAG)) { parseUiGridTag(attributes); } - else if (tagName.equals(Schema.UiItem.TAG)) { - parseUiItemTag(attributes); + else if (tagName.equals(Schema.UiButton.TAG)) { + parseUiButton(attributes); + } + else if (tagName.equals(Schema.UiCheckbox.TAG)) { + parseUiCheckbox(attributes); + } + else if (tagName.equals(Schema.UiInput.TAG)) { + parseUiInput(attributes); + } + else if (tagName.equals(Schema.UiLabel.TAG)) { + parseUiLabel(attributes); + } + else if (tagName.equals(Schema.UiLink.TAG)) { + parseUiLink(attributes); + } + else if (tagName.equals(Schema.UiPulldown.TAG)) { + parseUiPulldown(attributes); } else { LOG.warn("WARNING: Unknown service directive " + tagName); @@ -316,74 +331,29 @@ import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; } private void parseUiGridTag(@NotNull Attributes attributes) { - parseGridCoords(attributes); + parseRowCol(attributes); String weights = requireAttr(attributes, Schema.UiGrid.ATTR_COL_DEFINITIONS); JPanel grid = myPanelBuilder.startGrid(weights); - bindComponentProperties(grid, attributes); + bindTopLevelProperties(grid, attributes); } private void closeUiGridTag() { myPanelBuilder.endGrid(); } - private void parseUiItemTag(@NotNull Attributes attributes) { - parseGridCoords(attributes); - - String type = requireAttr(attributes, Schema.UiItem.ATTR_TYPE); - if (type.equals(Schema.UiItem.Type.VALUE_BUTTON)) { - JButton button = myPanelBuilder.addButton(); - bindButtonProperties(button, attributes); - bindComponentProperties(button, attributes); - } - else if (type.equals(Schema.UiItem.Type.VALUE_CHECKBOX)) { - JCheckBox checkbox = myPanelBuilder.addCheckbox(); - bindComponentProperties(checkbox, attributes); - bindCheckboxProperties(checkbox, attributes); - } - else if (type.equals(Schema.UiItem.Type.VALUE_INPUT)) { - JTextField field = myPanelBuilder.addField(); - bindComponentProperties(field, attributes); - bindFieldProperties(field, attributes); - } - else if (type.equals(Schema.UiItem.Type.VALUE_LABEL)) { - JLabel label = myPanelBuilder.addLabel(); - bindComponentProperties(label, attributes); - bindLabelProperties(label, attributes); - } - else if (type.equals(Schema.UiItem.Type.VALUE_LINK)) { - HyperlinkLabel link = myPanelBuilder.addLink(requireAttr(attributes, Schema.UiItem.Type.Text.ATTR_TEXT), - toUri(requireAttr(attributes, Schema.UiItem.Type.Link.ATTR_URL))); - bindComponentProperties(link, attributes); - } - else if (type.equals(Schema.UiItem.Type.VALUE_PULLDOWN)) { - String listKey = requireAttr(attributes, Schema.UiItem.Type.Pulldown.ATTR_LIST); - ObservableList<String> backingList = getList(listKey); - JComboBox comboBox = myPanelBuilder.addComboBox(backingList); - bindComponentProperties(comboBox, attributes); - bindComboBoxProperties(comboBox, attributes); - } - } - - private void parseGridCoords(@NotNull Attributes attributes) { - String row = attributes.getValue(Schema.UiTag.ATTR_ROW); - if (row != null) { - myPanelBuilder.setRow(Integer.parseInt(row)); - } - String col = attributes.getValue(Schema.UiTag.ATTR_COL); - if (col != null) { - myPanelBuilder.setCol(Integer.parseInt(col)); - } - } + private void parseUiButton(@NotNull Attributes attributes) { + parseRowCol(attributes); + JButton button = myPanelBuilder.addButton(); + bindTopLevelProperties(button, attributes); - private void bindButtonProperties(@NotNull JButton button, @NotNull Attributes attributes) { - String textKey = attributes.getValue(Schema.UiItem.Type.Text.ATTR_TEXT); + String textKey = attributes.getValue(Schema.UiButton.ATTR_TEXT); if (textKey != null) { TextProperty textProperty = new TextProperty(button); myPanelBuilder.getBindings().bind(textProperty, parseString(textKey)); } - String actionKey = attributes.getValue(Schema.UiItem.Type.Button.ATTR_ACTION); + String actionKey = attributes.getValue(Schema.UiButton.ATTR_ACTION); if (actionKey != null) { final Runnable action = parseAction(actionKey); button.addActionListener(new ActionListener() { @@ -394,29 +364,19 @@ import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; }); } } + + private void parseUiCheckbox(@NotNull Attributes attributes) { + parseRowCol(attributes); + JCheckBox checkbox = myPanelBuilder.addCheckbox(); + bindTopLevelProperties(checkbox, attributes); - private void bindComponentProperties(@NotNull JComponent component, @NotNull Attributes attributes) { - String visibleKey = attributes.getValue(Schema.UiItem.Type.Component.ATTR_VISIBLE); - if (visibleKey != null) { - VisibleProperty visibleProperty = new VisibleProperty(component); - myPanelBuilder.getBindings().bind(visibleProperty, parseBool(visibleKey)); - } - - String enabledKey = attributes.getValue(Schema.UiItem.Type.Component.ATTR_ENABLED); - if (enabledKey != null) { - EnabledProperty enabledProperty = new EnabledProperty(component); - myPanelBuilder.getBindings().bind(enabledProperty, parseBool(enabledKey)); - } - } - - private void bindCheckboxProperties(@NotNull JCheckBox checkbox, @NotNull Attributes attributes) { - String textKey = attributes.getValue(Schema.UiItem.Type.Text.ATTR_TEXT); + String textKey = attributes.getValue(Schema.UiCheckbox.ATTR_TEXT); if (textKey != null) { TextProperty textProperty = new TextProperty(checkbox); myPanelBuilder.getBindings().bind(textProperty, parseString(textKey)); } - String checkedKey = attributes.getValue(Schema.UiItem.Type.CheckBox.ATTR_CHECKED); + String checkedKey = attributes.getValue(Schema.UiCheckbox.ATTR_CHECKED); if (checkedKey != null) { SelectedProperty selectedProperty = new SelectedProperty(checkbox); BoolProperty checkedValue = (BoolProperty)parseBool(checkedKey); @@ -424,17 +384,12 @@ import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; } } - private void bindComboBoxProperties(@NotNull JComboBox comboBox, @NotNull Attributes attributes) { - String indexKey = attributes.getValue(Schema.UiItem.Type.Pulldown.ATTR_INDEX); - if (indexKey != null) { - SelectedIndexProperty indexProperty = new SelectedIndexProperty(comboBox); - IntProperty indexValue = (IntProperty)parseInt(indexKey); - myPanelBuilder.getBindings().bindTwoWay(indexProperty, indexValue); - } - } + private void parseUiInput(@NotNull Attributes attributes) { + parseRowCol(attributes); + JTextField field = myPanelBuilder.addField(); + bindTopLevelProperties(field, attributes); - private void bindFieldProperties(@NotNull JTextField field, @NotNull Attributes attributes) { - String textKey = attributes.getValue(Schema.UiItem.Type.Text.ATTR_TEXT); + String textKey = attributes.getValue(Schema.UiInput.ATTR_TEXT); if (textKey != null) { TextProperty textProperty = new TextProperty(field); StringProperty textValue = (StringProperty)parseString(textKey); @@ -442,13 +397,62 @@ import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; } } - private void bindLabelProperties(@NotNull JLabel label, @NotNull Attributes attributes) { - String textKey = attributes.getValue(Schema.UiItem.Type.Text.ATTR_TEXT); + private void parseUiLabel(@NotNull Attributes attributes) { + parseRowCol(attributes); + JLabel label = myPanelBuilder.addLabel(); + bindTopLevelProperties(label, attributes); + + String textKey = attributes.getValue(Schema.UiLabel.ATTR_TEXT); if (textKey != null) { TextProperty textProperty = new TextProperty(label); myPanelBuilder.getBindings().bind(textProperty, parseString(textKey)); } } + private void parseUiLink(@NotNull Attributes attributes) { + parseRowCol(attributes); + HyperlinkLabel link = myPanelBuilder.addLink(requireAttr(attributes, Schema.UiLink.ATTR_TEXT), + toUri(requireAttr(attributes, Schema.UiLink.ATTR_URL))); + bindTopLevelProperties(link, attributes); + } + private void parseUiPulldown(@NotNull Attributes attributes) { + parseRowCol(attributes); + String listKey = requireAttr(attributes, Schema.UiPulldown.ATTR_LIST); + ObservableList<String> backingList = getList(listKey); + JComboBox comboBox = myPanelBuilder.addComboBox(backingList); + bindTopLevelProperties(comboBox, attributes); + + String indexKey = attributes.getValue(Schema.UiPulldown.ATTR_INDEX); + if (indexKey != null) { + SelectedIndexProperty indexProperty = new SelectedIndexProperty(comboBox); + IntProperty indexValue = (IntProperty)parseInt(indexKey); + myPanelBuilder.getBindings().bindTwoWay(indexProperty, indexValue); + } + } + + private void parseRowCol(@NotNull Attributes attributes) { + String row = attributes.getValue(Schema.UiTag.ATTR_ROW); + if (row != null) { + myPanelBuilder.setRow(Integer.parseInt(row)); + } + String col = attributes.getValue(Schema.UiTag.ATTR_COL); + if (col != null) { + myPanelBuilder.setCol(Integer.parseInt(col)); + } + } + + private void bindTopLevelProperties(@NotNull JComponent component, @NotNull Attributes attributes) { + String visibleKey = attributes.getValue(Schema.UiTag.ATTR_VISIBLE); + if (visibleKey != null) { + VisibleProperty visibleProperty = new VisibleProperty(component); + myPanelBuilder.getBindings().bind(visibleProperty, parseBool(visibleKey)); + } + + String enabledKey = attributes.getValue(Schema.UiTag.ATTR_ENABLED); + if (enabledKey != null) { + EnabledProperty enabledProperty = new EnabledProperty(component); + myPanelBuilder.getBindings().bind(enabledProperty, parseBool(enabledKey)); + } + } @NotNull private Runnable parseAction(String value) { @@ -550,15 +554,16 @@ import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; public static final String ATTR_EXECUTE = "execute"; public static final String ATTR_FORMAT = "format"; public static final String ATTR_ICON = "icon"; - public static final String ATTR_INITIALIZE = "initialize"; public static final String ATTR_LEARN_MORE = "learnMore"; public static final String ATTR_MIN_API = "minApi"; public static final String ATTR_NAME = "name"; } - public static class UiTag { + public static abstract class UiTag { public static final String ATTR_COL = "col"; public static final String ATTR_ROW = "row"; + public static final String ATTR_ENABLED = "enabled"; + public static final String ATTR_VISIBLE = "visible"; } public static final class UiGrid extends UiTag { @@ -566,44 +571,38 @@ import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; public static final String ATTR_COL_DEFINITIONS = "colDefinitions"; } - public static final class UiItem extends UiTag { - public static final String TAG = "uiItem"; - public static final String ATTR_TYPE = "type"; - - public static final class Type { - public static final String VALUE_BUTTON = "button"; - public static final String VALUE_CHECKBOX = "checkbox"; - public static final String VALUE_INPUT = "input"; - public static final String VALUE_LABEL = "label"; - public static final String VALUE_LINK = "link"; - public static final String VALUE_PULLDOWN = "pulldown"; - - public static final class Component { - public static final String ATTR_ENABLED = "enabled"; - public static final String ATTR_VISIBLE = "visible"; - } + public static final class UiButton extends UiTag { + public static final String TAG = "uiButton"; + public static final String ATTR_TEXT = "text"; + public static final String ATTR_ACTION = "action"; + } - public static final class Button { - public static final String ATTR_ACTION = "action"; - } + public static final class UiCheckbox extends UiTag { + public static final String TAG = "uiCheckbox"; + public static final String ATTR_TEXT = "text"; + public static final String ATTR_CHECKED = "checked"; + } - public static final class CheckBox { - public static final String ATTR_CHECKED = "checked"; - } + public static final class UiInput extends UiTag { + public static final String TAG = "uiInput"; + public static final String ATTR_TEXT = "text"; + } - public static final class Link { - public static final String ATTR_URL = "url"; - } + public static final class UiLabel extends UiTag { + public static final String TAG = "uiLabel"; + public static final String ATTR_TEXT = "text"; + } - public static final class Text { - public static final String ATTR_TEXT = "text"; - } + public static final class UiLink extends UiTag { + public static final String TAG = "uiLink"; + public static final String ATTR_TEXT = "text"; + public static final String ATTR_URL = "url"; + } - public static final class Pulldown { - public static final String ATTR_LIST = "list"; - public static final String ATTR_INDEX = "index"; - } - } + public static final class UiPulldown extends UiTag { + public static final String TAG = "uiPulldown"; + public static final String ATTR_LIST = "list"; + public static final String ATTR_INDEX = "index"; } } } diff --git a/android/src/com/android/tools/idea/templates/RepositoryUrlManager.java b/android/src/com/android/tools/idea/templates/RepositoryUrlManager.java index e0d9aa85161..0a94550264f 100644 --- a/android/src/com/android/tools/idea/templates/RepositoryUrlManager.java +++ b/android/src/com/android/tools/idea/templates/RepositoryUrlManager.java @@ -18,6 +18,7 @@ package com.android.tools.idea.templates; import com.android.SdkConstants; import com.android.annotations.VisibleForTesting; import com.android.ide.common.repository.GradleCoordinate; +import com.android.ide.common.repository.SdkMavenRepository; import com.google.common.base.Predicate; import com.google.common.collect.*; import com.intellij.openapi.diagnostic.Logger; @@ -154,14 +155,54 @@ public class RepositoryUrlManager { } /** + * Returns the string for the specific version number of the most recent version of the given library + * (matching the given prefix filter, if any) in one of the Sdk repositories. + * + * @param groupId the group id + * @param artifactId the artifact id + * @param filterPrefix a prefix, if any + * @param includePreviews whether to include preview versions of libraries + * @return + */ + @Nullable + public String getLibraryCoordinate(String groupId, String artifactId, @Nullable String filterPrefix, boolean includePreviews) { + SdkMavenRepository repository = SdkMavenRepository.getByGroupId(groupId); + if (repository == null) { + return null; + } + AndroidSdkData sdk = tryToChooseAndroidSdk(); + if (sdk == null) { + return null; + } + + File sdkLocation = sdk.getLocation(); + File repo = repository.getRepositoryLocation(sdkLocation, false); + if (repo == null) { + return null; + } + + GradleCoordinate max = repository.getHighestInstalledVersion(sdk.getLocation(), groupId, artifactId, filterPrefix, includePreviews); + if (max == null) { + return null; + } + + return max.getFullRevision(); + } + + /** * Calculate the coordinate pointing to the highest valued version of the given library we * have available in our repository. * @param libraryId the id of the library to find * @param filterPrefix an optional prefix libraries must match; e.g. if the prefix is "18." then only coordinates * in version 18.x will be considered * @return a maven coordinate for the requested library or null if we don't support that library + * @deprecated Use {@link #getLibraryCoordinate(String, String, String, boolean)} instead. This method only takes + * an artifact id, which may <b>not</b> be unique across group id's, and besides, the below method relies on a hardcoded + * list of libraries in each repository, which gets obsolete all the time as new repositories are added. The method + * above however, does not rely on a table like that and should continue to work as new libraries are added. */ @Nullable + @Deprecated public String getLibraryCoordinate(String libraryId, @Nullable String filterPrefix, boolean includePreviews) { // Check to see if this is a URL we support: if (!EXTRAS_REPOSITORY.containsKey(libraryId)) { diff --git a/android/src/com/android/tools/idea/welcome/install/ComponentInstaller.java b/android/src/com/android/tools/idea/welcome/install/ComponentInstaller.java index 9246178f533..ba3a6a3530c 100644 --- a/android/src/com/android/tools/idea/welcome/install/ComponentInstaller.java +++ b/android/src/com/android/tools/idea/welcome/install/ComponentInstaller.java @@ -143,7 +143,7 @@ public final class ComponentInstaller { } public void installPackages(@NotNull SdkManager manager, @NotNull ArrayList<String> packages, ILogger logger) throws WizardException { - SdkUpdaterNoWindow updater = new SdkUpdaterNoWindow(manager.getLocation(), manager, logger, false, true, null, null); + SdkUpdaterNoWindow updater = new SdkUpdaterNoWindow(manager.getLocation(), manager, logger, false, null, null); updater.updateAll(packages, true, false, null, false); } } diff --git a/android/src/com/android/tools/idea/wizard/IconPicker.java b/android/src/com/android/tools/idea/wizard/IconPicker.java index d33aef1a2a8..4d5681d4150 100644 --- a/android/src/com/android/tools/idea/wizard/IconPicker.java +++ b/android/src/com/android/tools/idea/wizard/IconPicker.java @@ -19,13 +19,14 @@ import com.android.SdkConstants; import com.android.annotations.Nullable; import com.android.assetstudiolib.GraphicGenerator; import com.android.assetstudiolib.vectordrawable.VdIcon; +import com.google.common.collect.Multimap; +import com.google.common.collect.TreeMultimap; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.ui.DialogBuilder; import com.intellij.ui.JBColor; import com.intellij.ui.components.JBList; import com.intellij.ui.components.JBScrollPane; import com.intellij.ui.table.JBTable; -import com.intellij.util.containers.MultiMap; import java.awt.*; import java.io.File; @@ -57,12 +58,12 @@ public class IconPicker extends JPanel { // specific category except "All". This is the reason when we reference this // array, we start from index 1. private final static String[] mIconCategories = - {"All", "Action", "Alert", "AV", "Communication", "Content", "Device", + {"All", "Action", "Alert", "Av", "Communication", "Content", "Device", "Editor", "File", "Hardware", "Image", "Maps", "Navigation", "Notification", "Social", "Toggle"}; // This is a map from category name to a hash set of icon names. - private final MultiMap<String, VdIcon> mAllIconCategoryMap = new MultiMap<String, VdIcon>(); + private final Multimap<String, VdIcon> mAllIconCategoryMap = TreeMultimap.create(); private AbstractTableModel mModel = new AbstractTableModel() { @@ -238,16 +239,14 @@ public class IconPicker extends JPanel { private void loadInternalDrawables() { // Starting from 1, since 0 means "all". for (int i = 1; i < mIconCategories.length; i++) { - Collection<VdIcon> iconSet = new HashSet<VdIcon>(); String categoryName = mIconCategories[i]; - mAllIconCategoryMap.put(categoryName, iconSet); String categoryNameLowerCase = categoryName.toLowerCase(Locale.ENGLISH); String fullDirName = MATERIAL_DESIGN_ICONS_PATH + categoryNameLowerCase + File.separator; for (Iterator<String> iter = GraphicGenerator.getResourcesNames(fullDirName, SdkConstants.DOT_XML); iter.hasNext(); ) { final String iconName = iter.next(); URL url = GraphicGenerator.class.getClassLoader().getResource(fullDirName + iconName); VdIcon icon = new VdIcon(url); - iconSet.add(icon); + mAllIconCategoryMap.put(categoryName, icon); } } diff --git a/android/src/com/android/tools/idea/wizard/NewProjectWizardDynamic.java b/android/src/com/android/tools/idea/wizard/NewProjectWizardDynamic.java index 8cadacb8356..ee4c4b10e56 100644 --- a/android/src/com/android/tools/idea/wizard/NewProjectWizardDynamic.java +++ b/android/src/com/android/tools/idea/wizard/NewProjectWizardDynamic.java @@ -70,7 +70,7 @@ public class NewProjectWizardDynamic extends DynamicWizard { String msg = "<html>Your Android SDK is missing, out of date, or is missing templates.<br>" + "You can configure your SDK via <b>Configure | Project Defaults | Project Structure | SDKs</b></html>"; Messages.showErrorDialog(msg, title); - return; + throw new IllegalStateException("Android SDK missing"); } addPaths(); initState(); diff --git a/android/src/com/android/tools/swing/layoutlib/GraphicsLayoutRenderer.java b/android/src/com/android/tools/swing/layoutlib/GraphicsLayoutRenderer.java index 30bc828d345..f1566827e34 100644 --- a/android/src/com/android/tools/swing/layoutlib/GraphicsLayoutRenderer.java +++ b/android/src/com/android/tools/swing/layoutlib/GraphicsLayoutRenderer.java @@ -137,13 +137,7 @@ public class GraphicsLayoutRenderer { LayoutLibrary layoutLib; try { IAndroidTarget latestTarget = configuration.getConfigurationManager().getHighestApiTarget(); - if (latestTarget != null && latestTarget != target) { - target = new CompatibilityRenderTarget( - latestTarget, - target.getVersion().getFeatureLevel(), - target); - } - layoutLib = platform.getSdkData().getTargetData(target).getLayoutLibrary(project); + layoutLib = platform.getSdkData().getTargetData(latestTarget).getLayoutLibrary(project); if (layoutLib == null) { throw new InitializationException("getLayoutLibrary() returned null"); diff --git a/android/src/org/jetbrains/android/facet/IdeaSourceProvider.java b/android/src/org/jetbrains/android/facet/IdeaSourceProvider.java index 80cd38c6f3b..76d75dc6d44 100644 --- a/android/src/org/jetbrains/android/facet/IdeaSourceProvider.java +++ b/android/src/org/jetbrains/android/facet/IdeaSourceProvider.java @@ -232,9 +232,13 @@ public abstract class IdeaSourceProvider { return file; } - VirtualFile root = AndroidRootUtil.getMainContentRoot(myFacet); - if (root != null) { - return root.findChild(ANDROID_MANIFEST_XML); + // Not calling AndroidRootUtil.getMainContentRoot(myFacet) because that method can + // recurse into this same method if it can't find a content root. (This scenario + // applies when we're looking for manifests in for example a temporary file system, + // as tested by ResourceTypeInspectionTest#testLibraryRevocablePermission) + VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots(); + if (contentRoots.length == 1) { + return contentRoots[0].findChild(ANDROID_MANIFEST_XML); } return null; diff --git a/android/src/org/jetbrains/android/inspections/ResourceTypeInspection.java b/android/src/org/jetbrains/android/inspections/ResourceTypeInspection.java index 7ac9e6ea55a..1202ad19d39 100644 --- a/android/src/org/jetbrains/android/inspections/ResourceTypeInspection.java +++ b/android/src/org/jetbrains/android/inspections/ResourceTypeInspection.java @@ -1119,6 +1119,29 @@ public class ResourceTypeInspection extends BaseJavaLocalInspectionTool { return guessSize(initializer); } } + } else if (argument instanceof PsiPrefixExpression) { + PsiPrefixExpression prefix = (PsiPrefixExpression)argument; + if (prefix.getOperationTokenType() == JavaTokenType.MINUS) { + PsiExpression operand = prefix.getOperand(); + if (operand != null) { + Number number = guessSize(operand); + if (number != null) { + if (number instanceof Long) { + return -number.longValue(); + } else if (number instanceof Integer) { + return -number.intValue(); + } else if (number instanceof Double) { + return -number.doubleValue(); + } else if (number instanceof Float) { + return -number.floatValue(); + } else if (number instanceof Short) { + return -number.shortValue(); + } else if (number instanceof Byte) { + return -number.byteValue(); + } + } + } + } } return null; } @@ -1732,13 +1755,20 @@ public class ResourceTypeInspection extends BaseJavaLocalInspectionTool { } else if (value instanceof PsiPrefixExpression) { // negative number PsiPrefixExpression exp = (PsiPrefixExpression)value; - PsiExpression operand = exp.getOperand(); - if (operand instanceof PsiLiteral) { - Object o = ((PsiLiteral)operand).getValue(); - if (o instanceof Number) { - return -((Number)o).longValue(); + if (exp.getOperationTokenType() == JavaTokenType.MINUS) { + PsiExpression operand = exp.getOperand(); + if (operand instanceof PsiLiteral) { + Object o = ((PsiLiteral)operand).getValue(); + if (o instanceof Number) { + return -((Number)o).longValue(); + } } } + } else if (value instanceof PsiReferenceExpression) { + PsiElement resolved = ((PsiReferenceExpression)value).resolve(); + if (resolved instanceof PsiField) { + return getLongValue(((PsiField)resolved).getInitializer(), defaultValue); + } } // TODO: Allow inlined arithmetic here? If so look for operator nodes return defaultValue; @@ -1752,6 +1782,11 @@ public class ResourceTypeInspection extends BaseJavaLocalInspectionTool { if (o instanceof Number) { return ((Number)o).doubleValue(); } + } else if (value instanceof PsiReferenceExpression) { + PsiElement resolved = ((PsiReferenceExpression)value).resolve(); + if (resolved instanceof PsiField) { + return getDoubleValue(((PsiField)resolved).getInitializer(), defaultValue); + } } // TODO: Allow inlined arithmetic here? If so look for operator nodes return defaultValue; diff --git a/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionToolProvider.java b/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionToolProvider.java index 265e27125cb..13a2f24b1a1 100644 --- a/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionToolProvider.java +++ b/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionToolProvider.java @@ -2,6 +2,7 @@ package org.jetbrains.android.inspections.lint; import com.android.SdkConstants; import com.android.ide.common.repository.GradleCoordinate; +import com.android.ide.common.repository.SdkMavenRepository; import com.android.ide.common.resources.ResourceUrl; import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.ide.common.resources.configuration.VersionQualifier; @@ -9,11 +10,13 @@ import com.android.resources.ResourceFolderType; import com.android.sdklib.AndroidVersion; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkVersionInfo; +import com.android.sdklib.repository.PreciseRevision; import com.android.tools.idea.actions.OverrideResourceAction; import com.android.tools.idea.gradle.util.GradleUtil; import com.android.tools.idea.rendering.ResourceHelper; import com.android.tools.idea.templates.RepositoryUrlManager; import com.android.tools.lint.checks.*; +import com.android.tools.lint.client.api.LintClient; import com.android.tools.lint.detector.api.Issue; import com.google.common.collect.Lists; import com.intellij.codeInsight.intention.IntentionAction; @@ -850,10 +853,26 @@ public class AndroidLintInspectionToolProvider { // If this coordinate points to an artifact in one of our repositories, mark it will a comment if they don't // have that repository available. + + RepositoryUrlManager manager = RepositoryUrlManager.get(); + String libraryCoordinate = manager + .getLibraryCoordinate(plus.getGroupId(), plus.getArtifactId(), filter, false); + if (libraryCoordinate != null) { + return libraryCoordinate; + } + // If that didn't yield any matches, try again, this time allowing preview platforms. + // This is necessary if the artifact filter includes enough of a version where there are + // only preview matches. + libraryCoordinate = manager.getLibraryCoordinate(plus.getGroupId(), plus.getArtifactId(), filter, true); + if (libraryCoordinate != null) { + return libraryCoordinate; + } + + // Obsolete; remove in 1.4 String artifactId = plus.getArtifactId(); if (RepositoryUrlManager.supports(plus.getArtifactId())) { // First look for matches, where we don't allow preview versions - String libraryCoordinate = RepositoryUrlManager.get().getLibraryCoordinate(artifactId, filter, false); + libraryCoordinate = manager.getLibraryCoordinate(artifactId, filter, false); if (libraryCoordinate != null) { GradleCoordinate available = GradleCoordinate.parseCoordinateString(libraryCoordinate); if (available != null) { @@ -863,7 +882,7 @@ public class AndroidLintInspectionToolProvider { // If that didn't yield any matches, try again, this time allowing preview platforms. // This is necessary if the artifact filter includes enough of a version where there are // only preview matches. - libraryCoordinate = RepositoryUrlManager.get().getLibraryCoordinate(artifactId, filter, true); + libraryCoordinate = manager.getLibraryCoordinate(artifactId, filter, true); if (libraryCoordinate != null) { GradleCoordinate available = GradleCoordinate.parseCoordinateString(libraryCoordinate); if (available != null) { @@ -873,11 +892,22 @@ public class AndroidLintInspectionToolProvider { } // Regular Gradle dependency? Look in Gradle cache - GradleCoordinate found = GradleUtil.findLatestVersionInGradleCache(plus, filter, startElement.getProject()); + Project project = startElement.getProject(); + GradleCoordinate found = GradleUtil.findLatestVersionInGradleCache(plus, filter, project); if (found != null) { return found.getFullRevision(); } + // Perform network lookup to resolve current best version, if possible + LintClient client = new IntellijLintClient(project); + PreciseRevision latest = GradleDetector.getLatestVersionFromRemoteRepo(client, plus, plus.isPreview()); + if (latest != null) { + String version = latest.toShortString(); + if (version.startsWith(filter)) { + return version; + } + } + return null; } }}; @@ -1638,6 +1668,12 @@ public class AndroidLintInspectionToolProvider { } } + public static class AndroidLintShiftFlagsInspection extends AndroidLintInspectionBase { + public AndroidLintShiftFlagsInspection() { + super(AndroidBundle.message("android.lint.inspections.shift.flags"), AnnotationDetector.FLAG_STYLE); + } + } + public static class AndroidLintShortAlarmInspection extends AndroidLintInspectionBase { public AndroidLintShortAlarmInspection() { super(AndroidBundle.message("android.lint.inspections.short.alarm"), AlarmDetector.ISSUE); diff --git a/android/src/org/jetbrains/android/uipreview/ChooseResourceDialog.java b/android/src/org/jetbrains/android/uipreview/ChooseResourceDialog.java index a7866b8ec7c..189e5637908 100644 --- a/android/src/org/jetbrains/android/uipreview/ChooseResourceDialog.java +++ b/android/src/org/jetbrains/android/uipreview/ChooseResourceDialog.java @@ -201,23 +201,35 @@ public class ChooseResourceDialog extends DialogWrapper implements TreeSelection /** * Constructor to use when the dialog needs to know the name of the resource used to open it */ - public ChooseResourceDialog(@NotNull Module module, @NotNull ResourceType[] types, @Nullable String value, - @Nullable XmlTag tag, ResourceNameVisibility resourceNameVisibility, @Nullable String resourceName) { - this(module, null, types, value,tag, resourceNameVisibility, resourceName, null); + public ChooseResourceDialog(@NotNull Module module, + @NotNull ResourceType[] types, + @Nullable String value, + @Nullable XmlTag tag, + ResourceNameVisibility resourceNameVisibility, + @Nullable String resourceName) { + this(module, null, types, value, tag, resourceNameVisibility, resourceName, null); } /** * Constructor to use to get the statelist panel, where the statelist shown is the one represented by colorStates */ - public ChooseResourceDialog(@NotNull Module module, @NotNull Configuration configuration, @NotNull ResourceType[] types, - @NotNull List<StateListPicker.StateListState> colorStates, ResourceNameVisibility resourceNameVisibility, + public ChooseResourceDialog(@NotNull Module module, + @NotNull Configuration configuration, + @NotNull ResourceType[] types, + @NotNull List<StateListPicker.StateListState> colorStates, + ResourceNameVisibility resourceNameVisibility, @Nullable String resourceName) { this(module, configuration, types, null, null,resourceNameVisibility, resourceName, colorStates); } - private ChooseResourceDialog(@NotNull Module module, @Nullable Configuration configuration, @NotNull ResourceType[] types, - @Nullable String value, @Nullable XmlTag tag, ResourceNameVisibility resourceNameVisibility, - @Nullable String resourceName, @Nullable List<StateListPicker.StateListState> colorStates) { + private ChooseResourceDialog(@NotNull Module module, + @Nullable Configuration configuration, + @NotNull ResourceType[] types, + @Nullable String value, + @Nullable XmlTag tag, + ResourceNameVisibility resourceNameVisibility, + @Nullable String resourceName, + @Nullable List<StateListPicker.StateListState> colorStates) { super(module.getProject()); myModule = module; myTag = tag; diff --git a/android/src/org/jetbrains/android/uipreview/ModuleClassLoader.java b/android/src/org/jetbrains/android/uipreview/ModuleClassLoader.java index 7f353d9fcfa..390655f9cfb 100644 --- a/android/src/org/jetbrains/android/uipreview/ModuleClassLoader.java +++ b/android/src/org/jetbrains/android/uipreview/ModuleClassLoader.java @@ -428,6 +428,13 @@ public final class ModuleClassLoader extends RenderClassLoader { ourCache.clear(); } + /** Remove the cached class loader for the module. */ + public static void clearCache(Module module) { + if (ourCache.containsKey(module)) { + ourCache.remove(module); + } + } + /** Temporary hack: Store this in a weak hash map cached by modules. In the next version we should move this * into a proper persistent render service. */ private static WeakHashMap<Module,ModuleClassLoader> ourCache = new WeakHashMap<Module, ModuleClassLoader>(); diff --git a/android/src/org/jetbrains/android/uipreview/RecyclerViewHelper.java b/android/src/org/jetbrains/android/uipreview/RecyclerViewHelper.java index a02a10d31a9..8a0bdac3435 100644 --- a/android/src/org/jetbrains/android/uipreview/RecyclerViewHelper.java +++ b/android/src/org/jetbrains/android/uipreview/RecyclerViewHelper.java @@ -75,6 +75,10 @@ public class RecyclerViewHelper { fv.visitEnd(); } { + fv = cw.visitField(ACC_PRIVATE, "mId", "I", null, null); + fv.visitEnd(); + } + { mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); @@ -88,19 +92,39 @@ public class RecyclerViewHelper { cw.visitMethod(ACC_PUBLIC, "onCreateViewHolder", "(Landroid/view/ViewGroup;I)Landroid/support/v7/widget/RecyclerView$ViewHolder;", null, null); mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, "com/android/layoutlib/bridge/android/support/Adapter", "mId", "I"); + Label l0 = new Label(); + mv.visitJumpInsn(IFLE, l0); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, "android/view/ViewGroup", "getContext", "()Landroid/content/Context;", false); + mv.visitMethodInsn(INVOKESTATIC, "android/view/LayoutInflater", "from", "(Landroid/content/Context;)Landroid/view/LayoutInflater;", + false); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, "com/android/layoutlib/bridge/android/support/Adapter", "mId", "I"); + mv.visitVarInsn(ALOAD, 1); + mv.visitInsn(ICONST_0); + mv.visitMethodInsn(INVOKEVIRTUAL, "android/view/LayoutInflater", "inflate", "(ILandroid/view/ViewGroup;Z)Landroid/view/View;", false); + mv.visitVarInsn(ASTORE, 3); + Label l1 = new Label(); + mv.visitJumpInsn(GOTO, l1); + mv.visitLabel(l0); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); mv.visitTypeInsn(NEW, "android/widget/TextView"); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "android/view/ViewGroup", "getContext", "()Landroid/content/Context;", false); mv.visitMethodInsn(INVOKESPECIAL, "android/widget/TextView", "<init>", "(Landroid/content/Context;)V", false); mv.visitVarInsn(ASTORE, 3); + mv.visitLabel(l1); + mv.visitFrame(Opcodes.F_APPEND,1, new Object[] {"android/view/View"}, 0, null); mv.visitTypeInsn(NEW, "com/android/layoutlib/bridge/android/support/Adapter$ViewHolder"); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 3); mv.visitMethodInsn(INVOKESPECIAL, "com/android/layoutlib/bridge/android/support/Adapter$ViewHolder", "<init>", "(Landroid/view/View;)V", false); mv.visitInsn(ARETURN); - mv.visitMaxs(3, 4); + mv.visitMaxs(4, 4); mv.visitEnd(); } { @@ -138,6 +162,16 @@ public class RecyclerViewHelper { mv.visitMaxs(1, 1); mv.visitEnd(); } + { + mv = cw.visitMethod(ACC_PUBLIC, "setLayoutId", "(I)V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ILOAD, 1); + mv.visitFieldInsn(PUTFIELD, "com/android/layoutlib/bridge/android/support/Adapter", "mId", "I"); + mv.visitInsn(RETURN); + mv.visitMaxs(2, 2); + mv.visitEnd(); + } cw.visitEnd(); return cw.toByteArray(); @@ -190,11 +224,17 @@ public class RecyclerViewHelper { //public class Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { // // private static final int ITEM_COUNT = 10; + // private int mId; // // @Override // public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, // int viewType) { - // TextView view = new TextView(parent.getContext()); + // View view; + // if (mId > 0) { + // view = LayoutInflater.from(parent.getContext()).inflate(mId, parent, false); + // } else { + // view = new TextView(parent.getContext()); + // } // return new ViewHolder(view); // } // @@ -205,7 +245,6 @@ public class RecyclerViewHelper { // if (view instanceof TextView) { // ((TextView) view).setText("Item number " + position); // } - // // } // // @Override @@ -213,6 +252,10 @@ public class RecyclerViewHelper { // return ITEM_COUNT; // } // + // public void setLayoutId(int id) { + // mId = id; + // } + // // private static class ViewHolder extends RecyclerView.ViewHolder { // public ViewHolder(View itemView) { // super(itemView); diff --git a/android/src/org/jetbrains/android/uipreview/ResourceDialogSouthPanel.form b/android/src/org/jetbrains/android/uipreview/ResourceDialogSouthPanel.form index ac7e490313e..989136b8be0 100644 --- a/android/src/org/jetbrains/android/uipreview/ResourceDialogSouthPanel.form +++ b/android/src/org/jetbrains/android/uipreview/ResourceDialogSouthPanel.form @@ -13,6 +13,7 @@ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/> </constraints> <properties> + <labelFor value="8164b"/> <text value="Name"/> </properties> </component> diff --git a/android/testData/guiTests/CapturesApplication/.gitignore b/android/testData/guiTests/CapturesApplication/.gitignore new file mode 100644 index 00000000000..6cb14b0afe6 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/.gitignore @@ -0,0 +1,17 @@ +.gradle +/gradle +/local.properties +/.idea/copyright +/.idea/scopes +/.idea/libraries +/.idea/compiler.xml +/.idea/encodings.xml +/.idea/gradle.xml +/.idea/misc.xml +/.idea/vcs.xml +/.idea/workspace.xml +.DS_Store +/build +gradlew +gradlew.bat +gradle.properties diff --git a/android/testData/guiTests/CapturesApplication/.idea/.name b/android/testData/guiTests/CapturesApplication/.idea/.name new file mode 100644 index 00000000000..1b1761fdf6f --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/.idea/.name @@ -0,0 +1 @@ +Captures Application
\ No newline at end of file diff --git a/android/testData/guiTests/CapturesApplication/.idea/modules.xml b/android/testData/guiTests/CapturesApplication/.idea/modules.xml new file mode 100644 index 00000000000..4e6c24c3d45 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/.idea/modules.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/CapturesApplication.iml" filepath="$PROJECT_DIR$/CapturesApplication.iml" /> + <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" /> + </modules> + </component> +</project> + diff --git a/android/testData/guiTests/CapturesApplication/CapturesApplication.iml b/android/testData/guiTests/CapturesApplication/CapturesApplication.iml new file mode 100644 index 00000000000..0bb6048ae08 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/CapturesApplication.iml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4"> + <component name="FacetManager"> + <facet type="java-gradle" name="Java-Gradle"> + <configuration> + <option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" /> + </configuration> + </facet> + </component> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <excludeFolder url="file://$MODULE_DIR$/.gradle" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> + diff --git a/android/testData/guiTests/CapturesApplication/app/.gitignore b/android/testData/guiTests/CapturesApplication/app/.gitignore new file mode 100644 index 00000000000..796b96d1c40 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/android/testData/guiTests/CapturesApplication/app/app.iml b/android/testData/guiTests/CapturesApplication/app/app.iml new file mode 100644 index 00000000000..58a1d57f959 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/app.iml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="CapturesApplication" external.system.module.version="unspecified" type="JAVA_MODULE" version="4"> + <component name="FacetManager"> + <facet type="android-gradle" name="Android-Gradle"> + <configuration> + <option name="GRADLE_PROJECT_PATH" value=":app" /> + </configuration> + </facet> + <facet type="android" name="Android"> + <configuration> + <option name="SELECTED_BUILD_VARIANT" value="debug" /> + <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" /> + <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugJava" /> + <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" /> + <option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" /> + <option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugTestSources" /> + <option name="ALLOW_USER_CONFIGURATION" value="false" /> + <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" /> + <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" /> + <option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" /> + <option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" /> + </configuration> + </facet> + </component> + <component name="NewModuleRootManager" inherit-compiler-output="false"> + <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" /> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/test/debug" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" /> + <excludeFolder url="file://$MODULE_DIR$/build/outputs" /> + </content> + <orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> + diff --git a/android/testData/guiTests/CapturesApplication/app/build.gradle b/android/testData/guiTests/CapturesApplication/app/build.gradle new file mode 100644 index 00000000000..4f87a18c80e --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/build.gradle @@ -0,0 +1,29 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 22 + buildToolsVersion "22.0.1" + + defaultConfig { + applicationId "com.android.captures.application" + minSdkVersion 19 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:22.1.1' + compile 'com.google.guava:guava:18.0' +} diff --git a/android/testData/guiTests/CapturesApplication/app/proguard-rules.pro b/android/testData/guiTests/CapturesApplication/app/proguard-rules.pro new file mode 100644 index 00000000000..e2f6a3ded07 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Applications/adt-bundle-mac-x86_64-20131030/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/android/testData/guiTests/CapturesApplication/app/src/androidTest/java/google/capturesapplication/ApplicationTest.java b/android/testData/guiTests/CapturesApplication/app/src/androidTest/java/google/capturesapplication/ApplicationTest.java new file mode 100644 index 00000000000..0a951cf6eee --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/androidTest/java/google/capturesapplication/ApplicationTest.java @@ -0,0 +1,13 @@ +package google.capturesapplication; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> + */ +public class ApplicationTest extends ApplicationTestCase<Application> { + public ApplicationTest() { + super(Application.class); + } +}
\ No newline at end of file diff --git a/android/testData/guiTests/CapturesApplication/app/src/main/AndroidManifest.xml b/android/testData/guiTests/CapturesApplication/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..209e02d7773 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="google.capturesapplication" > + + <application + android:allowBackup="true" + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" + android:theme="@style/AppTheme" > + <activity + android:name=".MyActivity" + android:label="@string/app_name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/android/testData/guiTests/CapturesApplication/app/src/main/java/google/capturesapplication/MyActivity.java b/android/testData/guiTests/CapturesApplication/app/src/main/java/google/capturesapplication/MyActivity.java new file mode 100644 index 00000000000..f076caddd68 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/main/java/google/capturesapplication/MyActivity.java @@ -0,0 +1,36 @@ +package google.capturesapplication; + +import android.app.Activity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; + + +public class MyActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_my); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.my, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + if (id == R.id.action_settings) { + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/android/testData/guiTests/CapturesApplication/app/src/main/res/drawable/ic_launcher.xml b/android/testData/guiTests/CapturesApplication/app/src/main/res/drawable/ic_launcher.xml new file mode 100644 index 00000000000..bebd9834a0e --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/main/res/drawable/ic_launcher.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<color xmlns:android="http://schemas.android.com/apk/res/android" android:color="#ff0000" /> diff --git a/android/testData/guiTests/CapturesApplication/app/src/main/res/layout/activity_my.xml b/android/testData/guiTests/CapturesApplication/app/src/main/res/layout/activity_my.xml new file mode 100644 index 00000000000..c17166f4d34 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/main/res/layout/activity_my.xml @@ -0,0 +1,16 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + android:paddingBottom="@dimen/activity_vertical_margin" + tools:context=".MyActivity"> + + <TextView + android:text="@string/hello_world" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + +</RelativeLayout> diff --git a/android/testData/guiTests/CapturesApplication/app/src/main/res/menu/my.xml b/android/testData/guiTests/CapturesApplication/app/src/main/res/menu/my.xml new file mode 100644 index 00000000000..bea58cc00be --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/main/res/menu/my.xml @@ -0,0 +1,8 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:context=".MyActivity" > + <item android:id="@+id/action_settings" + android:title="@string/action_settings" + android:orderInCategory="100" + android:showAsAction="never" /> +</menu> diff --git a/android/testData/guiTests/CapturesApplication/app/src/main/res/values-en-rGB/strings.xml b/android/testData/guiTests/CapturesApplication/app/src/main/res/values-en-rGB/strings.xml new file mode 100644 index 00000000000..02f9daa54d0 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/main/res/values-en-rGB/strings.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2014 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<resources> + + <string name="app_name">Captures Application</string> + <string name="hello_world">Hello world!</string> + +</resources> diff --git a/android/testData/guiTests/CapturesApplication/app/src/main/res/values-en/strings.xml b/android/testData/guiTests/CapturesApplication/app/src/main/res/values-en/strings.xml new file mode 100644 index 00000000000..75d1aa488c5 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/main/res/values-en/strings.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">Captures Application</string> + <string name="hello_world">Hello world!</string> + <string name="action_settings">Settings</string> + +</resources> diff --git a/android/testData/guiTests/CapturesApplication/app/src/main/res/values-ta/strings.xml b/android/testData/guiTests/CapturesApplication/app/src/main/res/values-ta/strings.xml new file mode 100644 index 00000000000..75d1aa488c5 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/main/res/values-ta/strings.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">Captures Application</string> + <string name="hello_world">Hello world!</string> + <string name="action_settings">Settings</string> + +</resources> diff --git a/android/testData/guiTests/CapturesApplication/app/src/main/res/values-w820dp/dimens.xml b/android/testData/guiTests/CapturesApplication/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 00000000000..63fc8164446 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ +<resources> + <!-- Example customization of dimensions originally defined in res/values/dimens.xml + (such as screen margins) for screens with more than 820dp of available width. This + would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> + <dimen name="activity_horizontal_margin">64dp</dimen> +</resources> diff --git a/android/testData/guiTests/CapturesApplication/app/src/main/res/values-zh-rCN/strings.xml b/android/testData/guiTests/CapturesApplication/app/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 00000000000..100d85e9da1 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2014 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<resources> + <string name="app_name">谷歌 I/O</string> + <string name="hello_world">Hello world!</string> + <string name="action_settings">Settings</string> + <string name="cancel">取消</string> +</resources> diff --git a/android/testData/guiTests/CapturesApplication/app/src/main/res/values/dimens.xml b/android/testData/guiTests/CapturesApplication/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000000..47c82246738 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> +</resources> diff --git a/android/testData/guiTests/CapturesApplication/app/src/main/res/values/strings.xml b/android/testData/guiTests/CapturesApplication/app/src/main/res/values/strings.xml new file mode 100644 index 00000000000..75d1aa488c5 --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">Captures Application</string> + <string name="hello_world">Hello world!</string> + <string name="action_settings">Settings</string> + +</resources> diff --git a/android/testData/guiTests/CapturesApplication/app/src/main/res/values/styles.xml b/android/testData/guiTests/CapturesApplication/app/src/main/res/values/styles.xml new file mode 100644 index 00000000000..ff6c9d2c0fb --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ +<resources> + + <!-- Base application theme. --> + <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar"> + <!-- Customize your theme here. --> + </style> + +</resources> diff --git a/android/src/com/android/tools/idea/sdk/remote/internal/updater/IUpdaterData.java b/android/testData/guiTests/CapturesApplication/app/src/test/java/google/capturesapplication/UnitTest.java index 4cf6cbe2a1e..22704727515 100755..100644 --- a/android/src/com/android/tools/idea/sdk/remote/internal/updater/IUpdaterData.java +++ b/android/testData/guiTests/CapturesApplication/app/src/test/java/google/capturesapplication/UnitTest.java @@ -13,28 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package google.capturesapplication; -package com.android.tools.idea.sdk.remote.internal.updater; - -import com.android.sdklib.SdkManager; -import com.android.tools.idea.sdk.remote.internal.DownloadCache; -import com.android.tools.idea.sdk.remote.internal.ITaskFactory; -import com.android.utils.ILogger; +import org.junit.Assert; +import org.junit.Test; +import java.lang.Deprecated; /** - * Interface used to retrieve some parameters from an {@link UpdaterData} instance. - * Useful mostly for unit tests purposes. + * A unit test to be executed on the local vm. */ -public interface IUpdaterData { - - ITaskFactory getTaskFactory(); - - ILogger getSdkLog(); - - DownloadCache getDownloadCache(); - - SdkManager getSdkManager(); - - SettingsController getSettingsController(); -} +public class UnitTest { + @Test + public void passingTest() throws Exception { + Assert.assertEquals(2, 1 + 1); + } + + @Test + public void failingTest() throws Exception { + Assert.assertEquals(5, 2 + 2); + } +}
\ No newline at end of file diff --git a/android/testData/guiTests/CapturesApplication/build.gradle b/android/testData/guiTests/CapturesApplication/build.gradle new file mode 100644 index 00000000000..94e371b6bce --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/build.gradle @@ -0,0 +1,22 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + if (System.getenv("MAVEN_URL") != null) { + maven {url System.getenv("MAVEN_URL")} + } + } + dependencies { + classpath 'com.android.tools.build:gradle:1.2.3' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/android/testData/guiTests/CapturesApplication/captures/snapshot.hprof b/android/testData/guiTests/CapturesApplication/captures/snapshot.hprof Binary files differnew file mode 100644 index 00000000000..dd9267ac74a --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/captures/snapshot.hprof diff --git a/android/testData/guiTests/CapturesApplication/settings.gradle b/android/testData/guiTests/CapturesApplication/settings.gradle new file mode 100644 index 00000000000..e7b4def49cb --- /dev/null +++ b/android/testData/guiTests/CapturesApplication/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/android/testData/lint/global/activityRegistered/expected.xml b/android/testData/lint/global/activityRegistered/expected.xml index 14ea47890f0..3904138ecd1 100644 --- a/android/testData/lint/global/activityRegistered/expected.xml +++ b/android/testData/lint/global/activityRegistered/expected.xml @@ -4,18 +4,18 @@ <file>MyDerived.java</file> <line>11</line> <problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Class is not registered in the manifest</problem_class> - <description>The &lt;activity> p1.p2.MyDerived is not registered in the manifest</description> + <description><html>The &lt;activity> p1.p2.MyDerived is not registered in the manifest</html></description> </problem> <problem> <file>MyDerived.java</file> <line>12</line> - <description>The &lt;service> p1.p2.MyDerived.MyInner is not registered in the manifest</description> + <description><html>The &lt;service> p1.p2.MyDerived.MyInner is not registered in the manifest</html></description> </problem> <problem> <file>MyDerived.java</file> <line>20</line> - <description>The &lt;provider> p1.p2.MyDerived.MyInner3 is not registered in the manifest</description> + <description><html>The &lt;provider> p1.p2.MyDerived.MyInner3 is not registered in the manifest</html></description> </problem> </problems> diff --git a/android/testData/lint/global/apiCheck1/expected.xml b/android/testData/lint/global/apiCheck1/expected.xml index f2baa060792..b56a4d96581 100644 --- a/android/testData/lint/global/apiCheck1/expected.xml +++ b/android/testData/lint/global/apiCheck1/expected.xml @@ -18,13 +18,13 @@ <problem> <file>MyActivity.java</file> <line>23</line> - <description>Call requires API level 11 (current min is 1): android.app.Activity#getActionBar</description> + <description><html>Call requires API level 11 (current min is 1): android.app.Activity#getActionBar</html></description> </problem> <problem> <file>MyActivity.java</file> <line>24</line> - <description>Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout</description> + <description><html>Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout</html></description> </problem> <!-- Missing getActionBar() call on line 26 --> @@ -32,13 +32,13 @@ <problem> <file>MyActivity.java</file> <line>27</line> - <description>Call requires API level 14 (current min is 1): android.widget.GridLayout#getAlignmentMode</description> + <description><html>Call requires API level 14 (current min is 1): android.widget.GridLayout#getAlignmentMode</html></description> </problem> <problem> <file>MyActivity.java</file> <line>28</line> - <description>Call requires API level 14 (current min is 1): android.widget.GridLayout#setRowOrderPreserved</description> + <description><html>Call requires API level 14 (current min is 1): android.widget.GridLayout#setRowOrderPreserved</html></description> </problem> </problems> diff --git a/android/testData/lint/global/apiInlined/expected.xml b/android/testData/lint/global/apiInlined/expected.xml index 351b6249fe5..50448d9d561 100644 --- a/android/testData/lint/global/apiInlined/expected.xml +++ b/android/testData/lint/global/apiInlined/expected.xml @@ -19,6 +19,6 @@ <file>MyActivity.java</file> <line>26</line> <problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Using inlined constants on older versions</problem_class> - <description>Field requires API level 11 (current min is 1): android.view.View#MEASURED_HEIGHT_STATE_SHIFT</description> + <description><html>Field requires API level 11 (current min is 1): android.view.View#MEASURED_HEIGHT_STATE_SHIFT</html></description> </problem> </problems> diff --git a/android/testData/lint/global/apiOverride/expected.xml b/android/testData/lint/global/apiOverride/expected.xml index fb084f39acd..8fea32594ed 100644 --- a/android/testData/lint/global/apiOverride/expected.xml +++ b/android/testData/lint/global/apiOverride/expected.xml @@ -19,6 +19,6 @@ <file>MyActivity.java</file> <line>55</line> <problem_class severity="ERROR" attribute_key="ERRORS_ATTRIBUTES">Method conflicts with new inherited method</problem_class> - <description>This method is not overriding anything with the current build target, but will in API level 17 (current target is 16): p1.p2.MyActivity#isDestroyed</description> + <description><html>This method is not overriding anything with the current build target, but will in API level 17 (current target is 16): p1.p2.MyActivity#isDestroyed</html></description> </problem> </problems> diff --git a/android/testData/lint/global/buttonsOrder/expected.xml b/android/testData/lint/global/buttonsOrder/expected.xml index 44f6565e701..14cfc61bcd9 100644 --- a/android/testData/lint/global/buttonsOrder/expected.xml +++ b/android/testData/lint/global/buttonsOrder/expected.xml @@ -3,6 +3,6 @@ <problem> <file>layout.xml</file> <line>15</line> - <description>Layout uses the wrong button order for API >= 14: Create a <code>layout-v14/layout.xml</code> file with opposite order: OK button should be on the right (was "OK | Cancel", should be "Cancel | OK")</description> + <description><html>Layout uses the wrong button order for API >= 14: Create a <code>layout-v14/layout.xml</code> file with opposite order: OK button should be on the right (was "OK | Cancel", should be "Cancel | OK")</html></description> </problem> </problems>
\ No newline at end of file diff --git a/android/testData/lint/global/duplicateIcons/expected.xml b/android/testData/lint/global/duplicateIcons/expected.xml index d376ce89bbc..1159eb4dc69 100644 --- a/android/testData/lint/global/duplicateIcons/expected.xml +++ b/android/testData/lint/global/duplicateIcons/expected.xml @@ -3,11 +3,11 @@ <problem> <file>dup2.png</file> <line>0</line> - <description>The following unrelated icon files have identical contents: dup1.png, dup2.png</description> + <description><html>The following unrelated icon files have identical contents: dup1.png, dup2.png</html></description> </problem> <problem> <file>dup1.png</file> <line>0</line> - <description>The following unrelated icon files have identical contents: dup1.png, dup2.png</description> + <description><html>The following unrelated icon files have identical contents: dup1.png, dup2.png</html></description> </problem> </problems>
\ No newline at end of file diff --git a/android/testData/lint/global/lintInJavaFile/expected.xml b/android/testData/lint/global/lintInJavaFile/expected.xml index 3c045d16435..1a06ca03630 100644 --- a/android/testData/lint/global/lintInJavaFile/expected.xml +++ b/android/testData/lint/global/lintInJavaFile/expected.xml @@ -3,6 +3,6 @@ <problem> <file>MyActivity.java</file> <line>15</line> - <description>Use <code>Integer.valueOf(3)</code> instead</description> + <description><html>Use <code>Integer.valueOf(3)</code> instead</html></description> </problem> </problems>
\ No newline at end of file diff --git a/android/testData/lint/global/manifestOrder/expected.xml b/android/testData/lint/global/manifestOrder/expected.xml index a74086013b1..11612f19cbb 100644 --- a/android/testData/lint/global/manifestOrder/expected.xml +++ b/android/testData/lint/global/manifestOrder/expected.xml @@ -3,6 +3,6 @@ <problem> <file>AndroidManifest.xml</file> <line>9</line> - <description>{uses-sdk} tag appears after {application} tag</description> + <description><html>{uses-sdk} tag appears after {application} tag</html></description> </problem> </problems>
\ No newline at end of file diff --git a/android/testData/lint/global/proguard/expected.xml b/android/testData/lint/global/proguard/expected.xml index cadb632ad6d..3b7028d0f67 100644 --- a/android/testData/lint/global/proguard/expected.xml +++ b/android/testData/lint/global/proguard/expected.xml @@ -3,6 +3,6 @@ <problem> <file>proguard.cfg</file> <line>1</line> - <description>Obsolete ProGuard file; use -keepclasseswithmembers instead of -keepclasseswithmembernames</description> + <description><html>Obsolete ProGuard file; use -keepclasseswithmembers instead of -keepclasseswithmembernames</html></description> </problem> </problems>
\ No newline at end of file diff --git a/android/testData/lint/global/viewType/expected.xml b/android/testData/lint/global/viewType/expected.xml index 4e45e689dff..ec8cc599ca3 100644 --- a/android/testData/lint/global/viewType/expected.xml +++ b/android/testData/lint/global/viewType/expected.xml @@ -3,6 +3,6 @@ <problem> <file>MyActivity.java</file> <line>13</line> - <description>Unexpected cast to ImageView: layout tag was TextView</description> + <description><html>Unexpected cast to ImageView: layout tag was TextView</html></description> </problem> </problems>
\ No newline at end of file diff --git a/android/testSrc/com/android/tools/idea/editors/theme/NewStyleDialogTest.java b/android/testSrc/com/android/tools/idea/editors/theme/NewStyleDialogTest.java index 51b484cb5a0..fcb12b08116 100644 --- a/android/testSrc/com/android/tools/idea/editors/theme/NewStyleDialogTest.java +++ b/android/testSrc/com/android/tools/idea/editors/theme/NewStyleDialogTest.java @@ -43,7 +43,7 @@ public class NewStyleDialogTest extends AndroidTestCase { myFixture.copyFileToProject("themeEditor/attrs.xml", "res/values/attrs.xml"); Configuration configuration = myFacet.getConfigurationManager().getConfiguration(myFile); - ThemeEditorContext context = new ThemeEditorContext(configuration, myModule); + ThemeEditorContext context = new ThemeEditorContext(configuration); String styleName = "@android:style/TextAppearance.Medium"; NewStyleDialog dialog = new NewStyleDialog(false, context, styleName, "textAppearance", null); diff --git a/android/testSrc/com/android/tools/idea/editors/theme/ResolutionUtilsTest.java b/android/testSrc/com/android/tools/idea/editors/theme/ResolutionUtilsTest.java index dbdc4a450c4..2e370af2f61 100644 --- a/android/testSrc/com/android/tools/idea/editors/theme/ResolutionUtilsTest.java +++ b/android/testSrc/com/android/tools/idea/editors/theme/ResolutionUtilsTest.java @@ -39,9 +39,9 @@ public class ResolutionUtilsTest extends AndroidTestCase { VirtualFile myLayout = myFixture.copyFileToProject("themeEditor/layout.xml", "res/layout/layout1.xml"); Configuration configuration = myFacet.getConfigurationManager().getConfiguration(myLayout); - assertNotNull(ResolutionUtils.getStyle(configuration, "@android:style/TextAppearance")); + assertNotNull(ResolutionUtils.getStyle(configuration, "@android:style/TextAppearance", null)); - ThemeEditorStyle style = ResolutionUtils.getStyle(configuration, "@android:style/Theme.Holo.Light"); + ThemeEditorStyle style = ResolutionUtils.getStyle(configuration, "@android:style/Theme.Holo.Light", null); assertEquals("Theme.Holo.Light", style.getName()); assertEmpty(style.getValues()); // Style shouldn't have the values of the parent. diff --git a/android/testSrc/com/android/tools/idea/editors/theme/ThemeEditorStyleTest.java b/android/testSrc/com/android/tools/idea/editors/theme/ThemeEditorStyleTest.java index f6e3f29b202..a016a71ca06 100644 --- a/android/testSrc/com/android/tools/idea/editors/theme/ThemeEditorStyleTest.java +++ b/android/testSrc/com/android/tools/idea/editors/theme/ThemeEditorStyleTest.java @@ -312,7 +312,7 @@ public class ThemeEditorStyleTest extends AndroidTestCase { Configuration configuration = myFacet.getConfigurationManager().getConfiguration(myFile); configuration.setTarget(new CompatibilityRenderTarget(configuration.getTarget(), 22, null)); - ThemeEditorStyle myTheme = ResolutionUtils.getStyle(configuration, "@style/Theme.MyTheme"); + ThemeEditorStyle myTheme = ResolutionUtils.getStyle(configuration, "@style/Theme.MyTheme", null); assertNotNull(myTheme); Set<String> expectedAttributes = Sets.newHashSet("actionModeStyle", "windowIsFloating", "checkedTextViewStyle"); for(EditedStyleItem item : myTheme.getValues()) { @@ -345,7 +345,7 @@ public class ThemeEditorStyleTest extends AndroidTestCase { // Test with a v14 configuration configuration.setTarget(new CompatibilityRenderTarget(configuration.getTarget(), 14, null)); - myTheme = ResolutionUtils.getStyle(configuration, "@style/Theme.MyTheme"); + myTheme = ResolutionUtils.getStyle(configuration, "@style/Theme.MyTheme", null); assertNotNull(myTheme); assertSize(3, myTheme.getValues()); for(EditedStyleItem item : myTheme.getValues()) { diff --git a/android/testSrc/com/android/tools/idea/editors/theme/ThemeEditorUtilsTest.java b/android/testSrc/com/android/tools/idea/editors/theme/ThemeEditorUtilsTest.java index d8f6782ca1c..b8dd2136f04 100644 --- a/android/testSrc/com/android/tools/idea/editors/theme/ThemeEditorUtilsTest.java +++ b/android/testSrc/com/android/tools/idea/editors/theme/ThemeEditorUtilsTest.java @@ -82,7 +82,7 @@ public class ThemeEditorUtilsTest extends AndroidTestCase { assertEquals(7, values.size()); for (EditedStyleItem item : values) { - String doc = ThemeEditorUtils.generateToolTipText(item.getItemResourceValue(), myModule, configuration); + String doc = ThemeEditorUtils.generateToolTipText(item.getSelectedValue(), myModule, configuration); compareWithAns(doc, myFixture.getTestDataPath() + "/themeEditor/tooltipDocAns/" + item.getName() + ".ans"); } } @@ -176,7 +176,7 @@ public class ThemeEditorUtilsTest extends AndroidTestCase { Configuration configuration = myFacet.getConfigurationManager().getConfiguration(myFile); - ThemeEditorStyle theme = ResolutionUtils.getStyle(configuration, "AppTheme"); + ThemeEditorStyle theme = ResolutionUtils.getStyle(configuration, "AppTheme", null); List<EditedStyleItem> attributes = ThemeEditorUtils.resolveAllAttributes(theme); HashMap<String, EditedStyleItem> items = Maps.newHashMapWithExpectedSize(attributes.size()); diff --git a/android/testSrc/com/android/tools/idea/editors/theme/ThemeResolverTest.java b/android/testSrc/com/android/tools/idea/editors/theme/ThemeResolverTest.java index b83e2639822..a7e686111bb 100644 --- a/android/testSrc/com/android/tools/idea/editors/theme/ThemeResolverTest.java +++ b/android/testSrc/com/android/tools/idea/editors/theme/ThemeResolverTest.java @@ -41,7 +41,7 @@ public class ThemeResolverTest extends AndroidTestCase { ThemeEditorStyle theme = themeResolver.getTheme("@android:style/Theme.Holo.Light"); assertEquals("Theme.Holo.Light", theme.getName()); - assertEquals(themeResolver.getThemes().size(), themeResolver.getFrameworkThemes().size()); // Only framework themes. + assertEquals(themeResolver.getThemesCount(), themeResolver.getFrameworkThemes().size()); // Only framework themes. assertEmpty(themeResolver.getLocalThemes()); assertNull("Theme resolver shouldn't resolve styles", themeResolver.getTheme("@android:style/TextAppearance")); @@ -56,8 +56,7 @@ public class ThemeResolverTest extends AndroidTestCase { ThemeResolver themeResolver = new ThemeResolver(configuration); assertEquals(1, themeResolver.getLocalThemes().size()); // We don't have any libraries so this will only include the project theme - assertEquals(2, themeResolver.getProjectThemes().size()); // The Theme.MyTheme + parent Theme - assertEquals(themeResolver.getThemes().size(), themeResolver.getFrameworkThemes().size() + 1); // One local theme + assertEquals(0, themeResolver.getExternalLibraryThemes().size()); // No library themes assertNull("The theme is an app theme and shouldn't be returned for the android namespace", themeResolver.getTheme("@android:style/Theme.MyTheme")); diff --git a/android/testSrc/com/android/tools/idea/editors/theme/datamodels/EditedStyleItemTest.java b/android/testSrc/com/android/tools/idea/editors/theme/datamodels/EditedStyleItemTest.java index fbf0bec5222..2ea6f1d1a2c 100644 --- a/android/testSrc/com/android/tools/idea/editors/theme/datamodels/EditedStyleItemTest.java +++ b/android/testSrc/com/android/tools/idea/editors/theme/datamodels/EditedStyleItemTest.java @@ -41,11 +41,10 @@ public class EditedStyleItemTest extends AndroidTestCase { EditedStyleItem editedStyleItem = new EditedStyleItem( new ConfiguredItemResourceValue(new FolderConfiguration(), new ItemResourceValue("attribute", false, "selectedValue", false)), - items, - ResolutionUtils.getStyle(configuration, "@android:style/Theme")); + items, ResolutionUtils.getStyle(configuration, "@android:style/Theme", null)); assertEquals("selectedValue", editedStyleItem.getValue()); - assertEquals("selectedValue", editedStyleItem.getItemResourceValue().getValue()); + assertEquals("selectedValue", editedStyleItem.getSelectedValue().getValue()); assertEquals(1, editedStyleItem.getNonSelectedItemResourceValues().size()); ConfiguredItemResourceValue notSelectedItem = editedStyleItem.getNonSelectedItemResourceValues().iterator().next(); assertEquals("otherValue", notSelectedItem.myValue.getValue()); diff --git a/android/testSrc/org/jetbrains/android/inspections/ResourceTypeInspectionTest.java b/android/testSrc/org/jetbrains/android/inspections/ResourceTypeInspectionTest.java index 662b1a16196..abb91d14c17 100644 --- a/android/testSrc/org/jetbrains/android/inspections/ResourceTypeInspectionTest.java +++ b/android/testSrc/org/jetbrains/android/inspections/ResourceTypeInspectionTest.java @@ -381,6 +381,11 @@ public class ResourceTypeInspectionTest extends LightInspectionTestCase { " public void printBetweenFromExclusiveToInclusive(@FloatRange(from=2.5,to=5.0,fromInclusive=false) float arg) { }\n" + " public void printBetweenFromInclusiveToExclusive(@FloatRange(from=2.5,to=5.0,toInclusive=false) float arg) { }\n" + " public void printBetweenFromExclusiveToExclusive(@FloatRange(from=2.5,to=5.0,fromInclusive=false,toInclusive=false) float arg) { }\n" + + " public static final int MINIMUM = -1;\n" + + " public static final int MAXIMUM = 42;\n" + + " public static final int SIZE = 5;\n" + + " public void printIndirect(@IntRange(from = MINIMUM, to = MAXIMUM) int arg) { }\n" + + " public void printIndirectSize(@Size(SIZE) String arg) { }\n" + "\n" + " public void testLength() {\n" + " String arg = \"1234\";\n" + @@ -410,6 +415,7 @@ public class ResourceTypeInspectionTest extends LightInspectionTestCase { " printRange(\"12345\"); // OK\n" + " printRange(\"123456\"); // OK\n" + " printRange(/*Length must be at least 4 and at most 6 (was 7)*/\"1234567\"/**/); // ERROR\n" + + " printIndirectSize(/*Length must be exactly 5*/\"1234567\"/**/); // ERROR\n" + " }\n" + "\n" + " public void testSize() {\n" + @@ -469,6 +475,8 @@ public class ResourceTypeInspectionTest extends LightInspectionTestCase { " printBetween(/*Value must be ≥ 4 and ≤ 7 (was 8)*/8/**/); // ERROR\n" + " int value = 8;\n" + " printBetween(/*Value must be ≥ 4 and ≤ 7 (was 8)*/value/**/); // ERROR\n" + + " printBetween(/*Value must be ≥ 4 and ≤ 7 (was -7)*/-7/**/);\n" + + " printIndirect(/*Value must be ≥ -1 and ≤ 42 (was -2)*/-2/**/);\n" + " }\n" + "\n" + " public void testFloatRange() {\n" + @@ -479,6 +487,7 @@ public class ResourceTypeInspectionTest extends LightInspectionTestCase { " printAtLeastExclusive(/*Value must be > 2.5 (was 2.49f)*/2.49f/**/); // ERROR\n" + " printAtLeastExclusive(/*Value must be > 2.5 (was 2.5f)*/2.5f/**/); // ERROR\n" + " printAtLeastExclusive(2.501f); // OK\n" + + " printAtLeastExclusive(/*Value must be > 2.5 (was -10.0)*/-10/**/);\n" + "\n" + " printAtMostInclusive(6.8f); // OK\n" + " printAtMostInclusive(6.9f); // OK\n" + diff --git a/sdk-updates/src/com/android/tools/idea/updater/configure/PlatformComponentsPanel.java b/sdk-updates/src/com/android/tools/idea/updater/configure/PlatformComponentsPanel.java index 48c123dbe2f..2b5a18666f3 100644 --- a/sdk-updates/src/com/android/tools/idea/updater/configure/PlatformComponentsPanel.java +++ b/sdk-updates/src/com/android/tools/idea/updater/configure/PlatformComponentsPanel.java @@ -180,9 +180,11 @@ public class PlatformComponentsPanel { myStates.clear(); } + /** + * After changing whether previews are included, setPackages() must be called again with appropriately filtered packages. + */ public void setIncludePreview(boolean includePreview) { myIncludePreview = includePreview; - updatePlatformItems(); } public void setEnabled(boolean enabled) { diff --git a/sdk-updates/src/com/android/tools/idea/updater/configure/SdkUpdaterConfigPanel.java b/sdk-updates/src/com/android/tools/idea/updater/configure/SdkUpdaterConfigPanel.java index a664564fa69..6dcc72bbad3 100644 --- a/sdk-updates/src/com/android/tools/idea/updater/configure/SdkUpdaterConfigPanel.java +++ b/sdk-updates/src/com/android/tools/idea/updater/configure/SdkUpdaterConfigPanel.java @@ -199,6 +199,7 @@ public class SdkUpdaterConfigPanel { myChannelLink.setVisible(myHasPreview && !myIncludePreview); myPlatformComponentsPanel.setIncludePreview(includePreview); myToolComponentsPanel.setIncludePreview(includePreview); + loadPackages(mySdkState.getPackages()); } public JComponent getComponent() { diff --git a/sdk-updates/src/com/android/tools/idea/updater/configure/SdkUpdaterConfigurable.java b/sdk-updates/src/com/android/tools/idea/updater/configure/SdkUpdaterConfigurable.java index 88821d1ab54..4ec026e8fa8 100644 --- a/sdk-updates/src/com/android/tools/idea/updater/configure/SdkUpdaterConfigurable.java +++ b/sdk-updates/src/com/android/tools/idea/updater/configure/SdkUpdaterConfigurable.java @@ -23,6 +23,7 @@ import com.android.tools.idea.updater.SdkComponentSource; import com.android.utils.HtmlBuilder; import com.google.common.collect.Lists; import com.intellij.icons.AllIcons; +import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SearchableConfigurable; import com.intellij.openapi.project.Project; @@ -46,8 +47,8 @@ import java.util.List; * for the UI? */ public class SdkUpdaterConfigurable implements SearchableConfigurable { - SdkUpdaterConfigPanel myPanel; - boolean myIncludePreview; + private SdkUpdaterConfigPanel myPanel; + private boolean myIncludePreview; @NotNull @Override diff --git a/sdk-updates/src/com/android/tools/idea/updater/configure/ToolComponentsPanel.java b/sdk-updates/src/com/android/tools/idea/updater/configure/ToolComponentsPanel.java index ae13f186ba4..4fa47253cae 100644 --- a/sdk-updates/src/com/android/tools/idea/updater/configure/ToolComponentsPanel.java +++ b/sdk-updates/src/com/android/tools/idea/updater/configure/ToolComponentsPanel.java @@ -189,8 +189,10 @@ public class ToolComponentsPanel { myStates.clear(); } + /** + * After changing whether previews are included, setPackages() must be called again with appropriately filtered packages. + */ public void setIncludePreview(boolean includePreview) { myIncludePreview = includePreview; - updateToolsItems(); } } diff --git a/sdk-updates/src/com/android/tools/idea/updater/configure/UpdateSitesPanel.form b/sdk-updates/src/com/android/tools/idea/updater/configure/UpdateSitesPanel.form index 8d287cf94ea..ea27f36f1d7 100644 --- a/sdk-updates/src/com/android/tools/idea/updater/configure/UpdateSitesPanel.form +++ b/sdk-updates/src/com/android/tools/idea/updater/configure/UpdateSitesPanel.form @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.android.tools.idea.updater.configure.UpdateSitesPanel"> - <grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> <margin top="0" left="0" bottom="0" right="0"/> <constraints> <xy x="20" y="20" width="604" height="400"/> @@ -52,6 +52,14 @@ </component> </children> </grid> + <component id="5d507" class="javax.swing.JCheckBox" binding="myForceHttp"> + <constraints> + <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value="Force https://... sources to be fetched using http://..."/> + </properties> + </component> </children> </grid> </form> diff --git a/sdk-updates/src/com/android/tools/idea/updater/configure/UpdateSitesPanel.java b/sdk-updates/src/com/android/tools/idea/updater/configure/UpdateSitesPanel.java index a8d28f2654c..f67c4070bd9 100644 --- a/sdk-updates/src/com/android/tools/idea/updater/configure/UpdateSitesPanel.java +++ b/sdk-updates/src/com/android/tools/idea/updater/configure/UpdateSitesPanel.java @@ -17,13 +17,13 @@ package com.android.tools.idea.updater.configure; import com.android.tools.idea.sdk.SdkState; import com.android.tools.idea.sdk.remote.internal.sources.SdkSources; +import com.android.tools.idea.sdk.remote.internal.updater.SettingsController; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.ui.AnActionButton; import com.intellij.ui.AnActionButtonRunnable; import com.intellij.ui.AnActionButtonUpdater; import com.intellij.ui.ToolbarDecorator; -import com.intellij.ui.table.JBTable; import com.intellij.ui.table.TableView; import com.intellij.util.ui.AsyncProcessIcon; import org.jetbrains.annotations.NotNull; @@ -39,7 +39,9 @@ public class UpdateSitesPanel { private JPanel mySourcesPanel; private JPanel mySourcesLoadingPanel; private AsyncProcessIcon mySourcesLoadingIcon; + private JCheckBox myForceHttp; private SourcesTableModel mySourcesTableModel; + private static SettingsController ourSettingsController = SettingsController.getInstance(); private void createUIComponents() { mySourcesLoadingIcon = new AsyncProcessIcon("Loading..."); @@ -102,11 +104,12 @@ public class UpdateSitesPanel { } public boolean isModified() { - return mySourcesTableModel.isSourcesModified(); + return mySourcesTableModel.isSourcesModified() || ourSettingsController.getForceHttp() != myForceHttp.isSelected(); } public void reset() { mySourcesTableModel.reset(); + myForceHttp.setSelected(ourSettingsController.getForceHttp()); } public void setSdkState(SdkState state) { @@ -115,6 +118,7 @@ public class UpdateSitesPanel { public void save() { mySourcesTableModel.save(); + ourSettingsController.setForceHttp(myForceHttp.isSelected()); } public void startLoading() { @@ -125,5 +129,4 @@ public class UpdateSitesPanel { mySourcesTableModel.refreshSources(); mySourcesLoadingPanel.setVisible(false); } - } |