Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Android Tool Windows Properly #22

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,18 @@ import org.jetbrains.android.facet.AndroidFacet
/**
* Returns true if called in Android Studio or if the project has an Android or an Apk facet.
*/
fun isAndroidEnvironment(project: Project): Boolean = true
fun isAndroidEnvironment(project: Project): Boolean =
IdeInfo.getInstance().isAndroidStudio || project.hasAndroidOrApkFacet()

/**
* Checks if the project contains a module with an Android or an Apk facet.
*/
fun Project.hasAndroidOrApkFacet(): Boolean =
ProjectFacetManager.getInstance(this).hasFacets(AndroidFacet.ID) || ApkFacetChecker.getInstance(this).hasApkFacet()

/**
* Checks if the project contains a module with an Android facet.
*/
fun Project.hasAndroidFacet(): Boolean =
ProjectFacetManager.getInstance(this).hasFacets(AndroidFacet.ID)

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2023 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

import com.android.tools.idea.isAndroidEnvironment
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ext.LibrarySearchHelper

/**
* A [LibrarySearchHelper] that returns true if in an Android environment
*
* An Android environment is either Android Studio or IntelliJ with an Android facet.
*
* Android Tool Windows should use this instead of `ToolWindowFactory.isApplicable()` to enable
* themselves. This is because an IntelliJ project may start up as a non Android project but then
* turn into one if an Android Module is added.
*
* Unlike `ToolWindowFactory.isApplicable()`, `LibrarySearchHelper.isLibraryExists()` is reevaluated
* when the project changes.
*/
class AndroidEnvironmentChecker : LibrarySearchHelper {
override fun isLibraryExists(project: Project): Boolean {
val value = isAndroidEnvironment(project)
thisLogger().debug { "isLibraryExists -> $value" }
return value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@
*/
package com.android.tools.idea.sdk

import com.android.tools.idea.hasAndroidFacet
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ext.LibrarySearchHelper

class AndroidSdkLibrarySearcher: LibrarySearchHelper {
/**
* A [LibrarySearchHelper] that checks for an Android Facet
*/
class AndroidFacetChecker : LibrarySearchHelper {
override fun isLibraryExists(project: Project): Boolean {
return AndroidSdkPathStore.getInstance().androidSdkPath != null
val value = project.hasAndroidFacet()
thisLogger().debug { "isLibraryExists -> $value" }
return value
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 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

import com.android.tools.idea.hasAndroidOrApkFacet
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ext.LibrarySearchHelper

/**
* A [LibrarySearchHelper] that checks for an Android or APK Facet
*/
class AndroidOrApkFacetChecker : LibrarySearchHelper {
override fun isLibraryExists(project: Project): Boolean {
val value = project.hasAndroidOrApkFacet()
thisLogger().debug { "isLibraryExists -> $value" }
return value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.android.tools.idea.sdk

import com.android.testutils.MockitoKt.mock
import com.android.testutils.MockitoKt.whenever
import com.android.tools.idea.ApkFacetChecker
import com.android.tools.idea.IdeInfo
import com.google.common.truth.Truth.assertThat
import com.intellij.openapi.application.ApplicationManager
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.RuleChain
import com.intellij.testFramework.registerOrReplaceServiceInstance
import com.intellij.testFramework.replaceService
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito

/**
* Tests for [AndroidEnvironmentChecker]
*/
class AndroidEnvironmentCheckerTest {
private val projectRule = ProjectRule()
private val project get() = projectRule.project
private val disposableRule = DisposableRule()
private val disposable get() = disposableRule.disposable

@get:Rule
val rule = RuleChain(projectRule, disposableRule)

private val mockIdeInfo by lazy { Mockito.spy(IdeInfo.getInstance()) }
private val mockApkFacetChecker = mock<ApkFacetChecker>()

@Test
fun isLibraryExists() {
assertThat(AndroidEnvironmentChecker().isLibraryExists(project)).isTrue()
}

@Test
fun isLibraryExists_notAndroidStudio_withoutAndroidFacet() {
whenever(mockIdeInfo.isAndroidStudio).thenReturn(false)
whenever(mockApkFacetChecker.hasApkFacet()).thenReturn(false)

ApplicationManager.getApplication().replaceService(IdeInfo::class.java, mockIdeInfo, disposable)
project.registerOrReplaceServiceInstance(ApkFacetChecker::class.java, mockApkFacetChecker, disposable)

assertThat(AndroidEnvironmentChecker().isLibraryExists(project)).isFalse()
}

@Test
fun isLibraryExists_notAndroidStudio_withAndroidFacet() {
whenever(mockIdeInfo.isAndroidStudio).thenReturn(false)
whenever(mockApkFacetChecker.hasApkFacet()).thenReturn(true)

ApplicationManager.getApplication().replaceService(IdeInfo::class.java, mockIdeInfo, disposable)
project.registerOrReplaceServiceInstance(ApkFacetChecker::class.java, mockApkFacetChecker, disposable)

assertThat(AndroidEnvironmentChecker().isLibraryExists(project)).isTrue()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.android.tools.idea.sdk

import com.android.testutils.MockitoKt.whenever
import com.google.common.truth.Truth.assertThat
import com.intellij.facet.ProjectFacetManager
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.RuleChain
import com.intellij.testFramework.registerOrReplaceServiceInstance
import org.jetbrains.android.facet.AndroidFacet
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito

/**
* Tests for [AndroidFacetChecker]
*/
class AndroidFacetCheckerTest {
private val projectRule = ProjectRule()
private val project get() = projectRule.project
private val disposableRule = DisposableRule()

private val mockProjectFacetManager by lazy { Mockito.spy(ProjectFacetManager.getInstance(project)) }

@get:Rule
val rule = RuleChain(projectRule, disposableRule)

@Before
fun setUp() {
project.registerOrReplaceServiceInstance(ProjectFacetManager::class.java, mockProjectFacetManager, disposableRule.disposable)
}

@Test
fun isLibraryExists() {
whenever(mockProjectFacetManager.hasFacets(AndroidFacet.ID)).thenReturn(true)

assertThat(AndroidFacetChecker().isLibraryExists(project)).isTrue()
}

@Test
fun isLibraryExists_withoutAndroidFacet() {
whenever(mockProjectFacetManager.hasFacets(AndroidFacet.ID)).thenReturn(false)

assertThat(AndroidFacetChecker().isLibraryExists(project)).isFalse()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.android.tools.idea.sdk

import com.android.testutils.MockitoKt
import com.android.testutils.MockitoKt.whenever
import com.android.tools.idea.ApkFacetChecker
import com.google.common.truth.Truth.assertThat
import com.intellij.facet.ProjectFacetManager
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.RuleChain
import com.intellij.testFramework.registerOrReplaceServiceInstance
import org.jetbrains.android.facet.AndroidFacet
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito

/**
* Tests for [AndroidOrApkFacetChecker]
*/
class AndroidOrApkFacetCheckerTest {
private val projectRule = ProjectRule()
private val project get() = projectRule.project
private val disposableRule = DisposableRule()

private val mockProjectFacetManager by lazy { Mockito.spy(ProjectFacetManager.getInstance(project)) }
private val mockApkFacetChecker = MockitoKt.mock<ApkFacetChecker>()

@get:Rule
val rule = RuleChain(projectRule, disposableRule)

@Before
fun setUp() {
project.registerOrReplaceServiceInstance(ProjectFacetManager::class.java, mockProjectFacetManager, disposableRule.disposable)
project.registerOrReplaceServiceInstance(ApkFacetChecker::class.java, mockApkFacetChecker, disposableRule.disposable)
}

@Test
fun isLibraryExists_hasAndroidFacet() {
whenever(mockProjectFacetManager.hasFacets(AndroidFacet.ID)).thenReturn(true)
whenever(mockApkFacetChecker.hasApkFacet()).thenReturn(false)

assertThat(AndroidOrApkFacetChecker().isLibraryExists(project)).isTrue()
}

@Test
fun isLibraryExists_hasApkFacet() {
whenever(mockProjectFacetManager.hasFacets(AndroidFacet.ID)).thenReturn(false)
whenever(mockApkFacetChecker.hasApkFacet()).thenReturn(true)

assertThat(AndroidOrApkFacetChecker().isLibraryExists(project)).isTrue()
}

@Test
fun isLibraryExists_withoutFacets() {
whenever(mockProjectFacetManager.hasFacets(AndroidFacet.ID)).thenReturn(false)
whenever(mockApkFacetChecker.hasApkFacet()).thenReturn(false)

assertThat(AndroidOrApkFacetChecker().isLibraryExists(project)).isFalse()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@

<extensions defaultExtensionNs="com.intellij">
<projectService serviceImplementation="com.android.tools.idea.ui.resourcemanager.importer.ImportConfigurationManager"/>
<facet.toolWindow id="Resources Explorer"
facetIdList="android"
<!--suppress PluginXmlValidity - Plugin XML files are merged into the same plugin.xml -->
<library.toolWindow id="Resources Explorer"
anchor="left" icon="StudioIcons.Shell.ToolWindows.VISUAL_ASSETS" doNotActivateOnStart="true"
factoryClass="com.android.tools.idea.ui.resourcemanager.ResourceExplorerToolFactory"/>
librarySearchClass="com.android.tools.idea.sdk.AndroidFacetChecker"
factoryClass="com.android.tools.idea.ui.resourcemanager.ResourceExplorerToolFactory"/>
</extensions>

<actions>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.android.tools.idea.ui.resourcemanager

import com.android.tools.idea.sdk.AndroidEnvironmentChecker
import com.android.tools.idea.sdk.AndroidFacetChecker
import com.google.common.truth.Truth.assertThat
import com.intellij.openapi.wm.ext.LibraryDependentToolWindow
import com.intellij.testFramework.ProjectRule
import org.junit.Rule
import org.junit.Test
import kotlin.test.fail

/**
* Tests for [ResourceExplorerToolFactory]
*/
class ResourceExplorerToolFactoryTest {
@get:Rule
val projectRule = ProjectRule()

@Test
fun isLibraryToolWindow() {
val toolWindow =
LibraryDependentToolWindow.EXTENSION_POINT_NAME.extensions.find { it.id == "Resources Explorer" }
?: fail("Tool window not found")

assertThat(toolWindow.librarySearchClass)
.isEqualTo(AndroidFacetChecker::class.qualifiedName)
}
}
3 changes: 2 additions & 1 deletion app-inspection/ide/src/META-INF/app-inspection.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
<applicationService
serviceInterface="com.android.tools.idea.appinspection.ide.InspectorArtifactService"
serviceImplementation="com.android.tools.idea.appinspection.ide.InspectorArtifactServiceImpl"/>
<!--suppress PluginXmlValidity - Plugin XML files are merged into the same plugin.xml -->
<library.toolWindow id="App Inspection"
librarySearchClass="com.android.tools.idea.sdk.AndroidSdkLibrarySearcher"
librarySearchClass="com.android.tools.idea.sdk.AndroidEnvironmentChecker"
anchor="bottom"
icon="StudioIcons.Shell.ToolWindows.INSPECTION"
factoryClass="com.android.tools.idea.appinspection.ide.ui.AppInspectionToolWindowFactory"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal const val APP_INSPECTION_ID = "App Inspection"
class AppInspectionToolWindowFactory : DumbAware, ToolWindowFactory {

override fun isApplicable(project: Project) =
StudioFlags.ENABLE_APP_INSPECTION_TOOL_WINDOW.get() && isAndroidEnvironment(project)
StudioFlags.ENABLE_APP_INSPECTION_TOOL_WINDOW.get()

override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
val appInspectionToolWindow = AppInspectionToolWindow(toolWindow, project)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.android.tools.idea.appinspection.ide.ui

import com.android.tools.idea.sdk.AndroidEnvironmentChecker
import com.google.common.truth.Truth.assertThat
import com.intellij.openapi.wm.ext.LibraryDependentToolWindow
import com.intellij.testFramework.ProjectRule
import org.junit.Rule
import org.junit.Test
import kotlin.test.fail

/** Tests for [AppInspectionToolWindowFactory] */
class AppInspectionToolWindowFactoryTest {
@get:Rule val projectRule = ProjectRule()

@Test
fun isLibraryToolWindow() {
val toolWindow =
LibraryDependentToolWindow.EXTENSION_POINT_NAME.extensions.find { it.id == "App Inspection" }
?: fail("Tool window not found")

assertThat(toolWindow.librarySearchClass)
.isEqualTo(AndroidEnvironmentChecker::class.qualifiedName)
}
}
3 changes: 2 additions & 1 deletion app-quality-insights/ide/src/META-INF/app-insights.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
<idea-plugin>
<depends>org.intellij.intelliLang</depends>
<extensions defaultExtensionNs="com.intellij">
<!--suppress PluginXmlValidity - Plugin XML files are merged into the same plugin.xml -->
<library.toolWindow id="App Quality Insights"
librarySearchClass="com.android.tools.idea.sdk.AndroidSdkLibrarySearcher"
librarySearchClass="com.android.tools.idea.sdk.AndroidEnvironmentChecker"
anchor="bottom"
icon="StudioIcons.Shell.ToolWindows.APP_QUALITY_INSIGHTS"
factoryClass="com.android.tools.idea.insights.ui.AppInsightsToolWindowFactory"
Expand Down
Loading