summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xandroid/gen/icons/AndroidIcons.java14
-rw-r--r--android/guiTestSrc/com/android/tools/idea/tests/gui/editing/RefactoringFlowTest.java1
-rw-r--r--android/guiTestSrc/com/android/tools/idea/tests/gui/editors/translations/TranslationsEditorTest.java19
-rw-r--r--android/guiTestSrc/com/android/tools/idea/tests/gui/framework/GuiTestRunner.java43
-rw-r--r--android/guiTestSrc/com/android/tools/idea/tests/gui/framework/MethodInvoker.java13
-rw-r--r--android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/ChooseResourceDialogFixture.java57
-rw-r--r--android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/EditorFixture.java38
-rw-r--r--android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/theme/ThemeEditorFixture.java4
-rw-r--r--android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTableTest.java29
-rw-r--r--android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTestUtils.java6
-rw-r--r--android/resources/icons/nele/api.pngbin0 -> 331 bytes
-rw-r--r--android/resources/icons/nele/api@2x.pngbin0 -> 662 bytes
-rw-r--r--android/resources/icons/nele/api@2x_dark.pngbin0 -> 700 bytes
-rw-r--r--android/resources/icons/nele/api_dark.pngbin0 -> 341 bytes
-rw-r--r--android/resources/icons/nele/language.pngbin0 -> 539 bytes
-rw-r--r--android/resources/icons/nele/language@2x.pngbin0 -> 1277 bytes
-rw-r--r--android/resources/icons/nele/language@2x_dark.pngbin0 -> 1291 bytes
-rw-r--r--android/resources/icons/nele/language_dark.pngbin0 -> 547 bytes
-rw-r--r--android/resources/icons/nele/phone.pngbin0 -> 289 bytes
-rw-r--r--android/resources/icons/nele/phone@2x.pngbin0 -> 420 bytes
-rw-r--r--android/resources/icons/nele/phone@2x_dark.pngbin0 -> 439 bytes
-rw-r--r--android/resources/icons/nele/phone_dark.pngbin0 -> 303 bytes
-rw-r--r--android/resources/icons/nele/preview.pngbin0 -> 188 bytes
-rw-r--r--android/resources/icons/nele/preview@2x.pngbin0 -> 229 bytes
-rw-r--r--android/resources/icons/nele/preview@2x_dark.pngbin0 -> 230 bytes
-rw-r--r--android/resources/icons/nele/preview_dark.pngbin0 -> 268 bytes
-rw-r--r--android/resources/icons/nele/rotate.pngbin0 -> 591 bytes
-rw-r--r--android/resources/icons/nele/rotate@2x.pngbin0 -> 1243 bytes
-rw-r--r--android/resources/icons/nele/rotate@2x_dark.pngbin0 -> 1235 bytes
-rw-r--r--android/resources/icons/nele/rotate_dark.pngbin0 -> 592 bytes
-rw-r--r--android/resources/icons/nele/size.pngbin0 -> 300 bytes
-rw-r--r--android/resources/icons/nele/size@2x.pngbin0 -> 413 bytes
-rw-r--r--android/resources/icons/nele/size@2x_dark.pngbin0 -> 422 bytes
-rw-r--r--android/resources/icons/nele/size_dark.pngbin0 -> 314 bytes
-rw-r--r--android/resources/icons/nele/tablet.pngbin0 -> 273 bytes
-rw-r--r--android/resources/icons/nele/tablet@2x.pngbin0 -> 375 bytes
-rw-r--r--android/resources/icons/nele/tablet@2x_dark.pngbin0 -> 388 bytes
-rw-r--r--android/resources/icons/nele/tablet_dark.pngbin0 -> 275 bytes
-rw-r--r--android/resources/icons/nele/theme.pngbin0 -> 417 bytes
-rw-r--r--android/resources/icons/nele/theme@2x.pngbin0 -> 840 bytes
-rw-r--r--android/resources/icons/nele/theme@2x_dark.pngbin0 -> 849 bytes
-rw-r--r--android/resources/icons/nele/theme_dark.pngbin0 -> 413 bytes
-rw-r--r--android/resources/icons/nele/tv.pngbin0 -> 253 bytes
-rw-r--r--android/resources/icons/nele/tv@2x.pngbin0 -> 388 bytes
-rw-r--r--android/resources/icons/nele/tv@2x_dark.pngbin0 -> 394 bytes
-rw-r--r--android/resources/icons/nele/tv_dark.pngbin0 -> 258 bytes
-rw-r--r--android/resources/icons/nele/wear.pngbin0 -> 399 bytes
-rw-r--r--android/resources/icons/nele/wear@2x.pngbin0 -> 798 bytes
-rw-r--r--android/resources/icons/nele/wear@2x_dark.pngbin0 -> 811 bytes
-rw-r--r--android/resources/icons/nele/wear_dark.pngbin0 -> 437 bytes
-rw-r--r--android/resources/messages/AndroidBundle.properties2
-rwxr-xr-xandroid/src/META-INF/plugin.xml5
-rw-r--r--android/src/com/android/tools/idea/actions/AndroidNewProjectAction.java2
-rw-r--r--android/src/com/android/tools/idea/actions/ShowLicensesUsedAction.java7
-rw-r--r--android/src/com/android/tools/idea/configurations/DeviceMenuAction.java56
-rw-r--r--android/src/com/android/tools/idea/configurations/LocaleMenuAction.java41
-rw-r--r--android/src/com/android/tools/idea/configurations/OrientationMenuAction.java10
-rw-r--r--android/src/com/android/tools/idea/configurations/TargetMenuAction.java29
-rw-r--r--android/src/com/android/tools/idea/configurations/ThemeSelectionPanel.java12
-rw-r--r--android/src/com/android/tools/idea/ddms/actions/ScreenshotAction.java1
-rw-r--r--android/src/com/android/tools/idea/editors/allocations/AllocationCaptureType.java2
-rw-r--r--android/src/com/android/tools/idea/editors/allocations/AllocationsFileType.java69
-rw-r--r--android/src/com/android/tools/idea/editors/allocations/AllocationsFileTypeFactory.java27
-rw-r--r--android/src/com/android/tools/idea/editors/allocations/AllocationsView.java3
-rw-r--r--android/src/com/android/tools/idea/editors/allocations/ColumnTreeBuilder.java10
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/ComputeDominatorAction.java63
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/HprofEditor.java9
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/HprofFileType.java69
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/HprofFileTypeFactory.java27
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/HprofViewPanel.java29
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/descriptors/InstanceFieldDescriptorImpl.java46
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/tables/ClassesTreeView.java459
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/views/ClassesTreeView.java670
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/views/InstanceReferenceTreeView.java (renamed from android/src/com/android/tools/idea/editors/hprof/tables/InstanceReferenceTree.java)9
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/views/InstancesTreeView.java (renamed from android/src/com/android/tools/idea/editors/hprof/tables/InstancesTree.java)11
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/views/SelectionModel.java (renamed from android/src/com/android/tools/idea/editors/hprof/tables/SelectionModel.java)8
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/views/nodedata/HeapClassObjNode.java153
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/views/nodedata/HeapNode.java51
-rw-r--r--android/src/com/android/tools/idea/editors/hprof/views/nodedata/HeapPackageNode.java225
-rw-r--r--android/src/com/android/tools/idea/editors/theme/NewStyleDialog.java2
-rw-r--r--android/src/com/android/tools/idea/editors/theme/ResolutionUtils.java8
-rw-r--r--android/src/com/android/tools/idea/editors/theme/ResourcesCompletionProvider.java2
-rw-r--r--android/src/com/android/tools/idea/editors/theme/ThemeEditorComponent.java94
-rw-r--r--android/src/com/android/tools/idea/editors/theme/ThemeEditorContext.java54
-rw-r--r--android/src/com/android/tools/idea/editors/theme/ThemeEditorProvider.java2
-rw-r--r--android/src/com/android/tools/idea/editors/theme/ThemeEditorUtils.java26
-rw-r--r--android/src/com/android/tools/idea/editors/theme/ThemeResolver.java239
-rw-r--r--android/src/com/android/tools/idea/editors/theme/attributes/AttributesModelColorPaletteModel.java2
-rw-r--r--android/src/com/android/tools/idea/editors/theme/attributes/AttributesTableModel.java6
-rw-r--r--android/src/com/android/tools/idea/editors/theme/attributes/ShowJavadocAction.java2
-rw-r--r--android/src/com/android/tools/idea/editors/theme/attributes/editors/BooleanRendererEditor.java7
-rw-r--r--android/src/com/android/tools/idea/editors/theme/attributes/editors/ColorRendererEditor.java24
-rw-r--r--android/src/com/android/tools/idea/editors/theme/attributes/editors/DelegatingCellEditor.java2
-rw-r--r--android/src/com/android/tools/idea/editors/theme/attributes/editors/DelegatingCellRenderer.java2
-rw-r--r--android/src/com/android/tools/idea/editors/theme/attributes/editors/DrawableRendererEditor.java8
-rw-r--r--android/src/com/android/tools/idea/editors/theme/attributes/editors/EnumRendererEditor.java2
-rw-r--r--android/src/com/android/tools/idea/editors/theme/attributes/editors/FlagRendererEditor.java2
-rw-r--r--android/src/com/android/tools/idea/editors/theme/attributes/editors/StyleListCellRenderer.java2
-rw-r--r--android/src/com/android/tools/idea/editors/theme/datamodels/EditedStyleItem.java8
-rw-r--r--android/src/com/android/tools/idea/editors/theme/datamodels/ThemeEditorStyle.java61
-rw-r--r--android/src/com/android/tools/idea/editors/vmtrace/VmTraceFileType.java69
-rw-r--r--android/src/com/android/tools/idea/editors/vmtrace/VmTraceFileTypeFactory.java27
-rw-r--r--android/src/com/android/tools/idea/profiling/capture/FileCaptureType.java6
-rw-r--r--android/src/com/android/tools/idea/profiling/view/CapturesToolWindow.java2
-rw-r--r--android/src/com/android/tools/idea/profiling/view/CapturesToolWindowFactory.java4
-rw-r--r--android/src/com/android/tools/idea/rendering/AarResourceClassRegistry.java37
-rw-r--r--android/src/com/android/tools/idea/rendering/AppResourceRepository.java5
-rw-r--r--android/src/com/android/tools/idea/rendering/FlagManager.java25
-rw-r--r--android/src/com/android/tools/idea/rendering/RenderLogger.java4
-rw-r--r--android/src/com/android/tools/idea/rendering/ResourceHelper.java12
-rwxr-xr-xandroid/src/com/android/tools/idea/sdk/SdkState.java5
-rwxr-xr-xandroid/src/com/android/tools/idea/sdk/remote/RemoteSdk.java14
-rwxr-xr-xandroid/src/com/android/tools/idea/sdk/remote/internal/AddonsListFetcher.java1
-rwxr-xr-xandroid/src/com/android/tools/idea/sdk/remote/internal/CanceledByUserException.java2
-rwxr-xr-xandroid/src/com/android/tools/idea/sdk/remote/internal/DownloadCache.java113
-rw-r--r--android/src/com/android/tools/idea/sdk/remote/internal/UrlOpener.java510
-rwxr-xr-xandroid/src/com/android/tools/idea/sdk/remote/internal/archives/ArchiveInstaller.java17
-rwxr-xr-xandroid/src/com/android/tools/idea/sdk/remote/internal/updater/SdkUpdaterNoWindow.java38
-rwxr-xr-xandroid/src/com/android/tools/idea/sdk/remote/internal/updater/SettingsController.java255
-rwxr-xr-xandroid/src/com/android/tools/idea/sdk/remote/internal/updater/UpdaterData.java55
-rwxr-xr-xandroid/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.java1
-rw-r--r--android/src/com/android/tools/idea/structure/services/ServiceXmlParser.java237
-rw-r--r--android/src/com/android/tools/idea/templates/RepositoryUrlManager.java41
-rw-r--r--android/src/com/android/tools/idea/welcome/install/ComponentInstaller.java2
-rw-r--r--android/src/com/android/tools/idea/wizard/IconPicker.java11
-rw-r--r--android/src/com/android/tools/idea/wizard/NewProjectWizardDynamic.java2
-rw-r--r--android/src/com/android/tools/swing/layoutlib/GraphicsLayoutRenderer.java8
-rw-r--r--android/src/org/jetbrains/android/facet/IdeaSourceProvider.java10
-rw-r--r--android/src/org/jetbrains/android/inspections/ResourceTypeInspection.java45
-rw-r--r--android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionToolProvider.java42
-rw-r--r--android/src/org/jetbrains/android/uipreview/ChooseResourceDialog.java28
-rw-r--r--android/src/org/jetbrains/android/uipreview/ModuleClassLoader.java7
-rw-r--r--android/src/org/jetbrains/android/uipreview/RecyclerViewHelper.java49
-rw-r--r--android/src/org/jetbrains/android/uipreview/ResourceDialogSouthPanel.form1
-rw-r--r--android/testData/guiTests/CapturesApplication/.gitignore17
-rw-r--r--android/testData/guiTests/CapturesApplication/.idea/.name1
-rw-r--r--android/testData/guiTests/CapturesApplication/.idea/modules.xml10
-rw-r--r--android/testData/guiTests/CapturesApplication/CapturesApplication.iml19
-rw-r--r--android/testData/guiTests/CapturesApplication/app/.gitignore1
-rw-r--r--android/testData/guiTests/CapturesApplication/app/app.iml85
-rw-r--r--android/testData/guiTests/CapturesApplication/app/build.gradle29
-rw-r--r--android/testData/guiTests/CapturesApplication/app/proguard-rules.pro17
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/androidTest/java/google/capturesapplication/ApplicationTest.java13
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/main/AndroidManifest.xml21
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/main/java/google/capturesapplication/MyActivity.java36
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/main/res/drawable/ic_launcher.xml2
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/main/res/layout/activity_my.xml16
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/main/res/menu/my.xml8
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/main/res/values-en-rGB/strings.xml22
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/main/res/values-en/strings.xml8
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/main/res/values-ta/strings.xml8
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/main/res/values-w820dp/dimens.xml6
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/main/res/values-zh-rCN/strings.xml22
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/main/res/values/dimens.xml5
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/main/res/values/strings.xml8
-rw-r--r--android/testData/guiTests/CapturesApplication/app/src/main/res/values/styles.xml8
-rw-r--r--[-rwxr-xr-x]android/testData/guiTests/CapturesApplication/app/src/test/java/google/capturesapplication/UnitTest.java (renamed from android/src/com/android/tools/idea/sdk/remote/internal/updater/IUpdaterData.java)36
-rw-r--r--android/testData/guiTests/CapturesApplication/build.gradle22
-rw-r--r--android/testData/guiTests/CapturesApplication/captures/snapshot.hprofbin0 -> 14316853 bytes
-rw-r--r--android/testData/guiTests/CapturesApplication/settings.gradle1
-rw-r--r--android/testData/lint/global/activityRegistered/expected.xml6
-rw-r--r--android/testData/lint/global/apiCheck1/expected.xml8
-rw-r--r--android/testData/lint/global/apiInlined/expected.xml2
-rw-r--r--android/testData/lint/global/apiOverride/expected.xml2
-rw-r--r--android/testData/lint/global/buttonsOrder/expected.xml2
-rw-r--r--android/testData/lint/global/duplicateIcons/expected.xml4
-rw-r--r--android/testData/lint/global/lintInJavaFile/expected.xml2
-rw-r--r--android/testData/lint/global/manifestOrder/expected.xml2
-rw-r--r--android/testData/lint/global/proguard/expected.xml2
-rw-r--r--android/testData/lint/global/viewType/expected.xml2
-rw-r--r--android/testSrc/com/android/tools/idea/editors/theme/NewStyleDialogTest.java2
-rw-r--r--android/testSrc/com/android/tools/idea/editors/theme/ResolutionUtilsTest.java4
-rw-r--r--android/testSrc/com/android/tools/idea/editors/theme/ThemeEditorStyleTest.java4
-rw-r--r--android/testSrc/com/android/tools/idea/editors/theme/ThemeEditorUtilsTest.java4
-rw-r--r--android/testSrc/com/android/tools/idea/editors/theme/ThemeResolverTest.java5
-rw-r--r--android/testSrc/com/android/tools/idea/editors/theme/datamodels/EditedStyleItemTest.java5
-rw-r--r--android/testSrc/org/jetbrains/android/inspections/ResourceTypeInspectionTest.java9
-rw-r--r--sdk-updates/src/com/android/tools/idea/updater/configure/PlatformComponentsPanel.java4
-rw-r--r--sdk-updates/src/com/android/tools/idea/updater/configure/SdkUpdaterConfigPanel.java1
-rw-r--r--sdk-updates/src/com/android/tools/idea/updater/configure/SdkUpdaterConfigurable.java5
-rw-r--r--sdk-updates/src/com/android/tools/idea/updater/configure/ToolComponentsPanel.java4
-rw-r--r--sdk-updates/src/com/android/tools/idea/updater/configure/UpdateSitesPanel.form10
-rw-r--r--sdk-updates/src/com/android/tools/idea/updater/configure/UpdateSitesPanel.java9
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
new file mode 100644
index 00000000000..509221d162f
--- /dev/null
+++ b/android/resources/icons/nele/api.png
Binary files differ
diff --git a/android/resources/icons/nele/api@2x.png b/android/resources/icons/nele/api@2x.png
new file mode 100644
index 00000000000..3c97d54a4a3
--- /dev/null
+++ b/android/resources/icons/nele/api@2x.png
Binary files differ
diff --git a/android/resources/icons/nele/api@2x_dark.png b/android/resources/icons/nele/api@2x_dark.png
new file mode 100644
index 00000000000..35a2ada7676
--- /dev/null
+++ b/android/resources/icons/nele/api@2x_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/api_dark.png b/android/resources/icons/nele/api_dark.png
new file mode 100644
index 00000000000..87f16931561
--- /dev/null
+++ b/android/resources/icons/nele/api_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/language.png b/android/resources/icons/nele/language.png
new file mode 100644
index 00000000000..d4546020525
--- /dev/null
+++ b/android/resources/icons/nele/language.png
Binary files differ
diff --git a/android/resources/icons/nele/language@2x.png b/android/resources/icons/nele/language@2x.png
new file mode 100644
index 00000000000..f8aa012a498
--- /dev/null
+++ b/android/resources/icons/nele/language@2x.png
Binary files differ
diff --git a/android/resources/icons/nele/language@2x_dark.png b/android/resources/icons/nele/language@2x_dark.png
new file mode 100644
index 00000000000..ec870e0a699
--- /dev/null
+++ b/android/resources/icons/nele/language@2x_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/language_dark.png b/android/resources/icons/nele/language_dark.png
new file mode 100644
index 00000000000..4bedb12faf8
--- /dev/null
+++ b/android/resources/icons/nele/language_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/phone.png b/android/resources/icons/nele/phone.png
new file mode 100644
index 00000000000..c3aeaec51a2
--- /dev/null
+++ b/android/resources/icons/nele/phone.png
Binary files differ
diff --git a/android/resources/icons/nele/phone@2x.png b/android/resources/icons/nele/phone@2x.png
new file mode 100644
index 00000000000..bcff2f60b0e
--- /dev/null
+++ b/android/resources/icons/nele/phone@2x.png
Binary files differ
diff --git a/android/resources/icons/nele/phone@2x_dark.png b/android/resources/icons/nele/phone@2x_dark.png
new file mode 100644
index 00000000000..25496665440
--- /dev/null
+++ b/android/resources/icons/nele/phone@2x_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/phone_dark.png b/android/resources/icons/nele/phone_dark.png
new file mode 100644
index 00000000000..cfa93521652
--- /dev/null
+++ b/android/resources/icons/nele/phone_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/preview.png b/android/resources/icons/nele/preview.png
new file mode 100644
index 00000000000..68fa9954197
--- /dev/null
+++ b/android/resources/icons/nele/preview.png
Binary files differ
diff --git a/android/resources/icons/nele/preview@2x.png b/android/resources/icons/nele/preview@2x.png
new file mode 100644
index 00000000000..8ce9031d982
--- /dev/null
+++ b/android/resources/icons/nele/preview@2x.png
Binary files differ
diff --git a/android/resources/icons/nele/preview@2x_dark.png b/android/resources/icons/nele/preview@2x_dark.png
new file mode 100644
index 00000000000..365441e073d
--- /dev/null
+++ b/android/resources/icons/nele/preview@2x_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/preview_dark.png b/android/resources/icons/nele/preview_dark.png
new file mode 100644
index 00000000000..480a7c4a8cc
--- /dev/null
+++ b/android/resources/icons/nele/preview_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/rotate.png b/android/resources/icons/nele/rotate.png
new file mode 100644
index 00000000000..780a73a7a6c
--- /dev/null
+++ b/android/resources/icons/nele/rotate.png
Binary files differ
diff --git a/android/resources/icons/nele/rotate@2x.png b/android/resources/icons/nele/rotate@2x.png
new file mode 100644
index 00000000000..3b76df27c7b
--- /dev/null
+++ b/android/resources/icons/nele/rotate@2x.png
Binary files differ
diff --git a/android/resources/icons/nele/rotate@2x_dark.png b/android/resources/icons/nele/rotate@2x_dark.png
new file mode 100644
index 00000000000..a00918e5c1b
--- /dev/null
+++ b/android/resources/icons/nele/rotate@2x_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/rotate_dark.png b/android/resources/icons/nele/rotate_dark.png
new file mode 100644
index 00000000000..924def96477
--- /dev/null
+++ b/android/resources/icons/nele/rotate_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/size.png b/android/resources/icons/nele/size.png
new file mode 100644
index 00000000000..858d48ddf5b
--- /dev/null
+++ b/android/resources/icons/nele/size.png
Binary files differ
diff --git a/android/resources/icons/nele/size@2x.png b/android/resources/icons/nele/size@2x.png
new file mode 100644
index 00000000000..de8fd625541
--- /dev/null
+++ b/android/resources/icons/nele/size@2x.png
Binary files differ
diff --git a/android/resources/icons/nele/size@2x_dark.png b/android/resources/icons/nele/size@2x_dark.png
new file mode 100644
index 00000000000..75425fa0327
--- /dev/null
+++ b/android/resources/icons/nele/size@2x_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/size_dark.png b/android/resources/icons/nele/size_dark.png
new file mode 100644
index 00000000000..71b02e61284
--- /dev/null
+++ b/android/resources/icons/nele/size_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/tablet.png b/android/resources/icons/nele/tablet.png
new file mode 100644
index 00000000000..db395003ef5
--- /dev/null
+++ b/android/resources/icons/nele/tablet.png
Binary files differ
diff --git a/android/resources/icons/nele/tablet@2x.png b/android/resources/icons/nele/tablet@2x.png
new file mode 100644
index 00000000000..4fb306477ce
--- /dev/null
+++ b/android/resources/icons/nele/tablet@2x.png
Binary files differ
diff --git a/android/resources/icons/nele/tablet@2x_dark.png b/android/resources/icons/nele/tablet@2x_dark.png
new file mode 100644
index 00000000000..f8d1edc9312
--- /dev/null
+++ b/android/resources/icons/nele/tablet@2x_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/tablet_dark.png b/android/resources/icons/nele/tablet_dark.png
new file mode 100644
index 00000000000..c8e12ab824b
--- /dev/null
+++ b/android/resources/icons/nele/tablet_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/theme.png b/android/resources/icons/nele/theme.png
new file mode 100644
index 00000000000..0f2be6c8b6c
--- /dev/null
+++ b/android/resources/icons/nele/theme.png
Binary files differ
diff --git a/android/resources/icons/nele/theme@2x.png b/android/resources/icons/nele/theme@2x.png
new file mode 100644
index 00000000000..e9e7586926d
--- /dev/null
+++ b/android/resources/icons/nele/theme@2x.png
Binary files differ
diff --git a/android/resources/icons/nele/theme@2x_dark.png b/android/resources/icons/nele/theme@2x_dark.png
new file mode 100644
index 00000000000..08e0581614e
--- /dev/null
+++ b/android/resources/icons/nele/theme@2x_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/theme_dark.png b/android/resources/icons/nele/theme_dark.png
new file mode 100644
index 00000000000..2ec8819db91
--- /dev/null
+++ b/android/resources/icons/nele/theme_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/tv.png b/android/resources/icons/nele/tv.png
new file mode 100644
index 00000000000..5345b7466e3
--- /dev/null
+++ b/android/resources/icons/nele/tv.png
Binary files differ
diff --git a/android/resources/icons/nele/tv@2x.png b/android/resources/icons/nele/tv@2x.png
new file mode 100644
index 00000000000..e3ff800f5ee
--- /dev/null
+++ b/android/resources/icons/nele/tv@2x.png
Binary files differ
diff --git a/android/resources/icons/nele/tv@2x_dark.png b/android/resources/icons/nele/tv@2x_dark.png
new file mode 100644
index 00000000000..c03d78cc1fa
--- /dev/null
+++ b/android/resources/icons/nele/tv@2x_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/tv_dark.png b/android/resources/icons/nele/tv_dark.png
new file mode 100644
index 00000000000..97cffef303b
--- /dev/null
+++ b/android/resources/icons/nele/tv_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/wear.png b/android/resources/icons/nele/wear.png
new file mode 100644
index 00000000000..165f51927ea
--- /dev/null
+++ b/android/resources/icons/nele/wear.png
Binary files differ
diff --git a/android/resources/icons/nele/wear@2x.png b/android/resources/icons/nele/wear@2x.png
new file mode 100644
index 00000000000..19e2321e708
--- /dev/null
+++ b/android/resources/icons/nele/wear@2x.png
Binary files differ
diff --git a/android/resources/icons/nele/wear@2x_dark.png b/android/resources/icons/nele/wear@2x_dark.png
new file mode 100644
index 00000000000..96a3c3609b6
--- /dev/null
+++ b/android/resources/icons/nele/wear@2x_dark.png
Binary files differ
diff --git a/android/resources/icons/nele/wear_dark.png b/android/resources/icons/nele/wear_dark.png
new file mode 100644
index 00000000000..3b52183a3af
--- /dev/null
+++ b/android/resources/icons/nele/wear_dark.png
Binary files differ
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
new file mode 100644
index 00000000000..dd9267ac74a
--- /dev/null
+++ b/android/testData/guiTests/CapturesApplication/captures/snapshot.hprof
Binary files differ
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 &amp;lt;activity&gt; p1.p2.MyDerived is not registered in the manifest</description>
+ <description><html>The &amp;lt;activity&gt; p1.p2.MyDerived is not registered in the manifest</html></description>
</problem>
<problem>
<file>MyDerived.java</file>
<line>12</line>
- <description>The &amp;lt;service&gt; p1.p2.MyDerived.MyInner is not registered in the manifest</description>
+ <description><html>The &amp;lt;service&gt; p1.p2.MyDerived.MyInner is not registered in the manifest</html></description>
</problem>
<problem>
<file>MyDerived.java</file>
<line>20</line>
- <description>The &amp;lt;provider&gt; p1.p2.MyDerived.MyInner3 is not registered in the manifest</description>
+ <description><html>The &amp;lt;provider&gt; 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 &gt;= 14: Create a &lt;code&gt;layout-v14/layout.xml&lt;/code&gt; file with opposite order: OK button should be on the right (was &quot;OK | Cancel&quot;, should be &quot;Cancel | OK&quot;)</description>
+ <description><html>Layout uses the wrong button order for API &gt;= 14: Create a &lt;code&gt;layout-v14/layout.xml&lt;/code&gt; file with opposite order: OK button should be on the right (was &quot;OK | Cancel&quot;, should be &quot;Cancel | OK&quot;)</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 &lt;code&gt;Integer.valueOf(3)&lt;/code&gt; instead</description>
+ <description><html>Use &lt;code&gt;Integer.valueOf(3)&lt;/code&gt; 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);
}
-
}