Snap for 10839468 from 850f40aa27db542f88521a70f1149a0a4eb1b3cc to studio-iguana-release
Change-Id: Ieb228e722c22fb4a96ecde49fad8527e9510f242
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 6e2f51b..12cef78 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -154,6 +154,7 @@
<module fileurl="file://$PROJECT_DIR$/apkanalyzer/intellij.android.apkanalyzer.iml" filepath="$PROJECT_DIR$/apkanalyzer/intellij.android.apkanalyzer.iml" />
<module fileurl="file://$PROJECT_DIR$/app-quality-insights/api/intellij.android.app-quality-insights.api.iml" filepath="$PROJECT_DIR$/app-quality-insights/api/intellij.android.app-quality-insights.api.iml" />
<module fileurl="file://$PROJECT_DIR$/app-quality-insights/ide/intellij.android.app-quality-insights.ide.iml" filepath="$PROJECT_DIR$/app-quality-insights/ide/intellij.android.app-quality-insights.ide.iml" />
+ <module fileurl="file://$PROJECT_DIR$/app-quality-insights/ide/gradle/intellij.android.app-quality-insights.ide.gradle.iml" filepath="$PROJECT_DIR$/app-quality-insights/ide/gradle/intellij.android.app-quality-insights.ide.gradle.iml" />
<module fileurl="file://$PROJECT_DIR$/app-quality-insights/ide/intellij.android.app-quality-insights.ide.tests.iml" filepath="$PROJECT_DIR$/app-quality-insights/ide/intellij.android.app-quality-insights.ide.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/app-quality-insights/play-vitals/ide/intellij.android.app-quality-insights.play-vitals.ide.iml" filepath="$PROJECT_DIR$/app-quality-insights/play-vitals/ide/intellij.android.app-quality-insights.play-vitals.ide.iml" />
<module fileurl="file://$PROJECT_DIR$/app-quality-insights/play-vitals/ide/intellij.android.app-quality-insights.play-vitals.ide.tests.iml" filepath="$PROJECT_DIR$/app-quality-insights/play-vitals/ide/intellij.android.app-quality-insights.play-vitals.ide.tests.iml" />
diff --git a/.idea/runConfigurations/ASfP.xml b/.idea/runConfigurations/ASfP.xml
index 8f4ecf1..5a887ab 100644
--- a/.idea/runConfigurations/ASfP.xml
+++ b/.idea/runConfigurations/ASfP.xml
@@ -8,7 +8,7 @@
<option name="MAIN_CLASS_NAME" value="com.intellij.idea.Main" />
<module name="asfp.studio" />
<shortenClasspath name="ARGS_FILE" />
- <option name="VM_PARAMETERS" value="@$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/default_user_jvm_args.txt @$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/required_jvm_args.txt -ea -Dapple.laf.useScreenMenuBar=true -Dsun.awt.disablegrab=true -Didea.jre.check=true -Didea.is.internal=true -Didea.debug.mode=true -Didea.paths.selector=AndroidStudioForPlatformDev -Didea.force.use.core.classloader=true -Dplugin.path=$PROJECT_DIR$/../../../prebuilts/tools/common/ide-perf-plugin/ide-perf -javaagent:$PROJECT_DIR$/../../../bazel-bin/tools/base/threading-agent/threading_agent.jar -Dpty4j.preferred.native.folder=$PROJECT_DIR$/../../../prebuilts/studio/intellij-sdk/AI/$SDK_PLATFORM$/lib/pty4j" />
+ <option name="VM_PARAMETERS" value="@$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/default_user_jvm_args.txt @$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/required_jvm_args.txt -ea -Dapple.laf.useScreenMenuBar=true -Dsun.awt.disablegrab=true -Didea.jre.check=true -Didea.is.internal=true -Didea.debug.mode=true -Dkotlinx.coroutines.debug=on -Didea.paths.selector=AndroidStudioForPlatformDev -Didea.force.use.core.classloader=true -Dplugin.path=$PROJECT_DIR$/../../../prebuilts/tools/common/ide-perf-plugin/ide-perf -javaagent:$PROJECT_DIR$/../../../bazel-bin/tools/base/threading-agent/threading_agent.jar -Dpty4j.preferred.native.folder=$PROJECT_DIR$/../../../prebuilts/studio/intellij-sdk/AI/$SDK_PLATFORM$/lib/pty4j" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/../../.." />
<method v="2">
<option name="Make" enabled="true" />
diff --git a/.idea/runConfigurations/ASwB.xml b/.idea/runConfigurations/ASwB.xml
index 310a5e5..25915ba 100644
--- a/.idea/runConfigurations/ASwB.xml
+++ b/.idea/runConfigurations/ASwB.xml
@@ -8,7 +8,7 @@
<option name="MAIN_CLASS_NAME" value="com.intellij.idea.Main" />
<module name="aswb.jcgdi.blaze.plugin.aswb" />
<shortenClasspath name="ARGS_FILE" />
- <option name="VM_PARAMETERS" value="@$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/default_user_jvm_args.txt @$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/required_jvm_args.txt -ea -Xms5G -Xmx8G -XX:+FlightRecorder -Didea.is.internal=true -Didea.force.use.core.classloader=true -Dapple.laf.useScreenMenuBar=true -Dsun.awt.disablegrab=true -Didea.jre.check=true -Didea.debug.mode=true -Didea.paths.selector=AndroidStudioWithBlazeDev -Dplugin.path=$PROJECT_DIR$/../../../prebuilts/tools/common/ide-perf-plugin/ide-perf -Ddisable.android.first.run=true -Dcaches.indexerThreadsCount=16 -Dstudio.projectview=true -Didea.classpath.index.enabled=false -Dandroid.sdk.custom.url=file:///google/src/head/depot/google3/third_party/android/sdk_repo/repository.xml -Dide.index.image.max.size=0 -Dide.old.project.model=true -Ddesign.tools.project.system.class.loader.cache.max.size=1000000000 -Dpty4j.preferred.native.folder=$PROJECT_DIR$/../../../prebuilts/studio/intellij-sdk/AI/$SDK_PLATFORM$/lib/pty4j" />
+ <option name="VM_PARAMETERS" value="@$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/default_user_jvm_args.txt @$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/required_jvm_args.txt -ea -Xms5G -Xmx8G -XX:+FlightRecorder -Didea.is.internal=true -Didea.force.use.core.classloader=true -Dapple.laf.useScreenMenuBar=true -Dsun.awt.disablegrab=true -Didea.jre.check=true -Didea.debug.mode=true -Dkotlinx.coroutines.debug=on -Didea.paths.selector=AndroidStudioWithBlazeDev -Dplugin.path=$PROJECT_DIR$/../../../prebuilts/tools/common/ide-perf-plugin/ide-perf -Ddisable.android.first.run=true -Dcaches.indexerThreadsCount=16 -Dstudio.projectview=true -Didea.classpath.index.enabled=false -Dandroid.sdk.custom.url=file:///google/src/head/depot/google3/third_party/android/sdk_repo/repository.xml -Dide.index.image.max.size=0 -Dide.old.project.model=true -Ddesign.tools.project.system.class.loader.cache.max.size=1000000000 -Dpty4j.preferred.native.folder=$PROJECT_DIR$/../../../prebuilts/studio/intellij-sdk/AI/$SDK_PLATFORM$/lib/pty4j" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/../../.." />
<method v="2">
<option name="Make" enabled="true" />
diff --git a/.idea/runConfigurations/Android_Studio.xml b/.idea/runConfigurations/Android_Studio.xml
index f08da96..9b56f8d 100644
--- a/.idea/runConfigurations/Android_Studio.xml
+++ b/.idea/runConfigurations/Android_Studio.xml
@@ -8,7 +8,7 @@
<option name="MAIN_CLASS_NAME" value="com.intellij.idea.Main" />
<module name="studio" />
<shortenClasspath name="ARGS_FILE" />
- <option name="VM_PARAMETERS" value="@$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/default_user_jvm_args.txt @$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/required_jvm_args.txt -ea -Dapple.laf.useScreenMenuBar=true -Dsun.awt.disablegrab=true -Didea.jre.check=true -Didea.is.internal=true -Didea.debug.mode=true -Didea.paths.selector=AndroidStudioDev -Daida.api.set.client=ANDROID_STUDIO_TESTERS -Didea.force.use.core.classloader=true -Dplugin.path=$PROJECT_DIR$/../../../prebuilts/tools/common/ide-perf-plugin/ide-perf -javaagent:$PROJECT_DIR$/../../../bazel-bin/tools/base/threading-agent/threading_agent.jar -Dpty4j.preferred.native.folder=$PROJECT_DIR$/../../../prebuilts/studio/intellij-sdk/AI/$SDK_PLATFORM$/lib/pty4j" />
+ <option name="VM_PARAMETERS" value="@$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/default_user_jvm_args.txt @$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/required_jvm_args.txt -ea -Dapple.laf.useScreenMenuBar=true -Dsun.awt.disablegrab=true -Didea.jre.check=true -Didea.is.internal=true -Didea.debug.mode=true -Dkotlinx.coroutines.debug=on -Didea.paths.selector=AndroidStudioDev -Daida.api.set.client=ANDROID_STUDIO_TESTERS -Didea.force.use.core.classloader=true -Dplugin.path=$PROJECT_DIR$/../../../prebuilts/tools/common/ide-perf-plugin/ide-perf -javaagent:$PROJECT_DIR$/../../../bazel-bin/tools/base/threading-agent/threading_agent.jar -Dpty4j.preferred.native.folder=$PROJECT_DIR$/../../../prebuilts/studio/intellij-sdk/AI/$SDK_PLATFORM$/lib/pty4j" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/../../.." />
<method v="2">
<option name="Make" enabled="true" />
diff --git a/.idea/runConfigurations/Game_Tools.xml b/.idea/runConfigurations/Game_Tools.xml
index 8675b9f..1ab8d2f 100644
--- a/.idea/runConfigurations/Game_Tools.xml
+++ b/.idea/runConfigurations/Game_Tools.xml
@@ -10,7 +10,7 @@
<module name="android.game-tools.main" />
<option name="PROGRAM_PARAMETERS" value="game-tools --mode APP --app-window PROFILER" />
<shortenClasspath name="ARGS_FILE" />
- <option name="VM_PARAMETERS" value="-ea -Xms512m -Xmx1024m -XX:ReservedCodeCacheSize=240m -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=50 -XX:MaxJavaStackTraceDepth=10000 -Didea.is.internal=true -Didea.force.use.core.classloader=true -Didea.platform.prefix=AndroidGameDevelopmentTools -Dsun.awt.disablegrab=true -Dawt.useSystemAAFontSettings=lcd -Dsun.io.useCanonCaches=false -Djava.net.preferIPv4Stack=true -Didea.jre.check=true -Didea.debug.mode=true -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine -Didea.load.plugins=false -Didea.initially.ask.config=force-not -Djava.system.class.loader=com.intellij.util.lang.PathClassLoader -Didea.vendor.name=Google -Didea.paths.selector=GameToolsDev -Didea.ui.icons.svg.disk.cache=false -Djna.boot.library.path=$PROJECT_DIR$/../../../prebuilts/studio/intellij-sdk/AI/$SDK_PLATFORM$/lib/jna/amd64/ --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.ref=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.base/jdk.internal.vm=ALL-UNNAMED --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/sun.nio.fs=ALL-UNNAMED --add-opens=java.base/sun.security.ssl=ALL-UNNAMED --add-opens=java.base/sun.security.util=ALL-UNNAMED --add-opens=java.desktop/com.apple.eawt=ALL-UNNAMED --add-opens=java.desktop/com.apple.eawt.event=ALL-UNNAMED --add-opens=java.desktop/com.apple.laf=ALL-UNNAMED --add-opens=java.desktop/java.awt=ALL-UNNAMED --add-opens=java.desktop/java.awt.dnd.peer=ALL-UNNAMED --add-opens=java.desktop/java.awt.event=ALL-UNNAMED --add-opens=java.desktop/java.awt.image=ALL-UNNAMED --add-opens=java.desktop/java.awt.peer=ALL-UNNAMED --add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED --add-opens=java.desktop/javax.swing=ALL-UNNAMED --add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED --add-opens=java.desktop/javax.swing.text.html=ALL-UNNAMED --add-opens=java.desktop/sun.awt.datatransfer=ALL-UNNAMED --add-opens=java.desktop/sun.awt.image=ALL-UNNAMED --add-opens=java.desktop/sun.awt=ALL-UNNAMED --add-opens=java.desktop/sun.font=ALL-UNNAMED --add-opens=java.desktop/sun.java2d=ALL-UNNAMED --add-opens=java.desktop/sun.lwawt=ALL-UNNAMED --add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED --add-opens=java.desktop/sun.swing=ALL-UNNAMED --add-opens=jdk.attach/sun.tools.attach=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-opens=jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED --add-opens=jdk.jdi/com.sun.tools.jdi=ALL-UNNAMED --add-opens=java.desktop/sun.awt.windows=ALL-UNNAMED" />
+ <option name="VM_PARAMETERS" value="-ea -Xms512m -Xmx1024m -XX:ReservedCodeCacheSize=240m -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=50 -XX:MaxJavaStackTraceDepth=10000 -Didea.is.internal=true -Didea.force.use.core.classloader=true -Didea.platform.prefix=AndroidGameDevelopmentTools -Dsun.awt.disablegrab=true -Dawt.useSystemAAFontSettings=lcd -Dsun.io.useCanonCaches=false -Djava.net.preferIPv4Stack=true -Didea.jre.check=true -Didea.debug.mode=true -Dkotlinx.coroutines.debug=on -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine -Didea.load.plugins=false -Didea.initially.ask.config=force-not -Djava.system.class.loader=com.intellij.util.lang.PathClassLoader -Didea.vendor.name=Google -Didea.paths.selector=GameToolsDev -Didea.ui.icons.svg.disk.cache=false -Djna.boot.library.path=$PROJECT_DIR$/../../../prebuilts/studio/intellij-sdk/AI/$SDK_PLATFORM$/lib/jna/amd64/ --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.ref=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.base/jdk.internal.vm=ALL-UNNAMED --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/sun.nio.fs=ALL-UNNAMED --add-opens=java.base/sun.security.ssl=ALL-UNNAMED --add-opens=java.base/sun.security.util=ALL-UNNAMED --add-opens=java.desktop/com.apple.eawt=ALL-UNNAMED --add-opens=java.desktop/com.apple.eawt.event=ALL-UNNAMED --add-opens=java.desktop/com.apple.laf=ALL-UNNAMED --add-opens=java.desktop/java.awt=ALL-UNNAMED --add-opens=java.desktop/java.awt.dnd.peer=ALL-UNNAMED --add-opens=java.desktop/java.awt.event=ALL-UNNAMED --add-opens=java.desktop/java.awt.image=ALL-UNNAMED --add-opens=java.desktop/java.awt.peer=ALL-UNNAMED --add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED --add-opens=java.desktop/javax.swing=ALL-UNNAMED --add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED --add-opens=java.desktop/javax.swing.text.html=ALL-UNNAMED --add-opens=java.desktop/sun.awt.datatransfer=ALL-UNNAMED --add-opens=java.desktop/sun.awt.image=ALL-UNNAMED --add-opens=java.desktop/sun.awt=ALL-UNNAMED --add-opens=java.desktop/sun.font=ALL-UNNAMED --add-opens=java.desktop/sun.java2d=ALL-UNNAMED --add-opens=java.desktop/sun.lwawt=ALL-UNNAMED --add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED --add-opens=java.desktop/sun.swing=ALL-UNNAMED --add-opens=jdk.attach/sun.tools.attach=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-opens=jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED --add-opens=jdk.jdi/com.sun.tools.jdi=ALL-UNNAMED --add-opens=java.desktop/sun.awt.windows=ALL-UNNAMED" />
<method v="2">
<option name="Make" enabled="true" />
<option name="BuildArtifacts" enabled="true" />
diff --git a/.idea/runConfigurations/K2.xml b/.idea/runConfigurations/K2.xml
index 2fe45dc..119bb56 100644
--- a/.idea/runConfigurations/K2.xml
+++ b/.idea/runConfigurations/K2.xml
@@ -8,7 +8,7 @@
<option name="MAIN_CLASS_NAME" value="com.intellij.idea.Main" />
<module name="studio" />
<shortenClasspath name="ARGS_FILE" />
- <option name="VM_PARAMETERS" value="@$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/default_user_jvm_args.txt @$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/required_jvm_args.txt -ea -Dapple.laf.useScreenMenuBar=true -Dsun.awt.disablegrab=true -Didea.jre.check=true -Didea.is.internal=true -Didea.debug.mode=true -Didea.paths.selector=AndroidStudioDev -Didea.force.use.core.classloader=true -Didea.kotlin.plugin.use.k2=true -Dplugin.path=$PROJECT_DIR$/../../../prebuilts/tools/common/ide-perf-plugin/ide-perf -javaagent:$PROJECT_DIR$/../../../bazel-bin/tools/base/threading-agent/threading_agent.jar -Dpty4j.preferred.native.folder=$PROJECT_DIR$/../../../prebuilts/studio/intellij-sdk/AI/$SDK_PLATFORM$/lib/pty4j" />
+ <option name="VM_PARAMETERS" value="@$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/default_user_jvm_args.txt @$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/required_jvm_args.txt -ea -Dapple.laf.useScreenMenuBar=true -Dsun.awt.disablegrab=true -Didea.jre.check=true -Didea.is.internal=true -Didea.debug.mode=true -Dkotlinx.coroutines.debug=on -Didea.paths.selector=AndroidStudioDev -Didea.force.use.core.classloader=true -Didea.kotlin.plugin.use.k2=true -Dplugin.path=$PROJECT_DIR$/../../../prebuilts/tools/common/ide-perf-plugin/ide-perf -javaagent:$PROJECT_DIR$/../../../bazel-bin/tools/base/threading-agent/threading_agent.jar -Dpty4j.preferred.native.folder=$PROJECT_DIR$/../../../prebuilts/studio/intellij-sdk/AI/$SDK_PLATFORM$/lib/pty4j" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/../../.." />
<method v="2">
<option name="Make" enabled="true" />
diff --git a/.idea/runConfigurations/_template__of_JUnit.xml b/.idea/runConfigurations/_template__of_JUnit.xml
index 562b392..c21891a 100644
--- a/.idea/runConfigurations/_template__of_JUnit.xml
+++ b/.idea/runConfigurations/_template__of_JUnit.xml
@@ -9,7 +9,7 @@
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
- <option name="VM_PARAMETERS" value="@$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/default_user_jvm_args.txt @$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/required_jvm_args.txt -ea -Duser.home=$PROJECT_DIR$/test-temp/user/home -Didea.config.path=$PROJECT_DIR$/test-temp/config -Didea.force.use.core.classloader=true -Didea.system.path=$PROJECT_DIR$/test-temp/system -Didea.plugins.path=$PROJECT_DIR$/test-temp/plugins -Didea.log.config.file=$PROJECT_DIR$/adt-testutils/test-log.xml -Dkotlin.script.classpath=" />
+ <option name="VM_PARAMETERS" value="@$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/default_user_jvm_args.txt @$PROJECT_DIR$/../../../bazel-bin/tools/adt/idea/studio/required_jvm_args.txt -ea -Dkotlinx.coroutines.debug=on -Duser.home=$PROJECT_DIR$/test-temp/user/home -Didea.config.path=$PROJECT_DIR$/test-temp/config -Didea.force.use.core.classloader=true -Didea.system.path=$PROJECT_DIR$/test-temp/system -Didea.plugins.path=$PROJECT_DIR$/test-temp/plugins -Didea.log.config.file=$PROJECT_DIR$/adt-testutils/test-log.xml -Dkotlin.script.classpath=" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/../../.." />
<option name="PASS_PARENT_ENVS" value="false" />
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index fd1f24c..b7d4f9b 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -2,7 +2,7 @@
[Builtin Hooks]
ktfmt = true
[Builtin Hooks Options]
-ktfmt = --include-dirs=app-inspection,app-quality-insights,assistant,compose-designer,dagger,designer,device-manager-v2,glance-designer,preview-designer,rendering,android-lint/src,android-lint/testSrc,lint/src,lint/tests/testSrc,layout-inspector,layout-ui,intellij.android.avdmanager.tests --google-style
+ktfmt = --include-dirs=app-inspection,app-quality-insights,assistant,compose-designer,dagger,designer,device-manager-v2,glance-designer,preview-designer,rendering,android-lint/src,android-lint/testSrc,lint/src,lint/tests/testSrc,layout-inspector,layout-ui,intellij.android.avdmanager.tests,compose-ide-plugin,nav/safeargs --google-style
[Tool Paths]
ktfmt = ${REPO_ROOT}/prebuilts/tools/common/ktfmt/ktfmt
diff --git a/adt-ui/src/main/java/com/android/tools/adtui/actions/EnableSwingProfilerAction.java b/adt-ui/src/main/java/com/android/tools/adtui/actions/EnableSwingProfilerAction.java
index e23122c..5d64ce2 100644
--- a/adt-ui/src/main/java/com/android/tools/adtui/actions/EnableSwingProfilerAction.java
+++ b/adt-ui/src/main/java/com/android/tools/adtui/actions/EnableSwingProfilerAction.java
@@ -16,6 +16,7 @@
package com.android.tools.adtui.actions;
import com.intellij.ide.BrowserUtil;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
@@ -45,6 +46,12 @@
}
@Override
+ @NotNull
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
+ @Override
public boolean isSelected(@NotNull AnActionEvent e) {
return SERVICE_KEY.isIn(ApplicationManager.getApplication());
}
diff --git a/android-adb/src/com/android/tools/idea/adb/StudioAdbLibSCacheJdwpSessionPipeline.kt b/android-adb/src/com/android/tools/idea/adb/StudioAdbLibSCacheJdwpSessionPipeline.kt
index a8c6617..a0f8c90 100644
--- a/android-adb/src/com/android/tools/idea/adb/StudioAdbLibSCacheJdwpSessionPipeline.kt
+++ b/android-adb/src/com/android/tools/idea/adb/StudioAdbLibSCacheJdwpSessionPipeline.kt
@@ -16,16 +16,16 @@
package com.android.tools.idea.adb
import com.android.adblib.AdbSession
-import com.android.adblib.thisLogger
+import com.android.adblib.adbLogger
import com.android.adblib.tools.debugging.JdwpSessionPipeline
import com.android.adblib.tools.debugging.SharedJdwpSessionMonitor
-import com.android.adblib.tools.debugging.utils.SynchronizedChannel
-import com.android.adblib.tools.debugging.utils.SynchronizedReceiveChannel
-import com.android.adblib.tools.debugging.utils.SynchronizedSendChannel
import com.android.adblib.tools.debugging.packets.JdwpPacketView
import com.android.adblib.tools.debugging.packets.writeToBuffer
import com.android.adblib.tools.debugging.receiveAllPacketsCatching
import com.android.adblib.tools.debugging.sendPacket
+import com.android.adblib.tools.debugging.utils.SynchronizedChannel
+import com.android.adblib.tools.debugging.utils.SynchronizedReceiveChannel
+import com.android.adblib.tools.debugging.utils.SynchronizedSendChannel
import com.android.adblib.tools.debugging.utils.receiveAllCatching
import com.android.adblib.utils.ResizableBuffer
import com.android.jdwpscache.SCacheResponse
@@ -41,7 +41,7 @@
private val sessionMonitor: SharedJdwpSessionMonitor?,
private val debuggerPipeline: JdwpSessionPipeline,
) : JdwpSessionPipeline {
- private val logger = thisLogger(session)
+ private val logger = adbLogger(session)
/**
* The actual SCache implementation.
diff --git a/android-adb/src/com/android/tools/idea/adb/wireless/PairDevicesUsingWiFiAction.kt b/android-adb/src/com/android/tools/idea/adb/wireless/PairDevicesUsingWiFiAction.kt
index bc140c5..56f7465 100644
--- a/android-adb/src/com/android/tools/idea/adb/wireless/PairDevicesUsingWiFiAction.kt
+++ b/android-adb/src/com/android/tools/idea/adb/wireless/PairDevicesUsingWiFiAction.kt
@@ -16,20 +16,19 @@
package com.android.tools.idea.adb.wireless
import com.android.annotations.concurrency.UiThread
+import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import icons.StudioIcons
/** The action to show the [WiFiPairingDialog] window. */
class PairDevicesUsingWiFiAction : AnAction(StudioIcons.Avd.PAIR_OVER_WIFI) {
- @UiThread
override fun update(e: AnActionEvent) {
- super.update(e)
- e.presentation.isEnabledAndVisible = false
- val project = e.project ?: return
- e.presentation.isEnabledAndVisible = true
+ e.presentation.isEnabledAndVisible = e.project != null
}
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+
@UiThread
override fun actionPerformed(event: AnActionEvent) {
val project = event.project ?: return
diff --git a/android-adb/src/com/android/tools/idea/ddms/actions/GetAdbAction.java b/android-adb/src/com/android/tools/idea/ddms/actions/GetAdbAction.java
index 837a788..e1ec6a2 100644
--- a/android-adb/src/com/android/tools/idea/ddms/actions/GetAdbAction.java
+++ b/android-adb/src/com/android/tools/idea/ddms/actions/GetAdbAction.java
@@ -25,6 +25,7 @@
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
@@ -45,6 +46,12 @@
e.getPresentation().setEnabled(adb != null && adb.exists());
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Notifications.Bus.notify(new Notification("Android", "ADB", "ADB requested.", NotificationType.INFORMATION));
diff --git a/android-common/src/com/android/tools/idea/flags/StudioFlags.java b/android-common/src/com/android/tools/idea/flags/StudioFlags.java
index f1726b3..fa97cd3 100644
--- a/android-common/src/com/android/tools/idea/flags/StudioFlags.java
+++ b/android-common/src/com/android/tools/idea/flags/StudioFlags.java
@@ -251,17 +251,17 @@
public static final Flag<Boolean> NELE_ATF_FOR_COMPOSE = Flag.create(
NELE, "atf.for.compose", "Enable ATF checks for Compose",
"Allow running accessibility checks for Compose using ATF.",
- false);
+ true);
public static final Flag<Boolean> NELE_COMPOSE_UI_CHECK_MODE = Flag.create(
NELE, "compose.ui.check.mode", "Enable UI Check mode for Compose preview",
"Enable UI Check mode in Compose preview for running ATF checks and Visual Linting",
- false);
+ true);
public static final Flag<Boolean> NELE_COMPOSE_VISUAL_LINT_RUN = Flag.create(
NELE, "compose.visual.lint.run", "Enable visual lint for Compose Preview",
"Enable so that visual lint runs on previews in the Compose Preview.",
- false);
+ true);
public static final Flag<Boolean> NELE_CLASS_PRELOADING_DIAGNOSTICS = Flag.create(
NELE, "preview.class.preloading.diagnostics", "Enable class preloading overlay",
@@ -1436,6 +1436,14 @@
"Enable FTL DirectAccess",
false);
+ public static final Flag<Boolean> USE_UXR_202309_FILTER =
+ Flag.create(
+ FIREBASE_TEST_LAB,
+ "direct.access.uxr202309",
+ "UXR 202309 Device Filter",
+ "Use custom device filter (for 2023-09 UXR study)",
+ false);
+
public static final Flag<Boolean> DIRECT_ACCESS_ADD_DEVICE =
Flag.create(
FIREBASE_TEST_LAB,
@@ -1533,11 +1541,15 @@
"Revamped App Links Assistant (new surfaces and navigation between surfaces).", true);
public static final Flag<Boolean> WEBSITE_ASSOCIATION_GENERATOR_V2 =
Flag.create(APP_LINKS_ASSISTANT, "website.association.generator.v2", "Website Association Generator V2",
- "Improvements to Website Association Generator.", false);
+ "Improvements to Website Association Generator.", true);
public static final Flag<String> DEEPLINKS_GRPC_SERVER =
Flag.create(APP_LINKS_ASSISTANT, "deeplinks.grpc.server", "Deep links gRPC server address",
"Deep links gRPC server address. Use a non-default value for testing purposes.",
"deeplinkassistant-pa.googleapis.com");
+
+ public static final Flag<Boolean> CREATE_APP_LINKS_V2 =
+ Flag.create(APP_LINKS_ASSISTANT, "create.app.links.v2", "Create App Links V2",
+ "Improvements to the Create App Links functionalities.", false);
// endregion App Links Assistant
// region GOOGLE_PLAY_SDK_INDEX
diff --git a/device-manager/testSrc/com/android/tools/idea/devicemanager/CountDownLatchAssert.java b/android-common/test/com/android/tools/idea/concurrency/CountDownLatchAssert.java
similarity index 95%
rename from device-manager/testSrc/com/android/tools/idea/devicemanager/CountDownLatchAssert.java
rename to android-common/test/com/android/tools/idea/concurrency/CountDownLatchAssert.java
index 368b049..a7601fe 100644
--- a/device-manager/testSrc/com/android/tools/idea/devicemanager/CountDownLatchAssert.java
+++ b/android-common/test/com/android/tools/idea/concurrency/CountDownLatchAssert.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tools.idea.devicemanager;
+package com.android.tools.idea.concurrency;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
diff --git a/device-manager/testSrc/com/android/tools/idea/devicemanager/CountDownLatchFutureCallback.java b/android-common/test/com/android/tools/idea/concurrency/CountDownLatchFutureCallback.java
similarity index 96%
rename from device-manager/testSrc/com/android/tools/idea/devicemanager/CountDownLatchFutureCallback.java
rename to android-common/test/com/android/tools/idea/concurrency/CountDownLatchFutureCallback.java
index 9f7d2c7..3307b12 100644
--- a/device-manager/testSrc/com/android/tools/idea/devicemanager/CountDownLatchFutureCallback.java
+++ b/android-common/test/com/android/tools/idea/concurrency/CountDownLatchFutureCallback.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tools.idea.devicemanager;
+package com.android.tools.idea.concurrency;
import com.google.common.util.concurrent.FutureCallback;
import java.util.concurrent.CountDownLatch;
diff --git a/android-lang/src/com/android/tools/idea/lang/proguardR8/ProguardR8CompletionContributor.kt b/android-lang/src/com/android/tools/idea/lang/proguardR8/ProguardR8CompletionContributor.kt
index 9a9555e..44008ff 100644
--- a/android-lang/src/com/android/tools/idea/lang/proguardR8/ProguardR8CompletionContributor.kt
+++ b/android-lang/src/com/android/tools/idea/lang/proguardR8/ProguardR8CompletionContributor.kt
@@ -45,7 +45,7 @@
import com.intellij.util.ProcessingContext
/**
- * Provides code completion for key words for proguardR8 files
+ * Provides code completion for keywords for proguardR8 files
*
* Provides code completion for flags, java keywords and proguardR8 specific wildcards
*/
@@ -234,7 +234,7 @@
flagCompletionProvider
)
- // Add completion for java key words ("private", "public" ...) inside class specification body
+ // Add completion for java keywords ("private", "public" ...) inside class specification body
extend(
CompletionType.BASIC,
or(startOfNewJavaRule, afterFieldOrMethodModifier, insideClassSpecification.afterLeaf("!")),
@@ -312,7 +312,7 @@
override fun beforeCompletion(context: CompletionInitializationContext) {
if (context.file is ProguardR8PsiFile) {
- // We need lower case identifier because original ("IntellijIdeaRulezzz") breaks lexer for flags (flags can be only lowercase).
+ // We need lower case identifier because original ("IntellijIdeaRule") breaks lexer for flags (flags can be only lowercase).
context.dummyIdentifier = lowerCaseIdentifier
}
super.beforeCompletion(context)
diff --git a/android-lang/src/com/android/tools/idea/lang/proguardR8/psi/ProguardR8PsiImplUtil.kt b/android-lang/src/com/android/tools/idea/lang/proguardR8/psi/ProguardR8PsiImplUtil.kt
index 6565127..ecd1b4b 100644
--- a/android-lang/src/com/android/tools/idea/lang/proguardR8/psi/ProguardR8PsiImplUtil.kt
+++ b/android-lang/src/com/android/tools/idea/lang/proguardR8/psi/ProguardR8PsiImplUtil.kt
@@ -29,6 +29,7 @@
import com.intellij.psi.PsiPrimitiveType
import com.intellij.psi.PsiReference
import com.intellij.psi.PsiType
+import com.intellij.psi.PsiTypes
import com.intellij.psi.impl.source.PsiClassReferenceType
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet
@@ -54,7 +55,7 @@
object : JavaClassReferenceSet(str, position, offsetInPosition, false, this) {
// If true allows inner classes to be separated by a dollar sign "$", e.g.java.lang.Thread$State
// We can't just use ALLOW_DOLLAR_NAMES flag because to make JavaClassReferenceSet work in the way we want
- // language of PsiElement that we parse should be instance of XMLLanguage.
+ // language of PsiElement that we parse should be an instance of XMLLanguage.
override fun isAllowDollarInNames() = treatDollarAsSeparator
}.allReferences as Array<PsiReference>
@@ -89,7 +90,7 @@
}
/**
- * Returns all resolvable psiClasses found in header, excluding classes that specified after "extends"/"implements" key words.
+ * Returns all resolvable psiClasses found in header, excluding classes that specified after "extends"/"implements" keywords.
*
* Example: for "-keep myClass1, myClass2 extends myClass3" returns "myClass1", "myClass2"
*/
@@ -98,7 +99,7 @@
}
/**
- * Returns classes in header that specified after "extends"/"implements" key words.
+ * Returns classes in header that specified after "extends"/"implements" keywords.
*/
fun resolveSuperPsiClasses(classSpecificationHeader: ProguardR8ClassSpecificationHeader): List<PsiClass> {
return classSpecificationHeader.superClassNameList.mapNotNull { it.qualifiedName.resolveToPsiClass() }
@@ -107,15 +108,15 @@
fun getPsiPrimitive(proguardR8JavaPrimitive: ProguardR8JavaPrimitive): PsiPrimitiveType? {
val primitive = proguardR8JavaPrimitive.node.firstChildNode
return when (primitive.elementType) {
- ProguardR8PsiTypes.BOOLEAN -> PsiPrimitiveType.BOOLEAN
- ProguardR8PsiTypes.BYTE -> PsiPrimitiveType.BYTE
- ProguardR8PsiTypes.CHAR -> PsiPrimitiveType.CHAR
- ProguardR8PsiTypes.SHORT -> PsiPrimitiveType.SHORT
- ProguardR8PsiTypes.INT -> PsiPrimitiveType.INT
- ProguardR8PsiTypes.LONG -> PsiPrimitiveType.LONG
- ProguardR8PsiTypes.FLOAT -> PsiPrimitiveType.FLOAT
- ProguardR8PsiTypes.DOUBLE -> PsiPrimitiveType.DOUBLE
- ProguardR8PsiTypes.VOID -> PsiPrimitiveType.VOID
+ ProguardR8PsiTypes.BOOLEAN -> PsiTypes.booleanType()
+ ProguardR8PsiTypes.BYTE -> PsiTypes.byteType()
+ ProguardR8PsiTypes.CHAR -> PsiTypes.charType()
+ ProguardR8PsiTypes.SHORT -> PsiTypes.shortType()
+ ProguardR8PsiTypes.INT -> PsiTypes.intType()
+ ProguardR8PsiTypes.LONG -> PsiTypes.longType()
+ ProguardR8PsiTypes.FLOAT -> PsiTypes.floatType()
+ ProguardR8PsiTypes.DOUBLE -> PsiTypes.doubleType()
+ ProguardR8PsiTypes.VOID -> PsiTypes.voidType()
else -> {
assert(false) { "Couldn't match ProguardR8JavaPrimitive \"${primitive.text}\" to PsiPrimitive" }
null
@@ -126,7 +127,7 @@
/**
* Returns number of dimensions or 0 if there is error
*
- * Examnple: For int[][][] it returns 3
+ * Example: For int[][][] it returns 3
*/
fun getNumberOfDimensions(array: ProguardR8ArrayType): Int {
if (PsiTreeUtil.hasErrorElements(array)) return 0
@@ -152,8 +153,8 @@
return when {
type.javaPrimitive != null -> type.javaPrimitive!!.psiPrimitive == typeToMatch
type.qualifiedName != null && typeToMatch is PsiClassReferenceType -> type.qualifiedName!!.resolveToPsiClass() == typeToMatch.resolve()
- // "%" matches any primitive type ("boolean", "int", etc, but not "void").
- type.anyPrimitiveType != null -> typeToMatch is PsiPrimitiveType && typeToMatch != PsiPrimitiveType.VOID
+ // "%" matches any primitive type ("boolean", "int", etc., but not "void").
+ type.anyPrimitiveType != null -> typeToMatch is PsiPrimitiveType && typeToMatch != PsiTypes.voidType()
type.anyNotPrimitiveType != null -> typeToMatch is PsiClassReferenceType
type.anyType != null -> true
else -> false
@@ -163,7 +164,7 @@
/**
* Returns true if ProguardR8Parameters doesn't have errors and matches given "other" PsiParameterList otherwise returns false
*
- * In general it checks if every type within ProguardR8Parameters [matchesPsiType] type in PsiParameterList at the same position.
+ * In general, it checks if every type within ProguardR8Parameters [matchesPsiType] type in PsiParameterList at the same position.
* Tricky case is when ProguardR8Parameters ends with '...' (matches any number of arguments of any type). In this case we need to check
* that all types at positions before '...' match and after if there are still some types remain at PsiParameterList, we just ignore them.
*/
diff --git a/android-lang/testSrc/com/android/tools/idea/lang/multiDexKeep/MultiDexKeepReferenceTest.kt b/android-lang/testSrc/com/android/tools/idea/lang/multiDexKeep/MultiDexKeepReferenceTest.kt
index 9c491fa..9a57371 100644
--- a/android-lang/testSrc/com/android/tools/idea/lang/multiDexKeep/MultiDexKeepReferenceTest.kt
+++ b/android-lang/testSrc/com/android/tools/idea/lang/multiDexKeep/MultiDexKeepReferenceTest.kt
@@ -16,6 +16,7 @@
package com.android.tools.idea.lang.multiDexKeep
import com.android.tools.idea.testing.caret
+import com.android.tools.tests.AdtTestProjectDescriptors
import com.google.common.truth.Truth.assertThat
import com.intellij.psi.PsiClass
import com.intellij.testFramework.PsiTestUtil
@@ -23,6 +24,12 @@
class MultiDexKeepReferenceTest : AndroidTestCase() {
override fun setUp() {
+ // These tests check if classes listed in multidex keep file are indeed kept.
+ // Inputs are .class files, and thus technically neither java nor kotlin.
+ // But using kotlin descriptor includes Kotlin stdlib, hence java instead.
+ // TODO(b/300170256): Remove this once 2023.3 merges
+ myProjectDescriptor = AdtTestProjectDescriptors.java()
+
super.setUp()
PsiTestUtil.addLibrary(myModule, "mylib", "", myFixture.testDataPath + "/maven/myjar/myjar-1.0.jar")
diff --git a/android-lang/testSrc/com/android/tools/idea/lang/proguardR8/ProguardR8CompletionContributorTest.kt b/android-lang/testSrc/com/android/tools/idea/lang/proguardR8/ProguardR8CompletionContributorTest.kt
index ba16ace..c6be40f 100644
--- a/android-lang/testSrc/com/android/tools/idea/lang/proguardR8/ProguardR8CompletionContributorTest.kt
+++ b/android-lang/testSrc/com/android/tools/idea/lang/proguardR8/ProguardR8CompletionContributorTest.kt
@@ -19,6 +19,8 @@
import com.android.tools.idea.projectsystem.CodeShrinker
import com.android.tools.idea.projectsystem.getModuleSystem
import com.android.tools.idea.testing.caret
+import com.android.tools.tests.AdtTestProjectDescriptor
+import com.android.tools.tests.AdtTestProjectDescriptors
import com.google.common.truth.Truth.assertThat
import com.intellij.codeInsight.completion.JavaPsiClassReferenceElement
import com.intellij.codeInsight.lookup.Lookup
@@ -26,6 +28,13 @@
class ProguardR8CompletionContributorTest : ProguardR8TestCase() {
+ override fun getProjectDescriptor(): AdtTestProjectDescriptor {
+ // Proguard / R8 config file is neither jar nor kotlin.
+ // Having Kotlin stdlib makes [ProguardR8CompletionContributor] suggest all classes from stdlib whenever possible.
+ // TODO(b/300170256): Remove this once 2023.3 merges
+ return AdtTestProjectDescriptors.java()
+ }
+
fun testFlagCompletion() {
myFixture.configureByText(ProguardR8FileType.INSTANCE, """
@@ -220,7 +229,7 @@
keys = myFixture.completeBasic()
- // don't suggests after type
+ // don't suggest after type
assertThat(keys.map { it.lookupString }.toList()).containsNoneOf("public", "private", "protected",
"static", "synchronized", "native", "abstract", "strictfp",
"volatile", "transient", "final")
@@ -573,7 +582,7 @@
class ProguardR8FlagsCodeCompletion : AndroidTestCase() {
- fun testFlagSuggestionRegardinShrinkerType() {
+ fun testFlagSuggestionRegardingShrinkerType() {
(myModule.getModuleSystem() as DefaultModuleSystem).codeShrinker = CodeShrinker.R8
val justProguardFlag = PROGUARD_FLAGS.minus(R8_FLAGS).first()
diff --git a/android-npw/src/com/android/tools/idea/npw/actions/AndroidNewProjectAction.kt b/android-npw/src/com/android/tools/idea/npw/actions/AndroidNewProjectAction.kt
index 10a31b8..a5988f7 100644
--- a/android-npw/src/com/android/tools/idea/npw/actions/AndroidNewProjectAction.kt
+++ b/android-npw/src/com/android/tools/idea/npw/actions/AndroidNewProjectAction.kt
@@ -24,6 +24,7 @@
import com.android.tools.idea.wizard.ui.StudioWizardDialogBuilder
import com.intellij.icons.AllIcons
import com.intellij.idea.ActionsBundle.actionText
+import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAware
@@ -32,6 +33,11 @@
@Suppress("ComponentNotRegistered")
class AndroidNewProjectAction @JvmOverloads constructor(text: String = actionText("NewDirectoryProject")) : AnAction(text), DumbAware {
+
+ override fun getActionUpdateThread(): ActionUpdateThread {
+ return ActionUpdateThread.BGT
+ }
+
override fun update(e: AnActionEvent) {
if (NewWelcomeScreen.isNewWelcomeScreen(e)) {
e.presentation.icon = AllIcons.Welcome.CreateNewProjectTab
diff --git a/android-npw/src/com/android/tools/idea/npw/module/recipes/androidProject/androidProjectGradleSettings.kt b/android-npw/src/com/android/tools/idea/npw/module/recipes/androidProject/androidProjectGradleSettings.kt
index 5af1e68..a36e08a 100644
--- a/android-npw/src/com/android/tools/idea/npw/module/recipes/androidProject/androidProjectGradleSettings.kt
+++ b/android-npw/src/com/android/tools/idea/npw/module/recipes/androidProject/androidProjectGradleSettings.kt
@@ -31,7 +31,13 @@
"""
pluginManagement {
repositories {$injectedRepositories
- google()
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
mavenCentral()
gradlePluginPortal()
}
diff --git a/android-npw/src/com/android/tools/idea/npw/platform/AndroidVersionsInfo.kt b/android-npw/src/com/android/tools/idea/npw/platform/AndroidVersionsInfo.kt
index bbfa975..659552b 100644
--- a/android-npw/src/com/android/tools/idea/npw/platform/AndroidVersionsInfo.kt
+++ b/android-npw/src/com/android/tools/idea/npw/platform/AndroidVersionsInfo.kt
@@ -35,7 +35,7 @@
import com.android.sdklib.repository.meta.DetailsTypes.SysImgDetailsType
import com.android.sdklib.repository.targets.SystemImage
import com.android.tools.adtui.device.FormFactor
-import com.android.tools.idea.gradle.npw.project.GradleBuildSettings.getRecommendedBuildToolsRevision
+import com.android.tools.idea.npw.platform.GradleBuildSettings.getRecommendedBuildToolsRevision
import com.android.tools.idea.npw.invokeLater
import com.android.tools.idea.sdk.AndroidSdks
import com.android.tools.idea.sdk.IdeSdks
diff --git a/android/src/com/android/tools/idea/gradle/npw/project/GradleBuildSettings.java b/android-npw/src/com/android/tools/idea/npw/platform/GradleBuildSettings.java
similarity index 90%
rename from android/src/com/android/tools/idea/gradle/npw/project/GradleBuildSettings.java
rename to android-npw/src/com/android/tools/idea/npw/platform/GradleBuildSettings.java
index 2024186..3d18b5c 100644
--- a/android/src/com/android/tools/idea/gradle/npw/project/GradleBuildSettings.java
+++ b/android-npw/src/com/android/tools/idea/npw/platform/GradleBuildSettings.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tools.idea.gradle.npw.project;
+package com.android.tools.idea.npw.platform;
import com.android.repository.Revision;
import com.android.repository.api.ProgressIndicator;
@@ -28,7 +28,8 @@
/**
* Gets the recommended gradle tools {@link Revision}. If there is no "stable" installed version, it will use the latest installed
* "preview" version.
- * If no stable or preview version is installed, or they are to older than the recommend version, it returns last known recommend version
+ * If no stable or preview version is installed, or they are older than the recommended version, it returns the last known
+ * recommended version
*
* @param sdkHandler The installed Android SDK handler
* @param progress a progress indicator
diff --git a/android-npw/src/com/android/tools/idea/npw/project/ConfigureAndroidProjectStep.java b/android-npw/src/com/android/tools/idea/npw/project/ConfigureAndroidProjectStep.java
index 53bf4bd..ed456be 100644
--- a/android-npw/src/com/android/tools/idea/npw/project/ConfigureAndroidProjectStep.java
+++ b/android-npw/src/com/android/tools/idea/npw/project/ConfigureAndroidProjectStep.java
@@ -59,6 +59,7 @@
import com.android.tools.idea.sdk.wizard.LicenseAgreementModel;
import com.android.tools.idea.sdk.wizard.LicenseAgreementStep;
import com.android.tools.idea.ui.validation.validators.PathValidator;
+import com.android.tools.idea.ui.validation.validators.StringPathValidator;
import com.android.tools.idea.wizard.model.ModelWizard;
import com.android.tools.idea.wizard.model.ModelWizardStep;
import com.android.tools.idea.wizard.template.BuildConfigurationLanguageForNewProject;
@@ -82,8 +83,6 @@
import com.intellij.util.ModalityUiUtil;
import com.intellij.util.containers.ContainerUtil;
import java.io.File;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@@ -177,7 +176,7 @@
TextProperty locationText = new TextProperty(myProjectLocation.getTextField());
BoolProperty isLocationSynced = new BoolValueProperty(true);
myBindings.bind(locationText, computedLocation, isLocationSynced);
- myBindings.bind(myProjectModel.getProjectLocation(), locationText);
+ myBindings.bind(myProjectModel.getProjectLocation(), locationText.trim());
myListeners.listen(locationText, value -> isLocationSynced.set(value.equals(computedLocation.get())));
OptionalProperty<VersionItem> androidSdkInfo = getModel().androidSdkInfo();
@@ -221,8 +220,8 @@
myValidatorPanel.registerValidator(myProjectModel.getApplicationName(), new ProjectNameValidator());
- Expression<Path> locationFile = myProjectModel.getProjectLocation().transform(Paths::get);
- myValidatorPanel.registerValidator(locationFile, PathValidator.createDefault("project location"));
+ myValidatorPanel.registerValidator(myProjectModel.getProjectLocation(),
+ new StringPathValidator(PathValidator.createDefault("Save location")));
myValidatorPanel.registerValidator(myProjectModel.getPackageName(),
value -> Validator.Result.fromNullableMessage(AndroidUtils.validatePackageName(value)));
diff --git a/android-plugin/BUILD b/android-plugin/BUILD
index 56acefd..1fefd49 100644
--- a/android-plugin/BUILD
+++ b/android-plugin/BUILD
@@ -16,6 +16,7 @@
runtime_deps = [
"//tools/adt/idea/android-plugin/descriptor:intellij.android.plugin.descriptor",
"//tools/adt/idea/layout-inspector:intellij.android.layout-inspector.gradle",
+ "//tools/adt/idea/app-quality-insights/ide/gradle:intellij.android.app-quality-insights.ide.gradle",
],
# do not sort: must match IML order
deps = [
diff --git a/android-plugin/descriptor/resources/META-INF/plugin.xml b/android-plugin/descriptor/resources/META-INF/plugin.xml
index d5a41d7..e5c0393 100644
--- a/android-plugin/descriptor/resources/META-INF/plugin.xml
+++ b/android-plugin/descriptor/resources/META-INF/plugin.xml
@@ -26,6 +26,7 @@
<xi:include href="/META-INF/android-dagger.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/app-insights-api.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/app-insights.xml" xpointer="xpointer(/idea-plugin/*)"/>
+ <xi:include href="/META-INF/app-insights-gradle.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/databinding.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/mlkit.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/sdk-updates.xml" xpointer="xpointer(/idea-plugin/*)"/>
diff --git a/android-plugin/intellij.android.plugin.iml b/android-plugin/intellij.android.plugin.iml
index 00a54af..364b2ed 100644
--- a/android-plugin/intellij.android.plugin.iml
+++ b/android-plugin/intellij.android.plugin.iml
@@ -58,5 +58,6 @@
<orderEntry type="module" module-name="intellij.android.kotlin.extensions" />
<orderEntry type="module" module-name="intellij.android.kotlin.idea" />
<orderEntry type="module" module-name="intellij.android.layout-inspector.gradle" scope="RUNTIME" />
+ <orderEntry type="module" module-name="intellij.android.app-quality-insights.ide.gradle" scope="RUNTIME" />
</component>
</module>
\ No newline at end of file
diff --git a/android-templates/testData/golden/testNewBasicActivityMaterial3/Template test module/src/main/java/template/test/pkg/MainActivity.kt b/android-templates/testData/golden/testNewBasicActivityMaterial3/Template test module/src/main/java/template/test/pkg/MainActivity.kt
index 3eef53b..25b3cf1 100644
--- a/android-templates/testData/golden/testNewBasicActivityMaterial3/Template test module/src/main/java/template/test/pkg/MainActivity.kt
+++ b/android-templates/testData/golden/testNewBasicActivityMaterial3/Template test module/src/main/java/template/test/pkg/MainActivity.kt
@@ -31,7 +31,8 @@
binding.fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
- .setAction("Action", null).show()
+ .setAction("Action", null)
+ .setAnchorView(R.id.fab).show()
}
}
diff --git a/android-templates/testData/golden/testNewNavigationDrawerActivity/Template test module/src/main/java/template/test/pkg/MainActivity.java b/android-templates/testData/golden/testNewNavigationDrawerActivity/Template test module/src/main/java/template/test/pkg/MainActivity.java
index 534123f..ed1ebcf 100644
--- a/android-templates/testData/golden/testNewNavigationDrawerActivity/Template test module/src/main/java/template/test/pkg/MainActivity.java
+++ b/android-templates/testData/golden/testNewNavigationDrawerActivity/Template test module/src/main/java/template/test/pkg/MainActivity.java
@@ -33,7 +33,8 @@
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
- .setAction("Action", null).show();
+ .setAction("Action", null)
+ .setAnchorView(R.id.fab).show();
}
});
DrawerLayout drawer = binding.drawerLayout;
diff --git a/android-templates/testData/golden/testNewNavigationDrawerActivityWithKotlin/Template test module/src/main/java/template/test/pkg/MainActivity.kt b/android-templates/testData/golden/testNewNavigationDrawerActivityWithKotlin/Template test module/src/main/java/template/test/pkg/MainActivity.kt
index 022cd80..2d08794 100644
--- a/android-templates/testData/golden/testNewNavigationDrawerActivityWithKotlin/Template test module/src/main/java/template/test/pkg/MainActivity.kt
+++ b/android-templates/testData/golden/testNewNavigationDrawerActivityWithKotlin/Template test module/src/main/java/template/test/pkg/MainActivity.kt
@@ -28,7 +28,8 @@
binding.appBarMain.fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
- .setAction("Action", null).show()
+ .setAction("Action", null)
+ .setAnchorView(R.id.fab).show()
}
val drawerLayout: DrawerLayout = binding.drawerLayout
val navView: NavigationView = binding.navView
diff --git a/android-templates/testData/golden/testNewScrollingActivity/Template test module/src/main/java/template/test/pkg/ScrollingActivity.java b/android-templates/testData/golden/testNewScrollingActivity/Template test module/src/main/java/template/test/pkg/ScrollingActivity.java
index 34ff859..e8d3519 100644
--- a/android-templates/testData/golden/testNewScrollingActivity/Template test module/src/main/java/template/test/pkg/ScrollingActivity.java
+++ b/android-templates/testData/golden/testNewScrollingActivity/Template test module/src/main/java/template/test/pkg/ScrollingActivity.java
@@ -37,7 +37,8 @@
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
- .setAction("Action", null).show();
+ .setAction("Action", null)
+ .setAnchorView(R.id.fab).show();
}
});
}
diff --git a/android-templates/testData/golden/testNewScrollingActivityWithKotlin/Template test module/src/main/java/template/test/pkg/ScrollingActivity.kt b/android-templates/testData/golden/testNewScrollingActivityWithKotlin/Template test module/src/main/java/template/test/pkg/ScrollingActivity.kt
index 9f0da4a..bffb558 100644
--- a/android-templates/testData/golden/testNewScrollingActivityWithKotlin/Template test module/src/main/java/template/test/pkg/ScrollingActivity.kt
+++ b/android-templates/testData/golden/testNewScrollingActivityWithKotlin/Template test module/src/main/java/template/test/pkg/ScrollingActivity.kt
@@ -23,7 +23,8 @@
binding.toolbarLayout.title = title
binding.fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
- .setAction("Action", null).show()
+ .setAction("Action", null)
+ .setAnchorView(R.id.fab).show()
}
}
diff --git a/android-templates/testData/golden/testNewTabbedActivity/Template test module/src/main/java/template/test/pkg/MainActivity.java b/android-templates/testData/golden/testNewTabbedActivity/Template test module/src/main/java/template/test/pkg/MainActivity.java
index fd5f9dd..0aae17d 100644
--- a/android-templates/testData/golden/testNewTabbedActivity/Template test module/src/main/java/template/test/pkg/MainActivity.java
+++ b/android-templates/testData/golden/testNewTabbedActivity/Template test module/src/main/java/template/test/pkg/MainActivity.java
@@ -38,7 +38,8 @@
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
- .setAction("Action", null).show();
+ .setAction("Action", null)
+ .setAnchorView(R.id.fab).show();
}
});
}
diff --git a/android-templates/testData/golden/testNewTabbedActivityWithKotlin/Template test module/src/main/java/template/test/pkg/MainActivity.kt b/android-templates/testData/golden/testNewTabbedActivityWithKotlin/Template test module/src/main/java/template/test/pkg/MainActivity.kt
index 1657075..c07c325 100644
--- a/android-templates/testData/golden/testNewTabbedActivityWithKotlin/Template test module/src/main/java/template/test/pkg/MainActivity.kt
+++ b/android-templates/testData/golden/testNewTabbedActivityWithKotlin/Template test module/src/main/java/template/test/pkg/MainActivity.kt
@@ -30,7 +30,8 @@
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
- .setAction("Action", null).show()
+ .setAction("Action", null)
+ .setAnchorView(R.id.fab).show()
}
}
}
\ No newline at end of file
diff --git a/android-templates/testData/golden/testResponsiveActivity/Template test module/src/main/java/template/test/pkg/MainActivity.java b/android-templates/testData/golden/testResponsiveActivity/Template test module/src/main/java/template/test/pkg/MainActivity.java
index 696fad5..59a22fc 100644
--- a/android-templates/testData/golden/testResponsiveActivity/Template test module/src/main/java/template/test/pkg/MainActivity.java
+++ b/android-templates/testData/golden/testResponsiveActivity/Template test module/src/main/java/template/test/pkg/MainActivity.java
@@ -32,7 +32,7 @@
setSupportActionBar(binding.appBarMain.toolbar);
if (binding.appBarMain.fab != null) {
binding.appBarMain.fab.setOnClickListener(view -> Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
- .setAction("Action", null).show());
+ .setAction("Action", null).setAnchorView(R.id.fab).show());
}
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_content_main);
assert navHostFragment != null;
diff --git a/android-templates/testData/golden/testResponsiveActivityWithKotlin/Template test module/src/main/java/template/test/pkg/MainActivity.kt b/android-templates/testData/golden/testResponsiveActivityWithKotlin/Template test module/src/main/java/template/test/pkg/MainActivity.kt
index 68686f1..ae463d7 100644
--- a/android-templates/testData/golden/testResponsiveActivityWithKotlin/Template test module/src/main/java/template/test/pkg/MainActivity.kt
+++ b/android-templates/testData/golden/testResponsiveActivityWithKotlin/Template test module/src/main/java/template/test/pkg/MainActivity.kt
@@ -27,7 +27,8 @@
binding.appBarMain.fab?.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
- .setAction("Action", null).show()
+ .setAction("Action", null)
+ .setAnchorView(R.id.fab).show()
}
val navHostFragment =
diff --git a/android-test-framework/testSrc/com/android/tools/idea/projectsystem/TestProjectSystem.kt b/android-test-framework/testSrc/com/android/tools/idea/projectsystem/TestProjectSystem.kt
index 18f6504..0d01f8f 100644
--- a/android-test-framework/testSrc/com/android/tools/idea/projectsystem/TestProjectSystem.kt
+++ b/android-test-framework/testSrc/com/android/tools/idea/projectsystem/TestProjectSystem.kt
@@ -88,6 +88,7 @@
var namespace: String? = null
var manifestOverrides = ManifestOverrides()
var useAndroidX: Boolean = false
+ var usesCompose: Boolean = false
init {
val sortedHighToLowDeps = availableDependencies.sortedWith(GradleCoordinate.COMPARE_PLUS_HIGHER).reversed()
@@ -250,6 +251,9 @@
override val moduleDependencies: ModuleDependencies
get() = StudioModuleDependencies(module)
+
+ override val usesCompose: Boolean
+ get() = this@TestProjectSystem.usesCompose
}
return TestAndroidModuleSystemImpl()
diff --git a/android-test-framework/testSrc/com/android/tools/idea/testing/AndroidGradleTestUtils.kt b/android-test-framework/testSrc/com/android/tools/idea/testing/AndroidGradleTestUtils.kt
index e0d8825..eccd782 100644
--- a/android-test-framework/testSrc/com/android/tools/idea/testing/AndroidGradleTestUtils.kt
+++ b/android-test-framework/testSrc/com/android/tools/idea/testing/AndroidGradleTestUtils.kt
@@ -126,6 +126,7 @@
import com.android.tools.idea.projectsystem.getHolderModule
import com.android.tools.idea.projectsystem.getProjectSystem
import com.android.tools.idea.projectsystem.gradle.GradleHolderProjectPath
+import com.android.tools.idea.projectsystem.gradle.GradleModuleSystem
import com.android.tools.idea.projectsystem.gradle.GradleProjectPath
import com.android.tools.idea.projectsystem.gradle.GradleProjectSystem
import com.android.tools.idea.projectsystem.gradle.GradleSourceSetProjectPath
@@ -133,8 +134,10 @@
import com.android.tools.idea.projectsystem.gradle.getGradleProjectPath
import com.android.tools.idea.projectsystem.gradle.resolveIn
import com.android.tools.idea.projectsystem.gradle.toSourceSetPath
+import com.android.tools.idea.res.ProjectLightResourceClassService
import com.android.tools.idea.run.ApkProvider
import com.android.tools.idea.run.ApplicationIdProvider
+import com.android.tools.idea.run.GradleApplicationIdProvider
import com.android.tools.idea.run.ValidationError
import com.android.tools.idea.sdk.IdeSdks
import com.android.tools.idea.stats.ProjectSizeUsageTrackerListener
@@ -1307,60 +1310,13 @@
error("There is already more than one module in the test project.")
}
- val gradleProjectSystem = GradleProjectSystem(project)
- ProjectSystemService.getInstance(project).replaceProjectSystemForTests(object : AndroidProjectSystem {
+ ProjectSystemService.getInstance(project).replaceProjectSystemForTests(object : GradleProjectSystem(project) {
// Many tests that invoke `compileProject` work with timestamps. To avoid flaky tests we inject a millisecond delay each time
// build is requested.
private val buildManager = TestProjectSystemBuildManager(ensureClockAdvancesWhileBuilding = true)
override fun getBuildManager(): ProjectSystemBuildManager = buildManager
- override fun getPsiElementFinders(): Collection<PsiElementFinder> = gradleProjectSystem.getPsiElementFinders()
-
- override fun getLightResourceClassService(): LightResourceClassService = gradleProjectSystem.getLightResourceClassService()
-
- override fun getSourceProvidersFactory(): SourceProvidersFactory = gradleProjectSystem.getSourceProvidersFactory()
-
- override fun getClassJarProvider(): ClassJarProvider = gradleProjectSystem.getClassJarProvider()
-
- override fun getAndroidFacetsWithPackageName(project: Project, packageName: String): Collection<AndroidFacet> =
- gradleProjectSystem.getAndroidFacetsWithPackageName(project, packageName)
-
- override fun isNamespaceOrParentPackage(packageName: String): Boolean =
- gradleProjectSystem.isNamespaceOrParentPackage(packageName)
-
- override fun getBootClasspath(module: Module): Collection<String> {
- return emptyList()
- }
-
- override fun getDefaultApkFile(): VirtualFile? = gradleProjectSystem.getDefaultApkFile()
-
- override fun getPathToAapt(): Path = gradleProjectSystem.getPathToAapt()
-
- override fun allowsFileCreation(): Boolean = gradleProjectSystem.allowsFileCreation()
-
- override fun getModuleSystem(module: Module): AndroidModuleSystem = gradleProjectSystem.getModuleSystem(module)
-
- override fun getApplicationIdProvider(runConfiguration: RunConfiguration): ApplicationIdProvider? =
- gradleProjectSystem.getApplicationIdProvider(runConfiguration)
-
- override fun getApkProvider(runConfiguration: RunConfiguration): ApkProvider? = gradleProjectSystem.getApkProvider(runConfiguration)
-
- override fun getSyncManager(): ProjectSystemSyncManager = gradleProjectSystem.getSyncManager()
-
- override fun validateRunConfiguration(runConfiguration: RunConfiguration): List<ValidationError> {
- return gradleProjectSystem.validateRunConfiguration(runConfiguration)
- }
-
- override fun getBuildConfigurationSourceProvider(): BuildConfigurationSourceProvider? =
- gradleProjectSystem.getBuildConfigurationSourceProvider()
-
- override fun getKnownApplicationIds(): Set<String> = gradleProjectSystem.getKnownApplicationIds()
-
- override fun findModulesWithApplicationId(applicationId: String): Collection<Module> =
- gradleProjectSystem.findModulesWithApplicationId(applicationId)
-
- override fun supportsProfilingMode(): Boolean = gradleProjectSystem.supportsProfilingMode()
-
+ override fun getBootClasspath(module: Module): Collection<String> = emptyList()
})
setupTestProjectFromAndroidModelCore(project, rootProjectBasePath, moduleBuilders, setupAllVariants, cacheExistingVariants = false)
}
diff --git a/android-transport/src/com/android/tools/idea/transport/manager/TransportStreamManager.kt b/android-transport/src/com/android/tools/idea/transport/manager/TransportStreamManager.kt
index f1634f6..beb81da 100644
--- a/android-transport/src/com/android/tools/idea/transport/manager/TransportStreamManager.kt
+++ b/android-transport/src/com/android/tools/idea/transport/manager/TransportStreamManager.kt
@@ -16,39 +16,43 @@
package com.android.tools.idea.transport.manager
import com.android.annotations.concurrency.AnyThread
+import com.android.tools.idea.concurrency.AndroidCoroutineScope
+import com.android.tools.idea.transport.TransportClient
+import com.android.tools.idea.transport.TransportService
import com.android.tools.profiler.proto.Common
import com.android.tools.profiler.proto.Transport.GetEventGroupsRequest
import com.android.tools.profiler.proto.Transport.GetEventGroupsResponse
import com.android.tools.profiler.proto.TransportServiceGrpc
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.components.Service
+import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flatMap
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.flow.transform
-import java.util.concurrent.atomic.AtomicBoolean
+import org.jetbrains.annotations.VisibleForTesting
const val DELAY_MILLIS = 100L
-/**
- * Represents stream connection activity.
- */
+/** Represents stream connection activity. */
sealed class StreamActivity(val streamChannel: TransportStreamChannel)
-/**
- * Represents a stream connect activity.
- */
+/** Represents a stream connect activity. */
class StreamConnected(streamChannel: TransportStreamChannel) : StreamActivity(streamChannel)
-/**
- * Represents a stream disconnect activity.
- */
+/** Represents a stream disconnect activity. */
class StreamDisconnected(streamChannel: TransportStreamChannel) : StreamActivity(streamChannel)
-/**
- * This simply wraps [Common.Event] and represents an event from a stream channel.
- */
+/** This simply wraps [Common.Event] and represents an event from a stream channel. */
class StreamEvent(val event: Common.Event)
private fun TransportServiceGrpc.TransportServiceBlockingStub.eventGroupFlow(
@@ -61,55 +65,71 @@
}
}
-/**
- * Listens to and manages STREAM events from the transport pipeline. Users can subscribe to the activity flow to listen in on new streams.
- *
- * Streams are provided in the form of [TransportStreamChannel], which provides users the ability to register their own listeners while
- * automatically associating them with the stream.
- */
-@AnyThread
-class TransportStreamManager private constructor(
- private val client: TransportServiceGrpc.TransportServiceBlockingStub,
- private val dispatcher: CoroutineDispatcher
-) {
- fun streamActivityFlow(): Flow<StreamActivity> {
- val streams = mutableMapOf<Long, TransportStreamChannel>()
- return flow {
- while (true) {
- StreamQueryUtils.queryForDevices(client).forEach { stream ->
- if (stream.device.state == Common.Device.State.ONLINE) {
- if (!streams.containsKey(stream.streamId)) {
- val streamChannel = TransportStreamChannel(stream, client, dispatcher)
- emit(StreamConnected(streamChannel))
- streams[stream.streamId] = streamChannel
- }
- }
- else {
- streams.remove(stream.streamId)?.let { channel ->
- channel.cleanUp()
- emit(StreamDisconnected(channel))
- }
- }
- }
- delay(DELAY_MILLIS)
- }
- }.flowOn(dispatcher)
+@Service(Service.Level.APP)
+class TransportStreamManagerService : Disposable {
+ private val client: TransportClient
+ init {
+ // Initialize transport gRpc machinery.
+ TransportService.getInstance()
+ client = TransportClient(TransportService.channelName)
}
- companion object {
- private val managers = mutableListOf<TransportStreamManager>()
+ val streamManager =
+ TransportStreamManager(
+ TransportClient(TransportService.channelName).transportStub,
+ AndroidCoroutineScope(this, Dispatchers.IO)
+ )
- fun createManager(
- client: TransportServiceGrpc.TransportServiceBlockingStub,
- dispatcher: CoroutineDispatcher
- ): TransportStreamManager {
- val manager = TransportStreamManager(client, dispatcher)
- managers.add(manager)
- return manager
- }
+ override fun dispose() = Unit
+}
- fun unregisterManager(manager: TransportStreamManager) {
- managers.remove(manager)
+/**
+ * Listens to and manages STREAM events from the transport pipeline. Users can subscribe to the
+ * activity flow to listen in on new streams.
+ *
+ * Streams are provided in the form of [TransportStreamChannel], which provides users the ability to
+ * register their own listeners while automatically associating them with the stream.
+ *
+ * Please use [TransportStreamManagerService] to obtain instance of [TransportStreamManager] in
+ * production code.
+ */
+class TransportStreamManager
+@VisibleForTesting
+constructor(
+ private val client: TransportServiceGrpc.TransportServiceBlockingStub,
+ scope: CoroutineScope
+) {
+
+ private val streamState =
+ flow {
+ while (true) {
+ emit(StreamQueryUtils.queryForDevices(client))
+ delay(DELAY_MILLIS)
+ }
+ }
+ .shareIn(scope, SharingStarted.Lazily, 1)
+
+ /**
+ * Collects the current state of connected devices in the form of a flow of [StreamActivity]
+ * events.
+ */
+ fun streamActivityFlow(): Flow<StreamActivity> = flow {
+ val streams = mutableMapOf<Long, TransportStreamChannel>()
+ streamState.collect { streamState ->
+ streamState.forEach { stream ->
+ if (stream.device.state == Common.Device.State.ONLINE) {
+ if (!streams.containsKey(stream.streamId)) {
+ val streamChannel = TransportStreamChannel(stream, client, Dispatchers.IO)
+ emit(StreamConnected(streamChannel))
+ streams[stream.streamId] = streamChannel
+ }
+ } else {
+ streams.remove(stream.streamId)?.let { channel ->
+ channel.cleanUp()
+ emit(StreamDisconnected(channel))
+ }
+ }
+ }
}
}
}
@@ -125,21 +145,19 @@
val client: TransportServiceGrpc.TransportServiceBlockingStub,
private val dispatcher: CoroutineDispatcher
) {
-
private val isClosed = AtomicBoolean(false)
- /**
- * Creates and returns a flow based on the filtering criteria indicated by [query].
- */
+ /** Creates and returns a flow based on the filtering criteria indicated by [query]. */
fun eventFlow(query: StreamEventQuery): Flow<StreamEvent> {
var lastTimeStamp = query.startTime?.invoke() ?: Long.MIN_VALUE
return client
.eventGroupFlow {
- val builder = GetEventGroupsRequest.newBuilder()
- .setStreamId(stream.streamId)
- .setKind(query.eventKind)
- .setFromTimestamp(lastTimeStamp)
- .setToTimestamp(query.endTime())
+ val builder =
+ GetEventGroupsRequest.newBuilder()
+ .setStreamId(stream.streamId)
+ .setKind(query.eventKind)
+ .setFromTimestamp(lastTimeStamp)
+ .setToTimestamp(query.endTime())
query.processId?.invoke()?.let { builder.pid = it }
query.groupId?.invoke()?.let { builder.groupId = it }
builder.build()
@@ -147,10 +165,11 @@
.takeWhile { !isClosed.get() }
.transform { response ->
if (response != GetEventGroupsResponse.getDefaultInstance()) {
- val filtered = response.groupsList
- .flatMap { group -> group.eventsList }
- .sortedWith(query.sortOrder)
- .filter { event -> event.timestamp >= lastTimeStamp && query.filter(event) }
+ val filtered =
+ response.groupsList
+ .flatMap { group -> group.eventsList }
+ .sortedWith(query.sortOrder)
+ .filter { event -> event.timestamp >= lastTimeStamp && query.filter(event) }
filtered.forEach { event -> emit(StreamEvent(event)) }
val maxTimeEvent = filtered.maxByOrNull { it.timestamp }
maxTimeEvent?.let { lastTimeStamp = kotlin.math.max(lastTimeStamp, it.timestamp + 1) }
@@ -164,39 +183,44 @@
doQuery: suspend () -> List<Common.Process> = {
StreamQueryUtils.queryForProcesses(client, stream.streamId, filter)
}
- ): Flow<Common.Process> {
- return flow<Common.Process> {
- val processes = mutableMapOf<Int, Common.Process>()
- while (true) {
- // The query will return a list of all processes the transport pipeline is aware of, whether they
- // are alive or dead. Therefore, in order to check for updates to the processes' states, we need
- // to reconcile it with the cached state from a previous query.
- val queryResult = doQuery()
- val queriedPids = queryResult.map { it.pid }.toSet()
- val deadProcesses = processes.filterNot { it.key in queriedPids }.values
+ ) =
+ flow<Common.Process> {
+ val processes = mutableMapOf<Int, Common.Process>()
+ while (true) {
+ // The query will return a list of all processes the transport pipeline is aware of,
+ // whether they
+ // are alive or dead. Therefore, in order to check for updates to the processes' states,
+ // we need
+ // to reconcile it with the cached state from a previous query.
+ val queryResult = doQuery()
+ val queriedPids = queryResult.map { it.pid }.toSet()
+ val deadProcesses = processes.filterNot { it.key in queriedPids }.values
- // Due to the nature of polling, we might miss DEAD process updates because, for example, the process
- // was restarted. We check for this case by looking for known pids that have disappeared from the
- // current query result.
- deadProcesses.forEach { process -> emit(process.toBuilder().setState(Common.Process.State.DEAD).build()) }
-
- // Emit updates in the order they were received
- queryResult.forEach { process ->
- if (process.state != processes[process.pid]?.state || process.pid !in processes.keys) {
- emit(process)
+ // Due to the nature of polling, we might miss DEAD process updates because, for example,
+ // the process
+ // was restarted. We check for this case by looking for known pids that have disappeared
+ // from the
+ // current query result.
+ deadProcesses.forEach { process ->
+ emit(process.toBuilder().setState(Common.Process.State.DEAD).build())
}
- }
- processes.clear()
- processes.putAll(queryResult.associateBy { it.pid })
- delay(DELAY_MILLIS)
+ // Emit updates in the order they were received
+ queryResult.forEach { process ->
+ if (process.state != processes[process.pid]?.state || process.pid !in processes.keys) {
+ emit(process)
+ }
+ }
+
+ processes.clear()
+ processes.putAll(queryResult.associateBy { it.pid })
+ delay(DELAY_MILLIS)
+ }
}
- }
- .flowOn(dispatcher)
.takeWhile { !isClosed.get() }
- }
+ .flowOn(dispatcher)
internal fun cleanUp() {
isClosed.set(true)
}
-}
\ No newline at end of file
+}
diff --git a/android-transport/testSrc/com/android/tools/idea/transport/manager/TransportStreamManagerRule.kt b/android-transport/testSrc/com/android/tools/idea/transport/manager/TransportStreamManagerRule.kt
new file mode 100644
index 0000000..6c8f49a
--- /dev/null
+++ b/android-transport/testSrc/com/android/tools/idea/transport/manager/TransportStreamManagerRule.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.transport.manager
+
+import com.android.tools.idea.transport.TransportClient
+import com.android.tools.idea.transport.faketransport.FakeGrpcServer
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import org.junit.rules.ExternalResource
+
+class TransportStreamManagerRule(private val fakeGrpcServer: FakeGrpcServer) : ExternalResource() {
+ lateinit var streamManager: TransportStreamManager
+ private lateinit var scope: CoroutineScope
+ private lateinit var client: TransportClient
+
+ override fun before() {
+ scope = CoroutineScope(EmptyCoroutineContext)
+ client = TransportClient(fakeGrpcServer.name)
+ streamManager =
+ TransportStreamManager(client.transportStub, scope)
+ }
+
+ override fun after() {
+ client.shutdown()
+ scope.cancel()
+ }
+}
diff --git a/android-transport/testSrc/com/android/tools/idea/transport/manager/TransportStreamManagerTest.kt b/android-transport/testSrc/com/android/tools/idea/transport/manager/TransportStreamManagerTest.kt
index 31811e2..ab2150a 100644
--- a/android-transport/testSrc/com/android/tools/idea/transport/manager/TransportStreamManagerTest.kt
+++ b/android-transport/testSrc/com/android/tools/idea/transport/manager/TransportStreamManagerTest.kt
@@ -16,74 +16,57 @@
package com.android.tools.idea.transport.manager
import com.android.tools.adtui.model.FakeTimer
-import com.android.tools.idea.transport.TransportClient
import com.android.tools.idea.transport.faketransport.FakeGrpcServer
import com.android.tools.idea.transport.faketransport.FakeTransportService
import com.android.tools.profiler.proto.Common
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.asCoroutineDispatcher
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectIndexed
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
-import org.junit.Before
import org.junit.Rule
import org.junit.Test
-import java.util.concurrent.ExecutorService
-import java.util.concurrent.Executors
+import org.junit.rules.RuleChain
@ExperimentalCoroutinesApi
class TransportStreamManagerTest {
private val timer = FakeTimer()
private val transportService = FakeTransportService(timer, false)
- private val fakeDevice2 = FakeTransportService.FAKE_DEVICE.toBuilder().setDeviceId(FakeTransportService.FAKE_DEVICE_ID + 1).build()
+ private val fakeDevice2 =
+ FakeTransportService.FAKE_DEVICE.toBuilder()
+ .setDeviceId(FakeTransportService.FAKE_DEVICE_ID + 1)
+ .build()
private val offlineFakeDevice2 =
- FakeTransportService.FAKE_OFFLINE_DEVICE.toBuilder().setDeviceId(FakeTransportService.FAKE_DEVICE_ID + 1).build()
- private val fakeProcess2 = FakeTransportService.FAKE_PROCESS.toBuilder().setDeviceId(fakeDevice2.deviceId).build()
- private lateinit var executorService: ExecutorService
- private lateinit var dispatcher: CoroutineDispatcher
+ FakeTransportService.FAKE_OFFLINE_DEVICE.toBuilder()
+ .setDeviceId(FakeTransportService.FAKE_DEVICE_ID + 1)
+ .build()
+ private val fakeProcess2 =
+ FakeTransportService.FAKE_PROCESS.toBuilder().setDeviceId(fakeDevice2.deviceId).build()
- @get:Rule
- val grpcServerRule = FakeGrpcServer.createFakeGrpcServer("AppInspectionDiscoveryTest", transportService)
+ private val grpcServerRule =
+ FakeGrpcServer.createFakeGrpcServer("AppInspectionDiscoveryTest", transportService)
+ private val streamManagerRule = TransportStreamManagerRule(grpcServerRule)
- @Before
- fun setUp() {
- executorService = Executors.newSingleThreadExecutor()
- dispatcher = executorService.asCoroutineDispatcher()
- }
-
- fun tearDown() {
- dispatcher.cancel()
- executorService.shutdownNow()
- }
+ @get:Rule val ruleChain = RuleChain.outerRule(grpcServerRule).around(streamManagerRule)
@Test
fun discoverNewStream() = runBlocking {
- val manager =
- TransportStreamManager
- .createManager(TransportClient(grpcServerRule.name).transportStub, dispatcher)
-
val streamReadyDeferred = CompletableDeferred<Unit>()
val streamDeadDeferred = CompletableDeferred<Unit>()
launch {
- manager.streamActivityFlow()
- .take(2)
- .collect {
- if (it is StreamConnected) {
- assertThat(it.streamChannel.stream.device).isEqualTo(FakeTransportService.FAKE_DEVICE)
- streamReadyDeferred.complete(Unit)
- }
- else if (it is StreamDisconnected) {
- streamDeadDeferred.complete(Unit)
- }
+ streamManagerRule.streamManager.streamActivityFlow().take(2).collect {
+ if (it is StreamConnected) {
+ assertThat(it.streamChannel.stream.device).isEqualTo(FakeTransportService.FAKE_DEVICE)
+ streamReadyDeferred.complete(Unit)
+ } else if (it is StreamDisconnected) {
+ streamDeadDeferred.complete(Unit)
}
+ }
}
transportService.addDevice(FakeTransportService.FAKE_DEVICE)
@@ -98,29 +81,22 @@
@Test
fun rediscoverStream() = runBlocking {
- val manager =
- TransportStreamManager
- .createManager(TransportClient(grpcServerRule.name).transportStub, dispatcher)
-
val streamReadyDeferred = CompletableDeferred<Unit>()
val streamReadyAgainDeferred = CompletableDeferred<Unit>()
val streamDeadDeferred = CompletableDeferred<Unit>()
launch {
- manager.streamActivityFlow()
- .take(3)
- .collectIndexed { index, activity ->
- if (activity is StreamConnected) {
- if (index == 0) {
- streamReadyDeferred.complete(Unit)
- }
- else {
- streamReadyAgainDeferred.complete(Unit)
- }
+ streamManagerRule.streamManager.streamActivityFlow().take(3).collectIndexed { index, activity
+ ->
+ if (activity is StreamConnected) {
+ if (index == 0) {
+ streamReadyDeferred.complete(Unit)
+ } else {
+ streamReadyAgainDeferred.complete(Unit)
}
- else if (activity is StreamDisconnected) {
- streamDeadDeferred.complete(Unit)
- }
+ } else if (activity is StreamDisconnected) {
+ streamDeadDeferred.complete(Unit)
}
+ }
}
transportService.addDevice(FakeTransportService.FAKE_DEVICE)
@@ -140,25 +116,19 @@
@Test
fun discoverMultipleStreams() = runBlocking {
- val manager =
- TransportStreamManager
- .createManager(TransportClient(grpcServerRule.name).transportStub, dispatcher)
-
val devicesDetected = CompletableDeferred<Unit>()
launch {
- manager.streamActivityFlow()
- .take(4)
- .collectIndexed { index, activity ->
- if (index < 2) {
- assertThat(activity).isInstanceOf(StreamConnected::class.java)
- }
- else {
- assertThat(activity).isInstanceOf(StreamDisconnected::class.java)
- }
- if (index == 1) {
- devicesDetected.complete(Unit)
- }
+ streamManagerRule.streamManager.streamActivityFlow().take(4).collectIndexed { index, activity
+ ->
+ if (index < 2) {
+ assertThat(activity).isInstanceOf(StreamConnected::class.java)
+ } else {
+ assertThat(activity).isInstanceOf(StreamDisconnected::class.java)
}
+ if (index == 1) {
+ devicesDetected.complete(Unit)
+ }
+ }
}
transportService.addDevice(FakeTransportService.FAKE_DEVICE)
@@ -177,19 +147,15 @@
// Tests stream manager does not ignore stream events from a device that has a slower clock.
@Test
fun streamsWithDifferentClocks() = runBlocking {
- val manager =
- TransportStreamManager.createManager(TransportClient(grpcServerRule.name).transportStub, dispatcher)
-
launch {
- manager.streamActivityFlow()
- .take(2)
- .collect { activity ->
- launch {
- activity.streamChannel.eventFlow(StreamEventQuery(Common.Event.Kind.PROCESS))
- .take(1)
- .collect()
- }
+ streamManagerRule.streamManager.streamActivityFlow().take(2).collect { activity ->
+ launch {
+ activity.streamChannel
+ .eventFlow(StreamEventQuery(Common.Event.Kind.PROCESS))
+ .take(1)
+ .collect()
}
+ }
}
// Start stream 1 and stream 2
@@ -207,24 +173,18 @@
@Test
fun streamDisconnect_closesFlows() = runBlocking {
- val manager =
- TransportStreamManager.createManager(TransportClient(grpcServerRule.name).transportStub, dispatcher)
-
val streamReadyDeferred = CompletableDeferred<Unit>()
launch {
- manager.streamActivityFlow()
- .take(2)
- .collect { activity ->
- if (activity is StreamConnected) {
- launch {
- activity.streamChannel.eventFlow(StreamEventQuery(Common.Event.Kind.PROCESS))
- .collect {
- // Collection should be cancelled when stream is disconnected.
- }
+ streamManagerRule.streamManager.streamActivityFlow().take(2).collect { activity ->
+ if (activity is StreamConnected) {
+ launch {
+ activity.streamChannel.eventFlow(StreamEventQuery(Common.Event.Kind.PROCESS)).collect {
+ // Collection should be cancelled when stream is disconnected.
}
- streamReadyDeferred.complete(Unit)
}
+ streamReadyDeferred.complete(Unit)
}
+ }
}
transportService.addDevice(FakeTransportService.FAKE_DEVICE)
@@ -238,36 +198,34 @@
@Test
fun `add and remove processes`() = runBlocking {
- val manager =
- TransportStreamManager
- .createManager(TransportClient(grpcServerRule.name).transportStub, dispatcher)
-
transportService.addDevice(FakeTransportService.FAKE_DEVICE)
val queryChannel = Channel<List<Common.Process>>(capacity = 1)
- manager.streamActivityFlow()
- .take(1)
- .collect { activity ->
- queryChannel.send(listOf(FakeTransportService.FAKE_PROCESS, FakeTransportService.FAKE_PROFILEABLE_PROCESS))
- activity.streamChannel.processesFlow({ _, _ -> true }) {
- queryChannel.receive()
- }
- .take(4)
- .collectIndexed { index, process ->
- when (index) {
- 0 -> assertThat(process).isEqualTo(FakeTransportService.FAKE_PROCESS)
- 1 -> {
- assertThat(process).isEqualTo(FakeTransportService.FAKE_PROFILEABLE_PROCESS)
- queryChannel.send(listOf(FakeTransportService.FAKE_OFFLINE_PROCESS))
- }
-
- 2 -> assertThat(process).isEqualTo(
- FakeTransportService.FAKE_PROFILEABLE_PROCESS.toBuilder().setState(Common.Process.State.DEAD).build())
-
- 3 -> assertThat(process).isEqualTo(FakeTransportService.FAKE_OFFLINE_PROCESS)
+ streamManagerRule.streamManager.streamActivityFlow().take(1).collect { activity ->
+ queryChannel.send(
+ listOf(FakeTransportService.FAKE_PROCESS, FakeTransportService.FAKE_PROFILEABLE_PROCESS)
+ )
+ activity.streamChannel
+ .processesFlow({ _, _ -> true }) { queryChannel.receive() }
+ .take(4)
+ .collectIndexed { index, process ->
+ when (index) {
+ 0 -> assertThat(process).isEqualTo(FakeTransportService.FAKE_PROCESS)
+ 1 -> {
+ assertThat(process).isEqualTo(FakeTransportService.FAKE_PROFILEABLE_PROCESS)
+ queryChannel.send(listOf(FakeTransportService.FAKE_OFFLINE_PROCESS))
}
+ 2 ->
+ assertThat(process)
+ .isEqualTo(
+ FakeTransportService.FAKE_PROFILEABLE_PROCESS.toBuilder()
+ .setState(Common.Process.State.DEAD)
+ .build()
+ )
+ 3 -> assertThat(process).isEqualTo(FakeTransportService.FAKE_OFFLINE_PROCESS)
}
- }
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/android-uitests/BUILD b/android-uitests/BUILD
index f5bd34c..c3115c6 100644
--- a/android-uitests/BUILD
+++ b/android-uitests/BUILD
@@ -3260,9 +3260,7 @@
name = "JavaToKotlinConversionTest",
size = "enormous",
timeout = "long",
- data = COMMON_DATA + glob([
- "testData/SimpleApplication/**",
- ]),
+ data = COMMON_DATA,
jvm_flags = ["-Dtest.suite.class=com.android.tools.idea.tests.gui.kotlin.JavaToKotlinConversionTest"],
tags = COMMON_TAGS + [
"no_mac",
diff --git a/android-uitests/testSrc/com/android/tools/idea/tests/gui/kotlin/JavaToKotlinConversionTest.java b/android-uitests/testSrc/com/android/tools/idea/tests/gui/kotlin/JavaToKotlinConversionTest.java
index bd6302d..ba3de8d 100644
--- a/android-uitests/testSrc/com/android/tools/idea/tests/gui/kotlin/JavaToKotlinConversionTest.java
+++ b/android-uitests/testSrc/com/android/tools/idea/tests/gui/kotlin/JavaToKotlinConversionTest.java
@@ -172,9 +172,26 @@
ideFrame.requestProjectSyncAndWaitForSyncToFinish();
guiTest.waitForAllBackgroundTasksToBeCompleted();
- //Invoking project make.
+ // TODO(devtools-engprod): remove this step once b/300329659 is fixed
+ // Starting form Iguana release onwards, kotlinOptions is added in the build.gradle
+ // on configuring kotlin in java project. Above bug is raise to update the
+ // correct version of jvmTarget in kotlinOptions.
+ editor.open("app/build.gradle")
+ .select("jvmTarget.*(17)")
+ .enterText("1.8");
+
+ guiTest.waitForAllBackgroundTasksToBeCompleted();
+
+ // TODO(devtools-engprod): Remove this once all libraries are migrated to kotlin-stdlib
+ // More details for this issue is found here b/278545487
+ // and here https://youtrack.jetbrains.com/issue/KT-55297
+ editor.open("app/build.gradle")
+ .moveBetween("dependencies {", "")
+ .enterText("\nimplementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')");
+
+ //Invoking project make.
ideFrame.invokeAndWaitForBuildAction(Wait.seconds(300),
"Build", "Rebuild Project");
guiTest.waitForAllBackgroundTasksToBeCompleted();
}
-}
+}
\ No newline at end of file
diff --git a/android-uitests/testSrc/com/android/tools/idea/tests/gui/projectstructure/AndroidLibsDepTest.java b/android-uitests/testSrc/com/android/tools/idea/tests/gui/projectstructure/AndroidLibsDepTest.java
index 34f35e8..16958c9 100644
--- a/android-uitests/testSrc/com/android/tools/idea/tests/gui/projectstructure/AndroidLibsDepTest.java
+++ b/android-uitests/testSrc/com/android/tools/idea/tests/gui/projectstructure/AndroidLibsDepTest.java
@@ -34,7 +34,7 @@
@RunWith(GuiTestRemoteRunner.class)
public class AndroidLibsDepTest {
- @Rule public final GuiTestRule guiTest = new GuiTestRule().withTimeout(10, TimeUnit.MINUTES);
+ @Rule public final GuiTestRule guiTest = new GuiTestRule().withTimeout(15, TimeUnit.MINUTES);
private static final String LIB_NAME_1 = "modulea";
private static final String LIB_NAME_2 = "moduleb";
diff --git a/android/agpIntegrationTestSrc/com/android/tools/idea/run/UnsignedApkQuickFixTest.kt b/android/agpIntegrationTestSrc/com/android/tools/idea/run/UnsignedApkQuickFixTest.kt
index 489bbf1..30fee5f 100644
--- a/android/agpIntegrationTestSrc/com/android/tools/idea/run/UnsignedApkQuickFixTest.kt
+++ b/android/agpIntegrationTestSrc/com/android/tools/idea/run/UnsignedApkQuickFixTest.kt
@@ -15,17 +15,22 @@
*/
package com.android.tools.idea.run
+import com.android.testutils.MockitoKt
import com.android.tools.idea.gradle.dsl.api.ProjectBuildModel
import com.android.tools.idea.testing.AndroidGradleProjectRule
import com.android.tools.idea.testing.TestProjectPaths
import com.google.common.truth.Truth.assertThat
import com.intellij.openapi.actionSystem.DataContext
+import com.intellij.openapi.module.Module
+import junit.framework.TestCase.*
import org.junit.Rule
import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+@RunWith(JUnit4::class)
class UnsignedApkQuickFixTest {
- @get:Rule
- val projectRule = AndroidGradleProjectRule()
+ @get:Rule val projectRule = AndroidGradleProjectRule()
@Test
fun updatesBuildWithSelectedConfig() {
@@ -39,21 +44,94 @@
assertThat(signingConfigs[0].name()).isEqualTo("debug")
// Fake a selector that picks the default debug signing config.
- val fakeSelector = object : SigningConfigSelector {
- override fun showAndGet() = true
- override fun selectedConfig() = signingConfigs[0]
- }
+ val fakeSelector =
+ object : SigningConfigSelector {
+ override fun showAndGet() = true
+ override fun selectedConfig() = signingConfigs[0]
+ }
// Release build doesn't have a signing config assigned.
- val releaseBuildSigningConfig = buildModel.android().buildTypes().find { it.name() == "release" }?.signingConfig()
+ val releaseBuildSigningConfig =
+ buildModel.android().buildTypes().find { it.name() == "release" }?.signingConfig()
assertThat(releaseBuildSigningConfig?.valueAsString()).isNull()
- val unsignedApkQuickFix = UnsignedApkQuickFix(module, "release") { fakeSelector }
+ val unsignedApkQuickFix = UnsignedApkQuickFix(module, "release", null) { fakeSelector }
unsignedApkQuickFix.applyFix(DataContext.EMPTY_CONTEXT)
val updatedBuildModel = ProjectBuildModel.get(projectRule.project).getModuleBuildModel(module)!!
// Release build is now assigned the debug signing config.
- val expectedSigningConfig = updatedBuildModel.android().buildTypes().find { it.name() == "release" }?.signingConfig()
+ val expectedSigningConfig =
+ updatedBuildModel.android().buildTypes().find { it.name() == "release" }?.signingConfig()
assertThat(expectedSigningConfig?.valueAsString()).contains("debug")
}
-}
\ No newline at end of file
+
+ @Test
+ fun differentModuleReCaches() {
+ val module = MockitoKt.mock<Module>()
+ UnsignedApkQuickFix.unsignedApkQuickFix = UnsignedApkQuickFix(module, "release", null)
+
+ val module2 = MockitoKt.mock<Module>()
+ val quickFix = UnsignedApkQuickFix.create(module2, "release", null)
+
+ assertEquals(module2, quickFix!!.module)
+ }
+
+ @Test
+ fun differentBuildTypeReCaches() {
+ val module = MockitoKt.mock<Module>()
+ UnsignedApkQuickFix.unsignedApkQuickFix = UnsignedApkQuickFix(module, "release", null)
+
+ val quickFix = UnsignedApkQuickFix.create(module, "debug", null)
+
+ assertEquals("debug", quickFix!!.selectedBuildTypeName)
+ }
+
+ /**
+ * Tests the case where validation has been run already, but then the {@code
+ * AndroidRunConfigurationEditor} is opened, creating a new QuickFix where the callback would
+ * trigger the editor revalidation.
+ */
+ @Test
+ fun nonNullCallbackReCachesIfCurrentlyNull() {
+ val module = MockitoKt.mock<Module>()
+ UnsignedApkQuickFix.unsignedApkQuickFix = UnsignedApkQuickFix(module, "release", null)
+
+ val callback = MockitoKt.mock<Runnable>()
+ val quickFix = UnsignedApkQuickFix.create(module, "release", callback)
+
+ assertEquals(callback, quickFix!!.callback)
+ }
+
+ /**
+ * Tests the case where the {@code AndroidRunConfigurationEditor} has already set a revalidation
+ * callback, but a different validation request would have created a new QuickFix. In this case,
+ * we do not want to overwrite the cache.
+ */
+ @Test
+ fun nullCallbackDoesNotReCache() {
+ val module = MockitoKt.mock<Module>()
+ val callback = MockitoKt.mock<Runnable>()
+ UnsignedApkQuickFix.unsignedApkQuickFix = UnsignedApkQuickFix(module, "release", callback)
+
+ val quickFix = UnsignedApkQuickFix.create(module, "release", null)
+
+ assertEquals(callback, quickFix!!.callback)
+ }
+
+ /**
+ * Tests the case where a different callback is requested, e.g. if the {@code
+ * AndroidRunConfigurationEditor} dialog is opened again, meaning the new dialog would need to
+ * receive the revalidation request.
+ */
+ @Test
+ fun differentCallbackReCaches() {
+ val module = MockitoKt.mock<Module>()
+ val callback = MockitoKt.mock<Runnable>()
+ UnsignedApkQuickFix.unsignedApkQuickFix = UnsignedApkQuickFix(module, "release", callback)
+
+ val callback2 = MockitoKt.mock<Runnable>()
+ val quickFix = UnsignedApkQuickFix.create(module, "release", callback2)
+
+ assertEquals(callback2, quickFix!!.callback)
+ }
+}
diff --git a/android/editors/testSrc/com/android/tools/idea/editors/GradleImplicitPropertyUsageProviderTest.java b/android/editors/testSrc/com/android/tools/idea/editors/GradleImplicitPropertyUsageProviderTest.java
index a385ec4..1bea436 100644
--- a/android/editors/testSrc/com/android/tools/idea/editors/GradleImplicitPropertyUsageProviderTest.java
+++ b/android/editors/testSrc/com/android/tools/idea/editors/GradleImplicitPropertyUsageProviderTest.java
@@ -55,4 +55,16 @@
}
}
}
+
+ // Regression test for b/298540715
+ public void testResourcesProperties() {
+ VirtualFile vFile = myFixture.createFile("resources.properties", "unqualifiedResLocale=en-US");
+ PsiFile file = PsiManager.getInstance(getProject()).findFile(vFile);
+ assertNotNull(file);
+ PropertiesFile propertiesFile = (PropertiesFile) file;
+ GradleImplicitPropertyUsageProvider provider = new GradleImplicitPropertyUsageProvider();
+ IProperty unqualifiedResLocale = propertiesFile.getProperties().get(0);
+ String name = unqualifiedResLocale.getName();
+ assertTrue(name, provider.isUsed((Property) unqualifiedResLocale));
+ }
}
\ No newline at end of file
diff --git a/android/gradle/testSrc/com/android/tools/idea/gradle/plugin/AgpVersionsTest.kt b/android/gradle/testSrc/com/android/tools/idea/gradle/plugin/AgpVersionsTest.kt
index 7226230..1b098e5 100644
--- a/android/gradle/testSrc/com/android/tools/idea/gradle/plugin/AgpVersionsTest.kt
+++ b/android/gradle/testSrc/com/android/tools/idea/gradle/plugin/AgpVersionsTest.kt
@@ -100,4 +100,27 @@
.inOrder()
}
+
+ @Test
+ fun `test get new project wizard versions with dev available`() {
+ val availableVersions = listOf(
+ "8.1.2",
+ "8.2.0-beta01", "8.2.0-beta02", "8.2.0-dev", // Incompatible dev version is ignored
+ "8.3.0-alpha01", "8.3.0-alpha02", "8.3.0-dev",
+ ).map { AgpVersion.parse(it) }.toSet()
+ assertThat(AgpVersions.getNewProjectWizardVersions(
+ latestKnown = AgpVersion.parse("8.3.0-dev"),
+ availableVersions = availableVersions
+ ).map { it.toString() })
+ .containsExactly("8.3.0-dev", "8.3.0-alpha02", "8.2.0-beta02", "8.1.2")
+ .inOrder()
+
+ assertThat(AgpVersions.getNewProjectWizardVersions(
+ latestKnown = AgpVersion.parse("8.3.0-alpha01"),
+ availableVersions = availableVersions
+ ).map { it.toString() })
+ .containsExactly("8.3.0-dev", "8.3.0-alpha01", "8.1.2")
+ .inOrder()
+ }
+
}
\ No newline at end of file
diff --git a/android/imports/testSrc/com/android/tools/idea/imports/MavenClassRegistryTest.kt b/android/imports/testSrc/com/android/tools/idea/imports/MavenClassRegistryTest.kt
index 2bb8daf..7e4d439 100644
--- a/android/imports/testSrc/com/android/tools/idea/imports/MavenClassRegistryTest.kt
+++ b/android/imports/testSrc/com/android/tools/idea/imports/MavenClassRegistryTest.kt
@@ -21,6 +21,7 @@
import com.android.tools.idea.imports.MavenClassRegistryBase.LibraryImportData
import com.google.common.truth.Truth.assertThat
import com.intellij.openapi.util.Disposer
+import org.jetbrains.kotlin.name.FqName
import org.junit.Assert.assertThrows
import org.junit.Test
import java.nio.charset.StandardCharsets.UTF_8
@@ -52,6 +53,10 @@
},
{
"fqn": "androidx.activity.FakeFunctionKt.FakeFunction"
+ },
+ {
+ "xfqn": "androidx.activity.FakeFunctionKt.FakeFunction",
+ "rcvr": "with.a.Receiver"
}
]
},
@@ -126,7 +131,7 @@
assertThat(mavenClassRegistry.lookup.topLevelFunctionsMap).containsExactlyEntriesIn(
mapOf(
- "PickVisualMediaRequest" to listOf(
+ FunctionSpecifier("PickVisualMediaRequest", null) to listOf(
LibraryImportData(
artifact = "androidx.activity:activity",
importedItemFqName = "androidx.activity.result.PickVisualMediaRequest",
@@ -134,7 +139,7 @@
version = "1.1.0"
)
),
- "FakeFunction" to listOf(
+ FunctionSpecifier("FakeFunction", null) to listOf(
LibraryImportData(
artifact = "androidx.activity:activity",
importedItemFqName = "androidx.activity.FakeFunction",
@@ -148,7 +153,15 @@
version = "1.1.0"
)
),
- "AnnotationFunction" to listOf(
+ FunctionSpecifier("FakeFunction", FqName("with.a.Receiver")) to listOf(
+ LibraryImportData(
+ artifact = "androidx.activity:activity",
+ importedItemFqName = "androidx.activity.FakeFunction",
+ importedItemPackageName = "androidx.activity",
+ version = "1.1.0"
+ ),
+ ),
+ FunctionSpecifier("AnnotationFunction", null) to listOf(
LibraryImportData(
artifact = "androidx.annotation:annotation",
importedItemFqName = "androidx.annotation.AnnotationFunction",
@@ -361,6 +374,10 @@
},
{
"has_no_fqn": "should be ignored"
+ },
+ {
+ "xfqn": "foo.bar.baz.FacadeFileKt.someExtensionFunction",
+ "rcvr": "amazingReceiver"
}
]
}
@@ -373,12 +390,18 @@
assertThat(mavenClassRegistry.lookup.topLevelFunctionsMap).containsExactlyEntriesIn(
mapOf(
- "someFqn" to listOf(LibraryImportData(
+ FunctionSpecifier("someFqn", null) to listOf(LibraryImportData(
artifact = "group3:artifact3",
importedItemFqName = "someFqn",
importedItemPackageName = "",
version = "1"
- ))
+ )),
+ FunctionSpecifier("someExtensionFunction", FqName("amazingReceiver")) to listOf(LibraryImportData(
+ artifact = "group3:artifact3",
+ importedItemFqName = "foo.bar.baz.someExtensionFunction",
+ importedItemPackageName = "foo.bar.baz",
+ version = "1"
+ ))
)
)
}
@@ -541,24 +564,38 @@
@Test
fun kotlinTopLevelFunction_fromJvmQualifiedName() {
+ with(KotlinTopLevelFunction.fromJvmQualifiedName("com.example.FileFacadeKt.foo", "com.example.Receiver")) {
+ assertThat(simpleName).isEqualTo("foo")
+ assertThat(packageName).isEqualTo("com.example")
+ assertThat(kotlinFqName.asString()).isEqualTo("com.example.foo")
+ assertThat(receiverFqName?.asString()).isEqualTo("com.example.Receiver")
+ }
+ }
+
+ @Test
+ fun kotlinTopLevelFunction_fromJvmQualifiedName_withNullReceiver() {
with(KotlinTopLevelFunction.fromJvmQualifiedName("com.example.FileFacadeKt.foo")) {
assertThat(simpleName).isEqualTo("foo")
assertThat(packageName).isEqualTo("com.example")
assertThat(kotlinFqName.asString()).isEqualTo("com.example.foo")
+ assertThat(receiverFqName).isNull()
}
}
@Test
fun kotlinTopLevelFunction_fromJvmQualifiedName_noPackageName() {
- with(KotlinTopLevelFunction.fromJvmQualifiedName("FileFacadeKt.foo")) {
+ with(KotlinTopLevelFunction.fromJvmQualifiedName("FileFacadeKt.foo", "com.example.Receiver")) {
assertThat(simpleName).isEqualTo("foo")
assertThat(packageName).isEqualTo("")
assertThat(kotlinFqName.asString()).isEqualTo("foo")
+ assertThat(receiverFqName?.asString()).isEqualTo("com.example.Receiver")
}
}
@Test
fun kotlinTopLevelFunction_fromJvmQualifiedName_noFacadeFile() {
- assertThrows(IllegalArgumentException::class.java) { KotlinTopLevelFunction.fromJvmQualifiedName("foo") }
+ assertThrows(IllegalArgumentException::class.java) {
+ KotlinTopLevelFunction.fromJvmQualifiedName("foo", "com.example.Receiver")
+ }
}
}
diff --git a/android/jetbrains/testSrc/org/jetbrains/android/exportSignedPackage/ApkStepTest.kt b/android/jetbrains/testSrc/org/jetbrains/android/exportSignedPackage/ApkStepTest.kt
index 1541d7c..03fd67c 100644
--- a/android/jetbrains/testSrc/org/jetbrains/android/exportSignedPackage/ApkStepTest.kt
+++ b/android/jetbrains/testSrc/org/jetbrains/android/exportSignedPackage/ApkStepTest.kt
@@ -15,14 +15,22 @@
*/
package org.jetbrains.android.exportSignedPackage
+import com.android.testutils.MockitoKt.argumentCaptor
import com.android.testutils.MockitoKt.whenever
import com.android.tools.idea.help.AndroidWebHelpProvider
import com.google.common.truth.Truth
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.testFramework.LightPlatformTestCase
+import org.jetbrains.android.exportSignedPackage.ApkStep.RUN_PROGUARD_PROPERTY
+import org.jetbrains.android.facet.AndroidFacet
+import org.jetbrains.android.facet.AndroidFacetConfiguration
+import org.jetbrains.android.facet.AndroidFacetProperties
import org.jetbrains.kotlin.konan.file.File
import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import java.nio.file.Files.isSameFile
+import kotlin.io.path.Path
class ApkStepTest : LightPlatformTestCase() {
private val myWizard = Mockito.mock(ExportSignedPackageWizard::class.java)
@@ -56,4 +64,31 @@
// Clean up properties after tests
properties.setValue(apkPathPropertyName, null)
}
+
+ fun testApkDestinationEndsWhiteSpace() {
+ val apkStep = ApkStep(myWizard)
+ val properties = PropertiesComponent.getInstance(project)
+ val destinationPath = "${this.homePath}${File.separator}Apk "
+ val testFacet = Mockito.mock(AndroidFacet::class.java)
+ whenever(testFacet.module).thenReturn(module)
+ val facetConfiguration = Mockito.mock(AndroidFacetConfiguration::class.java)
+ val facetProperties = AndroidFacetProperties()
+ facetProperties.RUN_PROGUARD = false
+ whenever(facetConfiguration.state).thenReturn(facetProperties)
+ whenever(testFacet.configuration).thenReturn(facetConfiguration)
+ whenever(myWizard.facet).thenReturn(testFacet)
+ properties.setValue(apkStep.getApkPathPropertyName(module.name), destinationPath)
+ properties.setValue(RUN_PROGUARD_PROPERTY, "false")
+
+ val apkDir = java.io.File(destinationPath)
+ if (!apkDir.exists()) {
+ Truth.assertThat(apkDir.mkdirs()).isTrue()
+ }
+
+ apkStep._init(testFacet)
+ val captor = argumentCaptor<String>()
+ apkStep._commit(false)
+ verify(myWizard).setApkPath(captor.capture())
+ Truth.assertThat(isSameFile(Path(captor.value), Path(destinationPath))).isTrue()
+ }
}
\ No newline at end of file
diff --git a/android/jetbrains/testSrc/org/jetbrains/android/exportSignedPackage/GradleSignStepTest.kt b/android/jetbrains/testSrc/org/jetbrains/android/exportSignedPackage/GradleSignStepTest.kt
index 37d61c1..fa8aacd 100644
--- a/android/jetbrains/testSrc/org/jetbrains/android/exportSignedPackage/GradleSignStepTest.kt
+++ b/android/jetbrains/testSrc/org/jetbrains/android/exportSignedPackage/GradleSignStepTest.kt
@@ -15,13 +15,18 @@
*/
package org.jetbrains.android.exportSignedPackage
+import com.android.testutils.MockitoKt.argumentCaptor
import com.android.testutils.MockitoKt.whenever
+import com.android.tools.idea.gradle.project.model.GradleAndroidModel
import com.android.tools.idea.help.AndroidWebHelpProvider
import com.google.common.truth.Truth.assertThat
import com.intellij.ide.util.PropertiesComponent
import com.intellij.testFramework.LightPlatformTestCase
import org.mockito.Mockito
+import org.mockito.Mockito.verify
import java.io.File
+import java.nio.file.Files
+import kotlin.io.path.Path
class GradleSignStepTest : LightPlatformTestCase() {
private var myWizard = Mockito.mock(ExportSignedPackageWizard::class.java)
@@ -41,7 +46,7 @@
val properties = PropertiesComponent.getInstance()
val projectPath = project.baseDir.path
// Set Bundle to confirm it is not the same
- val bundlePath = this.homePath + File.pathSeparator + "Bundle"
+ val bundlePath = this.homePath + File.separator + "Bundle"
properties.setValue(gradleSignStep.getApkPathPropertyName(name, ExportSignedPackageWizard.BUNDLE), bundlePath)
assertThat(gradleSignStep.getInitialPath(properties, name, ExportSignedPackageWizard.APK)).isEqualTo(projectPath)
}
@@ -51,7 +56,7 @@
val properties = PropertiesComponent.getInstance()
val projectPath = project.baseDir.path
// Set Apk to confirm it is not the same
- val apkPath = this.homePath + File.pathSeparator + "Apk"
+ val apkPath = this.homePath + File.separator + "Apk"
properties.setValue(gradleSignStep.getApkPathPropertyName(name, ExportSignedPackageWizard.APK), apkPath)
assertThat(gradleSignStep.getInitialPath(properties, name, ExportSignedPackageWizard.BUNDLE)).isEqualTo(projectPath)
}
@@ -59,8 +64,8 @@
fun testInitialDestinationApkSet() {
val gradleSignStep = GradleSignStep(myWizard)
val properties = PropertiesComponent.getInstance()
- val apkPath = this.homePath + File.pathSeparator + "Apk"
- val bundlePath = this.homePath + File.pathSeparator + "Bundle"
+ val apkPath = this.homePath + File.separator + "Apk"
+ val bundlePath = this.homePath + File.separator + "Bundle"
properties.setValue(gradleSignStep.getApkPathPropertyName(name, ExportSignedPackageWizard.APK), apkPath)
properties.setValue(gradleSignStep.getApkPathPropertyName(name, ExportSignedPackageWizard.BUNDLE), bundlePath)
assertThat(gradleSignStep.getInitialPath(properties, name, ExportSignedPackageWizard.APK)).isEqualTo(apkPath)
@@ -69,10 +74,40 @@
fun testInitialDestinationBundleSet() {
val gradleSignStep = GradleSignStep(myWizard)
val properties = PropertiesComponent.getInstance()
- val apkPath = this.homePath + File.pathSeparator + "Apk"
- val bundlePath = this.homePath + File.pathSeparator + "Bundle"
+ val apkPath = this.homePath + File.separator + "Apk"
+ val bundlePath = this.homePath + File.separator + "Bundle"
properties.setValue(gradleSignStep.getApkPathPropertyName(name, ExportSignedPackageWizard.BUNDLE), bundlePath)
properties.setValue(gradleSignStep.getApkPathPropertyName(name, ExportSignedPackageWizard.APK), apkPath)
assertThat(gradleSignStep.getInitialPath(properties, name, ExportSignedPackageWizard.BUNDLE)).isEqualTo(bundlePath)
}
+
+ fun testApkDestinationEndsWhiteSpace() {
+ verifyDestinationEndsWhiteSpace(ExportSignedPackageWizard.APK)
+ }
+
+ fun testBundleDestinationEndsWhiteSpace() {
+ verifyDestinationEndsWhiteSpace(ExportSignedPackageWizard.BUNDLE)
+ }
+
+ private fun verifyDestinationEndsWhiteSpace(targetType: String) {
+ val gradleSignStep = GradleSignStep(myWizard)
+ val properties = PropertiesComponent.getInstance(project)
+ val destinationPath = "${this.homePath}${File.separator}$targetType "
+ whenever(myWizard.targetType).thenReturn(targetType)
+ val testAndroidModel = Mockito.mock(GradleAndroidModel::class.java)
+ whenever(testAndroidModel.moduleName).thenReturn(name)
+ whenever(testAndroidModel.variantNames).thenReturn(listOf("debug", "release"))
+ properties.setValue(gradleSignStep.getApkPathPropertyName(name, targetType), destinationPath)
+ properties.setList(GradleSignStep.PROPERTY_BUILD_VARIANTS, listOf("release"))
+
+ val apkDir = File(destinationPath)
+ if (!apkDir.exists()) {
+ assertThat(apkDir.mkdirs()).isTrue()
+ }
+ gradleSignStep._init(testAndroidModel)
+ val captor = argumentCaptor<String>()
+ gradleSignStep.commitForNext()
+ verify(myWizard).setApkPath(captor.capture())
+ assertThat(Files.isSameFile(Path(captor.value), Path(destinationPath))).isTrue()
+ }
}
\ No newline at end of file
diff --git a/android/lint_baseline.xml b/android/lint_baseline.xml
index 556f9f8..349301a 100644
--- a/android/lint_baseline.xml
+++ b/android/lint_baseline.xml
@@ -499,6 +499,30 @@
<issue
id="VisibleForTests"
+ message="This class should only be accessed from tests or within package private scope">
+ <location
+ file="src/com/android/tools/idea/rendering/classloading/ClassWriterWithPseudoClassLocator.kt"
+ line="164"/>
+ </issue>
+
+ <issue
+ id="VisibleForTests"
+ message="This class should only be accessed from tests or within package private scope">
+ <location
+ file="src/com/android/tools/idea/editors/literals/LiveLiteralsService.kt"
+ line="135"/>
+ </issue>
+
+ <issue
+ id="VisibleForTests"
+ message="This class should only be accessed from tests or within package private scope">
+ <location
+ file="src/com/android/tools/idea/editors/literals/LiveLiteralsService.kt"
+ line="135"/>
+ </issue>
+
+ <issue
+ id="VisibleForTests"
message="This method should only be accessed from tests or within package private scope">
<location
file="src/com/android/tools/idea/editors/literals/LiveLiteralsService.kt"
@@ -563,6 +587,14 @@
<issue
id="VisibleForTests"
+ message="This class should only be accessed from tests or within package private scope">
+ <location
+ file="src/com/android/tools/idea/editors/literals/actions/LiveLiteralsStatusAction.kt"
+ line="53"/>
+ </issue>
+
+ <issue
+ id="VisibleForTests"
message="This method should only be accessed from tests or within package private scope">
<location
file="src/com/android/tools/idea/editors/literals/actions/LiveLiteralsStatusAction.kt"
diff --git a/android/res/testSrc/com/android/tools/idea/res/LightClassesTests.kt b/android/res/testSrc/com/android/tools/idea/res/LightClassesTests.kt
index ad46e44..21b06fe 100644
--- a/android/res/testSrc/com/android/tools/idea/res/LightClassesTests.kt
+++ b/android/res/testSrc/com/android/tools/idea/res/LightClassesTests.kt
@@ -36,6 +36,7 @@
import com.android.tools.idea.testing.AndroidModuleModelBuilder
import com.android.tools.idea.testing.AndroidProjectRule
import com.android.tools.idea.testing.AndroidProjectStubBuilder
+import com.android.tools.idea.testing.JavaLibraryDependency
import com.android.tools.idea.testing.JavaModuleModelBuilder.Companion.rootModuleBuilder
import com.android.tools.idea.testing.buildAgpProjectFlagsStub
import com.android.tools.idea.testing.caret
@@ -49,6 +50,7 @@
import com.android.tools.idea.testing.updatePrimaryManifest
import com.android.tools.idea.testing.waitForResourceRepositoryUpdates
import com.android.tools.idea.util.androidFacet
+import com.android.tools.tests.AdtTestKotlinArtifacts
import com.android.utils.executeWithRetries
import com.google.common.truth.Truth.assertThat
import com.intellij.codeInsight.lookup.LookupElement
@@ -715,6 +717,10 @@
.withAndroidModuleDependencyList { _ ->
listOf(AndroidModuleDependency(":mylib", "debug"))
}
+ // TODO(b/300170256): Remove this once 2023.3 merges and we no longer need kotlin-stdlib for every Kotlin test.
+ .withJavaLibraryDependencyList {
+ listOf(JavaLibraryDependency.forJar(AdtTestKotlinArtifacts.kotlinStdlib))
+ }
.withAgpProjectFlags { getAgpProjectFlags(this) },
)
diff --git a/android/resources/wizardData/distributions.json b/android/resources/wizardData/distributions.json
index 966a3e5..3acdecf 100644
--- a/android/resources/wizardData/distributions.json
+++ b/android/resources/wizardData/distributions.json
@@ -3,7 +3,7 @@
"name": "KitKat",
"version": "4.4",
"apiLevel": 19,
- "distributionPercentage": 0.005,
+ "distributionPercentage": 0.004,
"url": "https://developer.android.com/about/versions/android-4.4.html",
"descriptionBlocks": [
{
@@ -40,15 +40,15 @@
},
{
"title": "",
- "body": "Last updated: May 30, 2023"
+ "body": "Last updated: September 1, 2023"
}
]
},
{
"name": "Lollipop",
- "version": "5.0",
+ "version": "5",
"apiLevel": 21,
- "distributionPercentage": 0.003,
+ "distributionPercentage": 0.002,
"url": "https://developer.android.com/about/versions/android-5.0.html",
"descriptionBlocks": [
{
@@ -105,7 +105,7 @@
},
{
"title": "",
- "body": "Last updated: May 30, 2023"
+ "body": "Last updated: September 1, 2023"
}
]
},
@@ -113,7 +113,7 @@
"name": "Lollipop",
"version": "5.1",
"apiLevel": 22,
- "distributionPercentage": 0.015,
+ "distributionPercentage": 0.013,
"url": "https://developer.android.com/about/versions/android-5.1.html",
"descriptionBlocks": [
{
@@ -126,15 +126,15 @@
},
{
"title": "",
- "body": "Last updated: May 30, 2023"
+ "body": "Last updated: September 1, 2023"
}
]
},
{
"name": "Marshmallow",
- "version": "6.0",
+ "version": "6",
"apiLevel": 23,
- "distributionPercentage": 0.023,
+ "distributionPercentage": 0.02,
"url": "https://developer.android.com/about/versions/marshmallow/android-6.0.html",
"descriptionBlocks": [
{
@@ -167,15 +167,15 @@
},
{
"title": "",
- "body": "Last updated: May 30, 2023"
+ "body": "Last updated: September 1, 2023"
}
]
},
{
"name": "Nougat",
- "version": "7.0",
+ "version": "7",
"apiLevel": 24,
- "distributionPercentage": 0.015,
+ "distributionPercentage": 0.013,
"url": "https://developer.android.com/about/versions/nougat/android-7.0.html",
"descriptionBlocks": [
{
@@ -224,7 +224,7 @@
},
{
"title": "",
- "body": "Last updated: May 30, 2023"
+ "body": "Last updated: September 1, 2023"
}
]
},
@@ -232,7 +232,7 @@
"name": "Nougat",
"version": "7.1",
"apiLevel": 25,
- "distributionPercentage": 0.015,
+ "distributionPercentage": 0.014,
"url": "https://developer.android.com/about/versions/nougat/android-7.1.html",
"descriptionBlocks": [
{
@@ -261,15 +261,15 @@
},
{
"title": "",
- "body": "Last updated: May 30, 2023"
+ "body": "Last updated: September 1, 2023"
}
]
},
{
"name": "Oreo",
- "version": "8.0",
+ "version": "8",
"apiLevel": 26,
- "distributionPercentage": 0.022,
+ "distributionPercentage": 0.02,
"url": "https://developer.android.com/about/versions/oreo/android-8.0",
"descriptionBlocks": [
{
@@ -298,7 +298,7 @@
},
{
"title": "",
- "body": "Last updated: May 30, 2023"
+ "body": "Last updated: September 1, 2023"
}
]
},
@@ -306,7 +306,7 @@
"name": "Oreo",
"version": "8.1",
"apiLevel": 27,
- "distributionPercentage": 0.061,
+ "distributionPercentage": 0.055,
"url": "https://developer.android.com/about/versions/oreo/android-8.1",
"descriptionBlocks": [
{
@@ -327,15 +327,15 @@
},
{
"title": "",
- "body": "Last updated: May 30, 2023"
+ "body": "Last updated: September 1, 2023"
}
]
},
{
"name": "Pie",
- "version": "9.0",
+ "version": "9",
"apiLevel": 28,
- "distributionPercentage": 0.119,
+ "distributionPercentage": 0.108,
"url": "https://developer.android.com/about/versions/pie/android-9.0",
"descriptionBlocks": [
{
@@ -360,15 +360,15 @@
},
{
"title": "",
- "body": "Last updated: May 30, 2023"
+ "body": "Last updated: September 1, 2023"
}
]
},
{
"name": "Q",
- "version": "10.0",
+ "version": "10",
"apiLevel": 29,
- "distributionPercentage": 0.178,
+ "distributionPercentage": 0.166,
"url": "https://developer.android.com/about/versions/10",
"descriptionBlocks": [
{
@@ -389,15 +389,15 @@
},
{
"title": "",
- "body": "Last updated: May 30, 2023"
+ "body": "Last updated: September 1, 2023"
}
]
},
{
"name": "R",
- "version": "11.0",
+ "version": "11",
"apiLevel": 30,
- "distributionPercentage": 0.231,
+ "distributionPercentage": 0.22,
"url": "https://developer.android.com/about/versions/11",
"descriptionBlocks": [
{
@@ -414,15 +414,15 @@
},
{
"title": "",
- "body": "Last updated: May 30, 2023"
+ "body": "Last updated: September 1, 2023"
}
]
},
{
"name": "S",
- "version": "12.0",
+ "version": "12",
"apiLevel": 31,
- "distributionPercentage": 0.163,
+ "distributionPercentage": 0.159,
"url": "https://developer.android.com/about/versions/12",
"descriptionBlocks": [
{
@@ -443,15 +443,15 @@
},
{
"title": "",
- "body": "Last updated: May 30, 2023"
+ "body": "Last updated: September 1, 2023"
}
]
},
{
"name": "T",
- "version": "13.0",
+ "version": "13",
"apiLevel": 33,
- "distributionPercentage": 0.147,
+ "distributionPercentage": 0.204,
"url": "https://developer.android.com/about/versions/13",
"descriptionBlocks": [
{
@@ -468,7 +468,7 @@
},
{
"title": "",
- "body": "Last updated: May 30, 2023"
+ "body": "Last updated: September 1, 2023"
}
]
}
diff --git a/android/run/testSrc/com/android/tools/idea/run/AndroidActivityRunLineMarkerContributorTest.kt b/android/run/testSrc/com/android/tools/idea/run/AndroidActivityRunLineMarkerContributorTest.kt
new file mode 100644
index 0000000..870ceef
--- /dev/null
+++ b/android/run/testSrc/com/android/tools/idea/run/AndroidActivityRunLineMarkerContributorTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 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.run
+
+import com.android.tools.idea.testing.AndroidProjectRule
+import com.android.tools.idea.testing.onEdt
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.testFramework.RunsInEdt
+import junit.framework.TestCase.assertNotNull
+import junit.framework.TestCase.assertNull
+import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class AndroidActivityRunLineMarkerContributorTest {
+
+ @get:Rule val projectRule = AndroidProjectRule.inMemory().onEdt()
+
+ @Before
+ fun setUp() {
+ // Simulate the existence of android.app.Activity
+ projectRule.fixture.addFileToProject(
+ "src/android/app/Activity.java",
+ """
+ package android.app
+
+ class Activity {}
+ """
+ .trimIndent()
+ )
+ }
+
+ @Test
+ @RunsInEdt
+ fun identifyKotlinActivity() {
+ val activityFile =
+ projectRule.fixture.addFileToProject(
+ "src/com/example/myapplication/MyActivity.kt",
+ """
+ package com.example.myapplication
+
+ import android.app.Activity
+
+ /**
+ */
+ class MyActivity : Activity() {
+ }
+ """
+ .trimIndent()
+ )
+
+ val contributor = AndroidActivityRunLineMarkerContributor()
+ assertNotNull(contributor.getInfo(activityFile.findElementByText("class")))
+ assertNull(
+ contributor.getInfo(activityFile.findElementByText("package com.example.myapplication"))
+ )
+ }
+
+ @Test
+ @RunsInEdt
+ fun identifyJavaActivity() {
+ val activityFile =
+ projectRule.fixture.addFileToProject(
+ "src/com/example/myapplication/MyActivity.java",
+ """
+ package com.example.myapplication;
+
+ import android.app.Activity;
+
+ /**
+ */
+ class MyActivity extends Activity {
+ }
+ """
+ .trimIndent()
+ )
+
+ val contributor = AndroidActivityRunLineMarkerContributor()
+ assertNotNull(contributor.getInfo(activityFile.findElementByText("class")))
+ assertNull(
+ contributor.getInfo(activityFile.findElementByText("package com.example.myapplication;"))
+ )
+ }
+}
+
+fun PsiFile.findElementByText(text: String): PsiElement =
+ findDescendantOfType { it.node.text == text }!!
diff --git a/android/run/testSrc/com/android/tools/idea/run/deployment/SelectMultipleDevicesDialogTest.java b/android/run/testSrc/com/android/tools/idea/run/deployment/SelectMultipleDevicesDialogTest.java
index 6aad8c0..0a81eac 100644
--- a/android/run/testSrc/com/android/tools/idea/run/deployment/SelectMultipleDevicesDialogTest.java
+++ b/android/run/testSrc/com/android/tools/idea/run/deployment/SelectMultipleDevicesDialogTest.java
@@ -156,7 +156,8 @@
ApplicationManager.getApplication().invokeAndWait(() -> myDialog.getOKAction().actionPerformed(null));
// Assert
- Mockito.verify(service).setTargetsSelectedWithDialog(Collections.singleton(new QuickBootTarget(Keys.PIXEL_4_API_30)));
+ QuickBootTarget target = new QuickBootTarget(Keys.PIXEL_4_API_30);
+ Mockito.verify(service).setTargetsSelectedWithDialog(Collections.singleton(target), Collections.singletonList(target));
}
@Test
diff --git a/android/run/testSrc/com/android/tools/idea/run/deployment/TargetsForWritingSupplierTest.java b/android/run/testSrc/com/android/tools/idea/run/deployment/TargetsForWritingSupplierTest.java
index cc547af..df9a2d4 100644
--- a/android/run/testSrc/com/android/tools/idea/run/deployment/TargetsForWritingSupplierTest.java
+++ b/android/run/testSrc/com/android/tools/idea/run/deployment/TargetsForWritingSupplierTest.java
@@ -15,8 +15,10 @@
*/
package com.android.tools.idea.run.deployment;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import com.google.common.collect.ImmutableList;
import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -91,4 +93,30 @@
assertEquals(Optional.of(newTarget), runningDeviceTarget);
assertEquals(Optional.of(oldTarget), target);
}
+
+ @Test
+ public void runningDeviceHasOldTarget() {
+ Target oldTarget = new BootWithSnapshotTarget(Keys.PIXEL_4_API_30, Keys.PIXEL_4_API_30_SNAPSHOT_2);
+ Target newTarget = new RunningDeviceTarget(Keys.PIXEL_4_API_30);
+ Target defaultLaunchTarget = new QuickBootTarget(Keys.PIXEL_4_API_30);
+
+ TargetsForWritingSupplier supplier =
+ new TargetsForWritingSupplier(ImmutableList.of(oldTarget), ImmutableList.of(newTarget), ImmutableList.of(defaultLaunchTarget));
+
+ // Default launch target is not used because we already had a launchable target
+ assertThat(supplier.getDialogRunningDeviceTargets()).containsExactly(newTarget);
+ assertThat(supplier.getDialogTargets()).containsExactly(oldTarget);
+ }
+
+ @Test
+ public void runningDeviceHasNoOldTarget() {
+ Target newTarget = new RunningDeviceTarget(Keys.PIXEL_4_API_30);
+ Target defaultLaunchTarget = new QuickBootTarget(Keys.PIXEL_4_API_30);
+
+ TargetsForWritingSupplier supplier =
+ new TargetsForWritingSupplier(ImmutableList.of(), ImmutableList.of(newTarget), ImmutableList.of(defaultLaunchTarget));
+
+ assertThat(supplier.getDialogRunningDeviceTargets()).containsExactly(newTarget);
+ assertThat(supplier.getDialogTargets()).containsExactly(defaultLaunchTarget);
+ }
}
diff --git a/android/run/testSrc/com/android/tools/idea/run/deployment/liveedit/ComposableCompileTest.kt b/android/run/testSrc/com/android/tools/idea/run/deployment/liveedit/ComposableCompileTest.kt
index 43293ed..e7875a7 100644
--- a/android/run/testSrc/com/android/tools/idea/run/deployment/liveedit/ComposableCompileTest.kt
+++ b/android/run/testSrc/com/android/tools/idea/run/deployment/liveedit/ComposableCompileTest.kt
@@ -285,7 +285,6 @@
""".trimIndent()
// Regression test for invalid incremental analysis. See b/295257198.
- @Ignore("b/295257198")
@Test
fun incrementalAnalysisFunctionBodyTest() {
val fileName = "Test.kt"
@@ -311,7 +310,6 @@
}
// Regression test for invalid incremental analysis. See b/295257198.
- @Ignore("b/295257198")
@Test
fun incrementalAnalysisFunctionExpressionBodyTest() {
val fileName = "Test.kt"
diff --git a/android/run/testSrc/com/android/tools/idea/run/editor/AndroidRunConfigurationEditorForCloudTestMatrixTest.java b/android/run/testSrc/com/android/tools/idea/run/editor/AndroidRunConfigurationEditorForCloudTestMatrixTest.java
index 05e194a..1a044b3 100644
--- a/android/run/testSrc/com/android/tools/idea/run/editor/AndroidRunConfigurationEditorForCloudTestMatrixTest.java
+++ b/android/run/testSrc/com/android/tools/idea/run/editor/AndroidRunConfigurationEditorForCloudTestMatrixTest.java
@@ -17,15 +17,10 @@
import static org.junit.Assert.assertEquals;
-import com.android.tools.idea.execution.common.debug.AndroidDebuggerContext;
-import com.android.tools.idea.run.ConfigurationSpecificEditor;
import com.android.tools.idea.run.TargetSelectionMode;
-import com.android.tools.idea.run.deployment.DeviceAndSnapshotComboBoxTargetProvider;
import com.android.tools.idea.testartifacts.instrumented.AndroidTestRunConfiguration;
import com.android.tools.idea.testing.AndroidProjectRule;
-import java.util.Arrays;
import java.util.List;
-import javax.swing.JLabel;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
@@ -33,41 +28,24 @@
public final class AndroidRunConfigurationEditorForCloudTestMatrixTest {
@Rule
public final AndroidProjectRule myRule = AndroidProjectRule.inMemory();
+ private AndroidRunConfigurationEditor<AndroidTestRunConfiguration> androidRunConfigurationEditor;
@Test
public void applyEditorTo() {
// Arrange
- AndroidDebuggerContext androidDebuggerContext = Mockito.mock(AndroidDebuggerContext.class);
DeployTargetProvider provider = new CloudTestMatrixTargetProvider();
- List<DeployTargetProvider> providers = Arrays.asList(new DeviceAndSnapshotComboBoxTargetProvider(), provider);
+ AndroidRunConfigurationEditorTest runConfigEditorTest = new AndroidRunConfigurationEditorTest();
+ List<DeployTargetProvider> providers = runConfigEditorTest.getTargetProviders(provider);
- AndroidTestRunConfiguration configuration1 = Mockito.mock(AndroidTestRunConfiguration.class);
- Mockito.when(configuration1.getAndroidDebuggerContext()).thenReturn(androidDebuggerContext);
- Mockito.when(configuration1.getApplicableDeployTargetProviders()).thenReturn(providers);
- Mockito.when(configuration1.getProfilerState()).thenReturn(new ProfilerState());
-
- @SuppressWarnings("unchecked")
- ConfigurationSpecificEditor<AndroidTestRunConfiguration> configurationSpecificEditor = Mockito.mock(ConfigurationSpecificEditor.class);
- Mockito.when(configurationSpecificEditor.getComponent()).thenReturn(new JLabel());
-
- AndroidRunConfigurationEditor<AndroidTestRunConfiguration> androidRunConfigurationEditor =
- new AndroidRunConfigurationEditor<>(
- myRule.getProject(),
- facet -> false,
- configuration1,
- true,
- false,
- moduleSelector -> configurationSpecificEditor);
-
+ androidRunConfigurationEditor = runConfigEditorTest.getAndroidRunConfigurationEditor(provider, myRule.getProject());
DeployTargetContext deployTargetContext = new DeployTargetContext(providers);
-
- AndroidTestRunConfiguration configuration2 = Mockito.mock(AndroidTestRunConfiguration.class);
- Mockito.when(configuration2.getDeployTargetContext()).thenReturn(deployTargetContext);
- Mockito.when(configuration2.getProfilerState()).thenReturn(new ProfilerState());
+ AndroidTestRunConfiguration configuration = Mockito.mock(AndroidTestRunConfiguration.class);
+ Mockito.when(configuration.getDeployTargetContext()).thenReturn(deployTargetContext);
+ Mockito.when(configuration.getProfilerState()).thenReturn(new ProfilerState());
// Act
androidRunConfigurationEditor.getDeploymentTargetOptions().getTargetComboBox().setSelectedItem(provider);
- androidRunConfigurationEditor.applyEditorTo(configuration2);
+ androidRunConfigurationEditor.applyEditorTo(configuration);
// Assert
assertEquals(TargetSelectionMode.FIREBASE_DEVICE_MATRIX, deployTargetContext.getTargetSelectionMode());
diff --git a/android/run/testSrc/com/android/tools/idea/run/editor/AndroidRunConfigurationEditorTest.kt b/android/run/testSrc/com/android/tools/idea/run/editor/AndroidRunConfigurationEditorTest.kt
index 859b97f..bf90191 100644
--- a/android/run/testSrc/com/android/tools/idea/run/editor/AndroidRunConfigurationEditorTest.kt
+++ b/android/run/testSrc/com/android/tools/idea/run/editor/AndroidRunConfigurationEditorTest.kt
@@ -15,6 +15,8 @@
*/
package com.android.tools.idea.run.editor
+import com.android.tools.idea.execution.common.debug.AndroidDebuggerContext
+import com.android.tools.idea.flags.StudioFlags
import com.android.tools.idea.gradle.model.IdeAndroidProjectType.PROJECT_TYPE_DYNAMIC_FEATURE
import com.android.tools.idea.gradle.model.IdeAndroidProjectType.PROJECT_TYPE_LIBRARY
import com.android.tools.idea.gradle.model.IdeAndroidProjectType.PROJECT_TYPE_TEST
@@ -23,6 +25,7 @@
import com.android.tools.idea.projectsystem.getMainModule
import com.android.tools.idea.run.AndroidRunConfiguration
import com.android.tools.idea.run.AndroidRunConfigurationType
+import com.android.tools.idea.run.ConfigurationSpecificEditor
import com.android.tools.idea.run.configuration.AndroidComplicationConfiguration
import com.android.tools.idea.run.configuration.AndroidComplicationConfigurationType
import com.android.tools.idea.run.configuration.AndroidTileConfigurationType
@@ -30,6 +33,7 @@
import com.android.tools.idea.run.configuration.AndroidWearConfiguration
import com.android.tools.idea.run.configuration.editors.AndroidComplicationConfigurationEditor
import com.android.tools.idea.run.configuration.editors.AndroidWearConfigurationEditor
+import com.android.tools.idea.run.deployment.DeviceAndSnapshotComboBoxTargetProvider
import com.android.tools.idea.testartifacts.instrumented.AndroidTestRunConfiguration
import com.android.tools.idea.testartifacts.instrumented.AndroidTestRunConfigurationType
import com.android.tools.idea.testing.AndroidModuleModelBuilder
@@ -41,7 +45,6 @@
import com.android.tools.idea.testing.onEdt
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
-import com.intellij.execution.RunConfigurationConverter
import com.intellij.execution.configurations.ConfigurationType
import com.intellij.execution.configurations.ConfigurationTypeUtil.findConfigurationType
import com.intellij.execution.configurations.RunConfiguration
@@ -49,9 +52,13 @@
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.options.SettingsEditor
+import com.intellij.openapi.project.Project
import com.intellij.testFramework.RunsInEdt
+import org.junit.After
import org.junit.Rule
import org.junit.Test
+import org.mockito.Mockito
+import javax.swing.JLabel
@RunsInEdt
class AndroidRunConfigurationEditorTest {
@@ -69,6 +76,12 @@
@get:Rule
val expect: Expect = Expect.createAndEnableStackTrace()
+ @After
+ fun tearDown() {
+ // Need to clear any override we use inside tests here.
+ StudioFlags.PROFILER_TASK_BASED_UX.clearOverride()
+ }
+
@Test
fun `android run configuration`() {
val runConfiguration = createConfiguration<AndroidRunConfiguration>(AndroidRunConfigurationType::class.java)
@@ -123,6 +136,20 @@
)
}
+ @Test
+ fun testProfilingTabAvailable() {
+ StudioFlags.PROFILER_TASK_BASED_UX.override(false)
+ // Profiling tab available
+ assertThat(getProfilingTabIndex()).isNotEqualTo(-1)
+ }
+
+ @Test
+ fun testProfilingTabNotAvailable() {
+ StudioFlags.PROFILER_TASK_BASED_UX.override(true)
+ // Profiling tab not available
+ assertThat(getProfilingTabIndex()).isEqualTo(-1)
+ }
+
private inline fun <reified R> createConfiguration(configurationTypeClass: Class<out ConfigurationType>): R {
val configurationType = findConfigurationType(configurationTypeClass)
val configurationFactory = configurationType.configurationFactories.single()
@@ -138,4 +165,38 @@
): List<Module> {
return ModuleManager.getInstance(projectRule.project).modules.filter { selector(configurationEditor as E).isModuleAccepted(it) }
}
+
+ private fun getProfilingTabIndex(): Int {
+ val provider: DeployTargetProvider = CloudTestMatrixTargetProvider()
+ var androidRunConfigurationEditor = getAndroidRunConfigurationEditor(provider, projectRule.project)
+ return androidRunConfigurationEditor.myTabbedPane.indexOfTab("Profiling")
+ }
+
+ fun getTargetProviders(provider: DeployTargetProvider): List<DeployTargetProvider> {
+ return listOf(DeviceAndSnapshotComboBoxTargetProvider(), provider)
+ }
+
+ fun getAndroidRunConfigurationEditor(provider: DeployTargetProvider,
+ project: Project): AndroidRunConfigurationEditor<AndroidTestRunConfiguration> {
+ val androidDebuggerContext = Mockito.mock(AndroidDebuggerContext::class.java)
+ val providers: List<DeployTargetProvider> = getTargetProviders(provider)
+ val configuration = Mockito.mock(AndroidTestRunConfiguration::class.java)
+ Mockito.`when`(configuration.androidDebuggerContext).thenReturn(androidDebuggerContext)
+ Mockito.`when`(configuration.applicableDeployTargetProviders).thenReturn(providers)
+ Mockito.`when`(configuration.profilerState).thenReturn(ProfilerState())
+
+ @Suppress("unchecked_cast")
+ val configurationSpecificEditor =
+ Mockito.mock(ConfigurationSpecificEditor::class.java) as ConfigurationSpecificEditor<AndroidTestRunConfiguration>
+
+ Mockito.`when`(configurationSpecificEditor.component).thenReturn(JLabel())
+
+ return AndroidRunConfigurationEditor(
+ project,
+ { false },
+ configuration,
+ true,
+ false,
+ { configurationSpecificEditor })
+ }
}
\ No newline at end of file
diff --git a/android/src/META-INF/android-plugin-androidstudio.xml b/android/src/META-INF/android-plugin-androidstudio.xml
index 3a3d15c..b46e020 100755
--- a/android/src/META-INF/android-plugin-androidstudio.xml
+++ b/android/src/META-INF/android-plugin-androidstudio.xml
@@ -156,9 +156,6 @@
<extensions defaultExtensionNs="com.intellij">
<defaultProjectTypeProvider type="Android"/>
<targetElementEvaluator language="XML" implementationClass="org.jetbrains.android.dom.AndroidXmlTargetElementEvaluatorEx" order="first"/>
- <applicationService serviceInterface="org.jetbrains.plugins.gradle.service.GradleInstallationManager"
- serviceImplementation="com.android.tools.idea.gradle.project.AndroidStudioGradleInstallationManager"
- overrides="true"/>
<!-- Unregister DefaultJdkConfigurator b/112481251 -->
<applicationService serviceInterface="com.intellij.openapi.projectRoots.DefaultJdkConfigurator"
@@ -167,10 +164,6 @@
headlessImplementation="com.android.tools.idea.sdk.StudioJdkConfigurator"
/>
- <projectService serviceInterface="org.jetbrains.plugins.gradle.settings.GradleSettings"
- serviceImplementation="com.android.tools.idea.gradle.project.AndroidStudioGradleSettings"
- overrides="true"/>
-
<welcomeFrameProvider implementation="com.android.tools.idea.welcome.wizard.FirstRunWizardFrameProvider" order="FIRST"/>
<localInspection groupPath="Java" language="JAVA" suppressId="deprecation" shortName="Deprecation" displayName="Deprecated API usage"
diff --git a/android/src/META-INF/android-plugin.xml b/android/src/META-INF/android-plugin.xml
index aa8277d..eb18c7b 100644
--- a/android/src/META-INF/android-plugin.xml
+++ b/android/src/META-INF/android-plugin.xml
@@ -354,6 +354,8 @@
<runConfigurationProducer implementation="com.android.tools.idea.run.configuration.AndroidComplicationRunConfigurationProducer"/>
<runLineMarkerContributor language="JAVA" implementationClass="com.android.tools.idea.run.configuration.AndroidWearRunMarkerContributor"/>
<runLineMarkerContributor language="kotlin" implementationClass="com.android.tools.idea.run.configuration.AndroidWearRunMarkerContributor"/>
+ <runLineMarkerContributor language="JAVA" implementationClass="com.android.tools.idea.run.AndroidActivityRunLineMarkerContributor"/>
+ <runLineMarkerContributor language="kotlin" implementationClass="com.android.tools.idea.run.AndroidActivityRunLineMarkerContributor"/>
<configurationType implementation="com.android.tools.idea.run.AndroidRunConfigurationType"/>
<configurationType implementation="com.android.tools.idea.testartifacts.instrumented.AndroidTestRunConfigurationType"/>
<configurationType implementation="com.android.tools.idea.run.configuration.AndroidWatchFaceConfigurationType"/>
diff --git a/android/src/com/android/tools/idea/actions/AndroidOpenFileAction.java b/android/src/com/android/tools/idea/actions/AndroidOpenFileAction.java
index 6241a72..03a84ee 100644
--- a/android/src/com/android/tools/idea/actions/AndroidOpenFileAction.java
+++ b/android/src/com/android/tools/idea/actions/AndroidOpenFileAction.java
@@ -31,6 +31,7 @@
import com.intellij.ide.IdeBundle;
import com.intellij.ide.actions.OpenProjectFileChooserDescriptor;
import com.intellij.openapi.Disposable;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.PathChooserDialog;
@@ -66,6 +67,12 @@
}
@Override
+ @NotNull
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
+ @Override
public void update(@NotNull AnActionEvent e) {
if (NewWelcomeScreen.isNewWelcomeScreen(e)) {
e.getPresentation().setIcon(AllIcons.Welcome.Open);
diff --git a/android/src/com/android/tools/idea/actions/ExportProjectZip.java b/android/src/com/android/tools/idea/actions/ExportProjectZip.java
index 8d1a474..093e4c8 100644
--- a/android/src/com/android/tools/idea/actions/ExportProjectZip.java
+++ b/android/src/com/android/tools/idea/actions/ExportProjectZip.java
@@ -19,6 +19,7 @@
import com.android.tools.idea.gradle.project.GradleProjectInfo;
import com.android.tools.idea.gradle.util.GradleUtil;
import com.google.common.annotations.VisibleForTesting;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
@@ -56,6 +57,12 @@
*/
public class ExportProjectZip extends AnAction implements DumbAware {
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@Override
public void update(@NotNull final AnActionEvent e) {
Project project = e.getProject();
diff --git a/android/src/com/android/tools/idea/actions/MeetAndroidStudioHelpAction.java b/android/src/com/android/tools/idea/actions/MeetAndroidStudioHelpAction.java
index f60707b..62e218a 100644
--- a/android/src/com/android/tools/idea/actions/MeetAndroidStudioHelpAction.java
+++ b/android/src/com/android/tools/idea/actions/MeetAndroidStudioHelpAction.java
@@ -18,6 +18,7 @@
import com.intellij.icons.AllIcons;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.actionSystem.ActionPlaces;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
@@ -29,6 +30,12 @@
super("Android Studio Help", "Help", AllIcons.Actions.Help);
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
if (e.getPlace().equals(ActionPlaces.MAIN_MENU)) {
diff --git a/android/src/com/android/tools/idea/actions/SendFeedbackAction.java b/android/src/com/android/tools/idea/actions/SendFeedbackAction.java
index ac2eab2..f6908bb 100644
--- a/android/src/com/android/tools/idea/actions/SendFeedbackAction.java
+++ b/android/src/com/android/tools/idea/actions/SendFeedbackAction.java
@@ -19,6 +19,7 @@
import com.android.tools.idea.flags.StudioFlags;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManagerCore;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ex.ApplicationInfoEx;
@@ -143,6 +144,11 @@
}
}
+ @Override
+ public @NotNull ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
private static String getNewFeedbackUrl() {
String instructions = """
####################################################
diff --git a/android/src/com/android/tools/idea/apk/ImportApkAction.java b/android/src/com/android/tools/idea/apk/ImportApkAction.java
index 2fb4b3d..3571c9c 100644
--- a/android/src/com/android/tools/idea/apk/ImportApkAction.java
+++ b/android/src/com/android/tools/idea/apk/ImportApkAction.java
@@ -21,6 +21,7 @@
import com.intellij.icons.AllIcons;
import com.intellij.ide.RecentProjectsManager;
import com.intellij.ide.util.PropertiesComponent;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.externalSystem.ExternalSystemManager;
import com.intellij.openapi.fileChooser.FileChooserDialog;
@@ -97,6 +98,11 @@
e.getPresentation().setEnabledAndVisible(enabled);
}
+ @Override
+ public @NotNull ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@VisibleForTesting
static class FileChooserDialogFactory {
@NotNull
diff --git a/android/src/com/android/tools/idea/avdmanager/skincombobox/DefaultSkin.java b/android/src/com/android/tools/idea/avdmanager/skincombobox/DefaultSkin.java
index 1ebdee8..08b38d8 100644
--- a/android/src/com/android/tools/idea/avdmanager/skincombobox/DefaultSkin.java
+++ b/android/src/com/android/tools/idea/avdmanager/skincombobox/DefaultSkin.java
@@ -20,6 +20,18 @@
@SuppressWarnings("unused")
record DefaultSkin(@NotNull Path path) implements Skin {
+ /**
+ * Resolve collisions in favor of the parameter. If the user picks a platform skin or a system image skin with the file chooser before the
+ * asynchronous skin collection finishes it will be represented by a DefaultSkin. We resolve collisions in this way because the combo box
+ * renders PlatformSkins and SystemImageSkins with more information.
+ */
+ @NotNull
+ @Override
+ public Skin merge(@NotNull Skin skin) {
+ assert skin.path().equals(path) : skin;
+ return skin;
+ }
+
@NotNull
@Override
public String toString() {
diff --git a/android/src/com/android/tools/idea/avdmanager/skincombobox/NoSkin.java b/android/src/com/android/tools/idea/avdmanager/skincombobox/NoSkin.java
index cc5899b..f7aa921 100644
--- a/android/src/com/android/tools/idea/avdmanager/skincombobox/NoSkin.java
+++ b/android/src/com/android/tools/idea/avdmanager/skincombobox/NoSkin.java
@@ -27,6 +27,12 @@
@NotNull
@Override
+ public Skin merge(@NotNull Skin skin) {
+ throw new UnsupportedOperationException();
+ }
+
+ @NotNull
+ @Override
public Path path() {
return Path.of(SkinUtils.NO_SKIN);
}
diff --git a/android/src/com/android/tools/idea/avdmanager/skincombobox/PlatformSkin.java b/android/src/com/android/tools/idea/avdmanager/skincombobox/PlatformSkin.java
index 818f1f7..42b7139 100644
--- a/android/src/com/android/tools/idea/avdmanager/skincombobox/PlatformSkin.java
+++ b/android/src/com/android/tools/idea/avdmanager/skincombobox/PlatformSkin.java
@@ -22,6 +22,18 @@
@SuppressWarnings("unused")
record PlatformSkin(@NotNull Path path, @NotNull AndroidVersion version) implements Skin {
+ /**
+ * Resolve collisions in favor of platform skins. System images that don't have their own skins refer to the platform skins for their
+ * platform version. This resolution will drop those skins. It also handles the user manually picking a platform skin before the
+ * asynchronous collection is done.
+ */
+ @NotNull
+ @Override
+ public Skin merge(@NotNull Skin skin) {
+ assert skin.path().equals(path) : skin;
+ return this;
+ }
+
@NotNull
@Override
public String toString() {
diff --git a/android/src/com/android/tools/idea/avdmanager/skincombobox/Skin.java b/android/src/com/android/tools/idea/avdmanager/skincombobox/Skin.java
index 2ea147d..539cf01 100644
--- a/android/src/com/android/tools/idea/avdmanager/skincombobox/Skin.java
+++ b/android/src/com/android/tools/idea/avdmanager/skincombobox/Skin.java
@@ -27,6 +27,9 @@
.thenComparing(Object::toString, Collator.getInstance(ULocale.ROOT));
@NotNull
+ Skin merge(@NotNull Skin skin);
+
+ @NotNull
Path path();
@Override
diff --git a/android/src/com/android/tools/idea/avdmanager/skincombobox/SkinComboBoxModel.java b/android/src/com/android/tools/idea/avdmanager/skincombobox/SkinComboBoxModel.java
new file mode 100644
index 0000000..7f29c25
--- /dev/null
+++ b/android/src/com/android/tools/idea/avdmanager/skincombobox/SkinComboBoxModel.java
@@ -0,0 +1,122 @@
+/*
+ * 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.avdmanager.skincombobox;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.util.concurrency.AppExecutorUtil;
+import com.intellij.util.concurrency.EdtExecutorService;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.swing.AbstractListModel;
+import javax.swing.MutableComboBoxModel;
+import org.jetbrains.annotations.NotNull;
+
+public final class SkinComboBoxModel extends AbstractListModel<Skin> implements MutableComboBoxModel<Skin> {
+ private List<Skin> mySkins = new ArrayList<>(List.of(NoSkin.INSTANCE));
+
+ @NotNull
+ private Object mySelectedSkin = NoSkin.INSTANCE;
+
+ public void load() {
+ var future = Futures.submit(new Collector()::collect, AppExecutorUtil.getAppExecutorService());
+ Futures.addCallback(future, new Merge(), EdtExecutorService.getInstance());
+ }
+
+ private final class Merge implements FutureCallback<Collection<Skin>> {
+ @Override
+ public void onSuccess(@NotNull Collection<Skin> skins) {
+ var map = Stream.concat(mySkins.stream(), skins.stream()).collect(Collectors.toMap(Skin::path, skin -> skin, Skin::merge));
+
+ mySkins = map.values().stream()
+ .sorted()
+ .collect(Collectors.toList());
+
+ fireContentsChanged(SkinComboBoxModel.this, 0, mySkins.size() - 1);
+ }
+
+ @Override
+ public void onFailure(@NotNull Throwable throwable) {
+ Logger.getInstance(SkinComboBoxModel.class).warn(throwable);
+ }
+ }
+
+ @NotNull
+ public Skin getSkin(@NotNull Path path) {
+ return mySkins.stream()
+ .filter(skin -> skin.path().equals(path))
+ .findFirst()
+ .orElse(new DefaultSkin(path));
+ }
+
+ @Override
+ public int getSize() {
+ return mySkins.size();
+ }
+
+ @NotNull
+ @Override
+ public Skin getElementAt(int index) {
+ return mySkins.get(index);
+ }
+
+ @Override
+ public void addElement(@NotNull Skin skin) {
+ if (mySkins.contains(skin)) {
+ return;
+ }
+
+ mySkins.add(skin);
+ mySkins.sort(null);
+
+ var index = mySkins.indexOf(skin);
+ fireIntervalAdded(this, index, index);
+ }
+
+ @Override
+ public void removeElement(@NotNull Object skin) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void insertElementAt(@NotNull Skin skin, int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeElementAt(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setSelectedItem(@NotNull Object selectedSkin) {
+ assert selectedSkin instanceof Skin skin && mySkins.contains(skin);
+
+ mySelectedSkin = selectedSkin;
+ fireContentsChanged(this, -1, -1);
+ }
+
+ @NotNull
+ @Override
+ public Object getSelectedItem() {
+ return mySelectedSkin;
+ }
+}
diff --git a/android/src/com/android/tools/idea/avdmanager/skincombobox/SystemImageSkin.java b/android/src/com/android/tools/idea/avdmanager/skincombobox/SystemImageSkin.java
index 738bab2..2427993 100644
--- a/android/src/com/android/tools/idea/avdmanager/skincombobox/SystemImageSkin.java
+++ b/android/src/com/android/tools/idea/avdmanager/skincombobox/SystemImageSkin.java
@@ -22,6 +22,18 @@
@SuppressWarnings("unused")
record SystemImageSkin(@NotNull Path path, @NotNull AndroidVersion version, @NotNull String abi) implements Skin {
+ /**
+ * If the parameter is a DefaultSkin, return this; otherwise return the parameter. System images that don't have their own skins refer to
+ * the platform skins for their platform version. This resolution will drop those skins. It also handles the user manually picking a
+ * system image skin before the asynchronous collection is done.
+ */
+ @NotNull
+ @Override
+ public Skin merge(@NotNull Skin skin) {
+ assert skin.path().equals(path) : skin;
+ return skin instanceof DefaultSkin ? this : skin;
+ }
+
@NotNull
@Override
public String toString() {
diff --git a/android/src/com/android/tools/idea/databinding/index/BindingXmlIndex.kt b/android/src/com/android/tools/idea/databinding/index/BindingXmlIndex.kt
index b8b9225..eb714fb 100644
--- a/android/src/com/android/tools/idea/databinding/index/BindingXmlIndex.kt
+++ b/android/src/com/android/tools/idea/databinding/index/BindingXmlIndex.kt
@@ -59,12 +59,13 @@
@JvmField
val NAME = ID.create<Int, BindingXmlData>("BindingXmlIndex")
- fun acceptsFile(file: VirtualFile): Boolean =
- "xml" == file.extension &&
- ResourceFolderType.getFolderType(file.parent?.name.orEmpty()) == ResourceFolderType.LAYOUT
-
private fun getDataForFile(file: VirtualFile, project: Project): BindingXmlData? {
- return FileBasedIndex.getInstance().getSingleEntryIndexData(NAME, file, project)
+ val data = FileBasedIndex.getInstance().getSingleEntryIndexData(NAME, file, project) ?: return null
+
+ val parentFolderName = file.parent?.name ?: return null
+ if (ResourceFolderType.getFolderType(parentFolderName) != ResourceFolderType.LAYOUT) return null
+
+ return data
}
fun getDataForFile(project: Project, file: VirtualFile) = getDataForFile(file, project)
@@ -161,7 +162,20 @@
override fun getIndexer(): SingleEntryIndexer<BindingXmlData> {
return object : SingleEntryIndexer<BindingXmlData>(false) {
- override fun computeValue(inputData: FileContent): BindingXmlData {
+
+ // Quick heuristic to avoid indexing non-layout files. We can't determine for sure at indexing time whether this is a layout file,
+ // as that relies on the parent directory which can't be accessed during indexing (see [FileBasedIndexExtension] docs). But layout
+ // files must contain the Android namespace declaration (see https://developer.android.com/guide/topics/resources/layout-resource),
+ // and so this indexer can skip processing any files that don't contain the declaration.
+ // This is checked with a text search rather than in the XML parsing below, since NanoXmlBuilder doesn't get directly called when
+ // the parser sees the namespace.
+ private val xmlNamespaceRegex = Regex("""xmlns:android\s*=\s*"http://schemas.android.com/apk/res/android"""")
+
+ override fun computeValue(inputData: FileContent): BindingXmlData? {
+ val inputAsText = inputData.contentAsText
+
+ if (!inputAsText.contains(xmlNamespaceRegex)) return null
+
var bindingLayoutType = PLAIN_LAYOUT
var customBindingName: String? = null
var viewBindingIgnore = false
@@ -183,7 +197,7 @@
var viewTypeOverride: String? = null
}
- NanoXmlUtil.parse(EscapingXmlReader(inputData.contentAsText), object : NanoXmlBuilder {
+ NanoXmlUtil.parse(EscapingXmlReader(inputAsText), object : NanoXmlBuilder {
val tags = mutableListOf<TagData>()
override fun startElement(name: String, nsPrefix: String?, nsURI: String?, systemID: String, lineNr: Int) {
@@ -295,13 +309,9 @@
}
}
- override fun getInputFilter(): FileBasedIndex.InputFilter {
- return object : DefaultFileTypeSpecificInputFilter(XmlFileType.INSTANCE) {
- override fun acceptInput(file: VirtualFile): Boolean = acceptsFile(file)
- }
- }
+ override fun getInputFilter() = DefaultFileTypeSpecificInputFilter(XmlFileType.INSTANCE)
- override fun getVersion() = 11
+ override fun getVersion() = 12
}
private const val COMMENT_START = "<!--"
diff --git a/android/src/com/android/tools/idea/diagnostics/heap/HeapSnapshotTraverseService.java b/android/src/com/android/tools/idea/diagnostics/heap/HeapSnapshotTraverseService.java
index 102f951..95849a4 100644
--- a/android/src/com/android/tools/idea/diagnostics/heap/HeapSnapshotTraverseService.java
+++ b/android/src/com/android/tools/idea/diagnostics/heap/HeapSnapshotTraverseService.java
@@ -183,7 +183,7 @@
}, statistics,
new MemoryReportCollector.HeapSnapshotPresentationConfig(
PLAIN_VALUES,
- /*shouldLogSharedClusters=*/false,
+ /*shouldLogSharedClusters=*/true,
/*shouldLogRetainedSizes=*/false));
statistics = null;
diff --git a/android/src/com/android/tools/idea/diagnostics/heap/RootPathTree.java b/android/src/com/android/tools/idea/diagnostics/heap/RootPathTree.java
index f5d65d4..ca72b3d 100644
--- a/android/src/com/android/tools/idea/diagnostics/heap/RootPathTree.java
+++ b/android/src/com/android/tools/idea/diagnostics/heap/RootPathTree.java
@@ -51,7 +51,7 @@
private int numberOfRootPathTreeNodes = 0;
private final ExtendedReportStatistics extendedReportStatistics;
- private static final int ROOT_PATH_TREE_MAX_OBJECT_DEPTH = 400;
+ private static final int ROOT_PATH_TREE_MAX_OBJECT_DEPTH = 200;
private static final int NODE_SUBTREE_SIZE_PERCENTAGE_REQUIREMENT = 2;
private static final int NODE_SUBTREE_OBJECTS_SIZE_REQUIREMENT_BYTES = 750_000; //750kb
diff --git a/android/src/com/android/tools/idea/diagnostics/profiler/DumpJfrRecording.java b/android/src/com/android/tools/idea/diagnostics/profiler/DumpJfrRecording.java
index c342aff..502a698 100644
--- a/android/src/com/android/tools/idea/diagnostics/profiler/DumpJfrRecording.java
+++ b/android/src/com/android/tools/idea/diagnostics/profiler/DumpJfrRecording.java
@@ -15,6 +15,7 @@
*/
package com.android.tools.idea.diagnostics.profiler;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.DumbAwareAction;
@@ -25,6 +26,12 @@
super("Dump JFR Recording");
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.EDT;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
e.getPresentation().setEnabled(ApplicationManager.getApplication().getService(Jfr.class).isProfilerActive());
diff --git a/android/src/com/android/tools/idea/diagnostics/profiler/StartJfr.java b/android/src/com/android/tools/idea/diagnostics/profiler/StartJfr.java
index 7874d66..02e9789 100644
--- a/android/src/com/android/tools/idea/diagnostics/profiler/StartJfr.java
+++ b/android/src/com/android/tools/idea/diagnostics/profiler/StartJfr.java
@@ -15,6 +15,7 @@
*/
package com.android.tools.idea.diagnostics.profiler;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.DumbAwareAction;
@@ -25,6 +26,12 @@
super("Start JFR");
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.EDT;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
e.getPresentation().setEnabled(JfrUtilsKt.isJfrAvailable() && !ApplicationManager.getApplication().getService(Jfr.class).isProfilerActive());
diff --git a/android/src/com/android/tools/idea/diagnostics/profiler/StopJfr.java b/android/src/com/android/tools/idea/diagnostics/profiler/StopJfr.java
index 0c9c547..9c57512 100644
--- a/android/src/com/android/tools/idea/diagnostics/profiler/StopJfr.java
+++ b/android/src/com/android/tools/idea/diagnostics/profiler/StopJfr.java
@@ -15,6 +15,7 @@
*/
package com.android.tools.idea.diagnostics.profiler;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.DumbAwareAction;
@@ -25,6 +26,12 @@
super("Stop JFR");
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.EDT;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
e.getPresentation().setEnabled(ApplicationManager.getApplication().getService(Jfr.class).isProfilerActive());
diff --git a/android/src/com/android/tools/idea/editors/GradleImplicitPropertyUsageProvider.java b/android/src/com/android/tools/idea/editors/GradleImplicitPropertyUsageProvider.java
index 6c19220..3582c64 100644
--- a/android/src/com/android/tools/idea/editors/GradleImplicitPropertyUsageProvider.java
+++ b/android/src/com/android/tools/idea/editors/GradleImplicitPropertyUsageProvider.java
@@ -36,7 +36,8 @@
return true;
}
- if (Comparing.equal(file.getName(), "gradle.properties", caseSensitive)) {
+ if (Comparing.equal(file.getName(), "gradle.properties", caseSensitive) ||
+ Comparing.equal(file.getName(), "resources.properties", caseSensitive)) {
// Ignore all properties in the gradle.properties; we don't have a complete set of what's used
// and we don't want to suggest to the user that these are unused
return true;
diff --git a/android/src/com/android/tools/idea/editors/build/ProjectBuildStatusManager.kt b/android/src/com/android/tools/idea/editors/build/ProjectBuildStatusManager.kt
index 2c8ecd7..f556477 100644
--- a/android/src/com/android/tools/idea/editors/build/ProjectBuildStatusManager.kt
+++ b/android/src/com/android/tools/idea/editors/build/ProjectBuildStatusManager.kt
@@ -15,7 +15,6 @@
*/
package com.android.tools.idea.editors.build
-import com.android.annotations.concurrency.UiThread
import com.android.tools.idea.concurrency.AndroidCoroutineScope
import com.android.tools.idea.concurrency.AndroidDispatchers.workerThread
import com.android.tools.idea.editors.fast.CompilationResult
@@ -37,18 +36,15 @@
import com.intellij.psi.PsiFile
import com.intellij.psi.SmartPointerManager
import com.intellij.psi.SmartPsiElementPointer
-import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
import org.jetbrains.annotations.TestOnly
-import org.jetbrains.kotlin.idea.gradleTooling.get
import org.jetbrains.kotlin.idea.util.projectStructure.module
+import java.util.concurrent.atomic.AtomicInteger
/**
* This represents the build status of the project without taking into account any file
diff --git a/android/src/com/android/tools/idea/gradle/plugin/AgpVersions.kt b/android/src/com/android/tools/idea/gradle/plugin/AgpVersions.kt
index 0eedccf..fedd9a3 100644
--- a/android/src/com/android/tools/idea/gradle/plugin/AgpVersions.kt
+++ b/android/src/com/android/tools/idea/gradle/plugin/AgpVersions.kt
@@ -111,10 +111,7 @@
}
@VisibleForTesting
- fun getNewProjectWizardVersions(
- latestKnown: AgpVersion = this.latestKnown,
- availableVersions: Set<AgpVersion> = getAvailableVersions()
- ): Set<AgpVersion> {
+ fun getNewProjectWizardVersions(latestKnown: AgpVersion, availableVersions: Set<AgpVersion>): Set<AgpVersion> {
val include = setOf(AndroidGradlePluginCompatibility.COMPATIBLE, AndroidGradlePluginCompatibility.DEPRECATED)
var minOfCurrentSeries = AgpVersion(Int.MAX_VALUE, Int.MAX_VALUE, Int.MAX_VALUE)
val recommended = mutableListOf<AgpVersion>()
@@ -122,7 +119,13 @@
// Go from latest first, and include latest from each series that is compatible
if (version < minOfCurrentSeries &&
include.contains(computeAndroidGradlePluginCompatibility(version, latestKnown)) ) {
- minOfCurrentSeries = AgpVersion.parse(version.toString().substringBefore("-") + "-alpha01")
+ minOfCurrentSeries = if (version.isSnapshot) {
+ // Treat -dev as special case, so also include the latest release version from the current series, if present.
+ version
+ } else {
+ // Exclude all older versions from the current series
+ AgpVersion.parse(version.toString().substringBefore("-") + "-alpha01")
+ }
recommended.add(version)
}
}
diff --git a/android/src/com/android/tools/idea/imports/AndroidMavenImportIntentionAction.kt b/android/src/com/android/tools/idea/imports/AndroidMavenImportIntentionAction.kt
index 46b1741..9213585 100644
--- a/android/src/com/android/tools/idea/imports/AndroidMavenImportIntentionAction.kt
+++ b/android/src/com/android/tools/idea/imports/AndroidMavenImportIntentionAction.kt
@@ -60,7 +60,8 @@
import org.jetbrains.kotlin.psi.KtUserType
/**
- * An action which recognizes classes from key Maven artifacts and offers to add a dependency on them.
+ * An action which recognizes classes from key Maven artifacts and offers to add a dependency on
+ * them.
*/
class AndroidMavenImportIntentionAction : PsiElementBaseIntentionAction() {
private var intentionActionText: String = familyName
@@ -72,21 +73,18 @@
val version: String?
) : Comparable<AutoImportVariant> {
override fun compareTo(other: AutoImportVariant): Int {
- artifactToAdd.compareTo(other.artifactToAdd).let {
- if (it != 0) return it
- }
+ artifactToAdd.compareTo(other.artifactToAdd).let { if (it != 0) return it }
return classToImport.compareTo(other.classToImport)
}
}
- private class Resolvable private constructor(
- val libraries: Collection<MavenClassRegistryBase.LibraryImportData>
- ) {
+ private class Resolvable
+ private constructor(val libraries: Collection<MavenClassRegistryBase.LibraryImportData>) {
companion object {
- fun createNewOrNull(libraries: Collection<MavenClassRegistryBase.LibraryImportData>): Resolvable? {
- return if (libraries.isEmpty()) null else Resolvable(libraries)
- }
+ fun createNewOrNull(
+ libraries: Collection<MavenClassRegistryBase.LibraryImportData>
+ ): Resolvable? = libraries.takeUnless { it.isEmpty() }?.let(::Resolvable)
}
}
@@ -94,28 +92,38 @@
perform(project, editor, element, true)
}
- /**
- * Performs a fix. Or let users to choose from the popup if there's multiple options.
- */
+ /** Performs a fix. Or let users choose from the popup if there are multiple options. */
fun perform(project: Project, editor: Editor, element: PsiElement, sync: Boolean) {
- val resolvable = findResolvable(element, editor.caretModel.offset) { text ->
- Resolvable.createNewOrNull(findLibraryData(project, text, element.containingFile?.fileType))
- } ?: return
-
- val suggestions = resolvable.libraries
- .asSequence()
- .map {
- val artifact = resolveArtifact(project, element.language, it.artifact)
- val importSymbol = resolveImport(project, it.importedItemFqName)
- AutoImportVariant(artifact, importSymbol, it.version)
+ val resolvable =
+ findResolvable(element, editor.caretModel.offset) { text, receiverType ->
+ Resolvable.createNewOrNull(
+ findLibraryData(project, text, receiverType, element.containingFile?.fileType)
+ )
}
- .toSortedSet()
+ ?: return
+
+ val suggestions =
+ resolvable.libraries
+ .asSequence()
+ .map {
+ val artifact = resolveArtifact(project, element.language, it.artifact)
+ val importSymbol = resolveImport(project, it.importedItemFqName)
+ AutoImportVariant(artifact, importSymbol, it.version)
+ }
+ .toSortedSet()
if (suggestions.isEmpty()) return
if (suggestions.size == 1 || ApplicationManager.getApplication().isUnitTestMode) {
val suggestion = suggestions.first()
- perform(project, element, suggestion.artifactToAdd, suggestion.version, suggestion.classToImport, sync)
+ perform(
+ project,
+ element,
+ suggestion.artifactToAdd,
+ suggestion.version,
+ suggestion.classToImport,
+ sync
+ )
return
}
@@ -129,19 +137,31 @@
suggestions: List<AutoImportVariant>,
sync: Boolean
) {
- val step = object : BaseListPopupStep<AutoImportVariant>(
- AndroidBundle.message("android.suggested.imports.title"),
- suggestions
- ) {
- override fun getTextFor(value: AutoImportVariant): String {
- return flagPreview(value.artifactToAdd, value.version)
- }
+ val step =
+ object :
+ BaseListPopupStep<AutoImportVariant>(
+ AndroidBundle.message("android.suggested.imports.title"),
+ suggestions
+ ) {
+ override fun getTextFor(value: AutoImportVariant): String {
+ return flagPreview(value.artifactToAdd, value.version)
+ }
- override fun onChosen(selectedValue: AutoImportVariant, finalChoice: Boolean): PopupStep<*>? {
- perform(project, element, selectedValue.artifactToAdd, selectedValue.version, selectedValue.classToImport, sync)
- return FINAL_CHOICE
+ override fun onChosen(
+ selectedValue: AutoImportVariant,
+ finalChoice: Boolean
+ ): PopupStep<*>? {
+ perform(
+ project,
+ element,
+ selectedValue.artifactToAdd,
+ selectedValue.version,
+ selectedValue.classToImport,
+ sync
+ )
+ return FINAL_CHOICE
+ }
}
- }
ListPopupImpl(project, step).showInBestPositionFor(editor)
}
@@ -159,9 +179,9 @@
var syncFuture: ListenableFuture<ProjectSystemSyncManager.SyncResult>? = null
WriteCommandAction.runWriteCommandAction(project) {
if (sync) {
- syncFuture = performWithLockAndSync(project, module, element, artifact, artifactVersion, importSymbol)
- }
- else {
+ syncFuture =
+ performWithLockAndSync(project, module, element, artifact, artifactVersion, importSymbol)
+ } else {
performWithLock(project, module, element, artifact, artifactVersion, importSymbol)
}
}
@@ -179,26 +199,35 @@
importSymbol: String?
): ListenableFuture<ProjectSystemSyncManager.SyncResult> {
// Register sync action for undo.
- UndoManager.getInstance(project).undoableActionPerformed(object : GlobalUndoableAction() {
- override fun undo() {
- project.requestSync()
- }
+ UndoManager.getInstance(project)
+ .undoableActionPerformed(
+ object : GlobalUndoableAction() {
+ override fun undo() {
+ project.requestSync()
+ }
- override fun redo() {}
- })
+ override fun redo() {}
+ }
+ )
performWithLock(project, module, element, artifact, artifactVersion, importSymbol)
// Register sync action for redo.
- UndoManager.getInstance(project).undoableActionPerformed(object : GlobalUndoableAction() {
- override fun undo() {}
+ UndoManager.getInstance(project)
+ .undoableActionPerformed(
+ object : GlobalUndoableAction() {
+ override fun undo() {}
- override fun redo() {
- project.requestSync()
- }
- })
+ override fun redo() {
+ project.requestSync()
+ }
+ }
+ )
- return project.getProjectSystem().getSyncManager().syncProject(ProjectSystemSyncManager.SyncReason.PROJECT_MODIFIED)
+ return project
+ .getProjectSystem()
+ .getSyncManager()
+ .syncProject(ProjectSystemSyncManager.SyncReason.PROJECT_MODIFIED)
}
/**
@@ -214,7 +243,8 @@
artifactVersion: String?,
importSymbol: String?
) {
- // Import the class as well (if possible); otherwise it might be confusing that you have to invoke two
+ // Import the class as well (if possible); otherwise it might be confusing that you have to
+ // invoke two
// separate intention actions in order to get your symbol resolved
if (importSymbol != null) {
addImportStatement(project, element, importSymbol)
@@ -222,26 +252,37 @@
addDependency(module, artifact, artifactVersion)
// Also add on an extra dependency for special cases.
- getMavenClassRegistry()
- .findExtraArtifacts(artifact)
- .forEach { addDependency(module, it.key, artifactVersion, it.value) }
+ getMavenClassRegistry().findExtraArtifacts(artifact).forEach {
+ addDependency(module, it.key, artifactVersion, it.value)
+ }
// Also add dependent annotation processor?
- if (module.getModuleSystem().canRegisterDependency(DependencyType.ANNOTATION_PROCESSOR).isSupported()) {
+ if (
+ module
+ .getModuleSystem()
+ .canRegisterDependency(DependencyType.ANNOTATION_PROCESSOR)
+ .isSupported()
+ ) {
getMavenClassRegistry().findAnnotationProcessor(artifact)?.let { it ->
- val annotationProcessor = if (project.isAndroidx()) {
- AndroidxNameUtils.getCoordinateMapping(it)
- }
- else {
- it
- }
+ val annotationProcessor =
+ if (project.isAndroidx()) {
+ AndroidxNameUtils.getCoordinateMapping(it)
+ } else {
+ it
+ }
- addDependency(module, annotationProcessor, artifactVersion, DependencyType.ANNOTATION_PROCESSOR)
+ addDependency(
+ module,
+ annotationProcessor,
+ artifactVersion,
+ DependencyType.ANNOTATION_PROCESSOR
+ )
}
}
}
- override fun getFamilyName(): String = AndroidBundle.message("android.suggested.import.action.family.name")
+ override fun getFamilyName(): String =
+ AndroidBundle.message("android.suggested.import.action.family.name")
override fun getText(): String = intentionActionText
@@ -249,23 +290,31 @@
val module = ModuleUtil.findModuleForPsiElement(element) ?: return false
if (!module.getModuleSystem().canRegisterDependency().isSupported()) return false
- val resolvable = findResolvable(element, editor?.caretModel?.offset ?: -1) { text ->
- Resolvable.createNewOrNull(findLibraryData(project, text, element.containingFile?.fileType))
- } ?: return false
+ val resolvable =
+ findResolvable(element, editor?.caretModel?.offset ?: -1) { text, receiverType ->
+ Resolvable.createNewOrNull(
+ findLibraryData(project, text, receiverType, element.containingFile?.fileType)
+ )
+ }
+ ?: return false
val foundLibraries = resolvable.libraries
// If we are already depending on any of them, we just abort providing any suggestions as well.
- if (foundLibraries.isEmpty() || foundLibraries.any { dependsOn(module, it.artifact) }) return false
+ if (foundLibraries.isEmpty() || foundLibraries.any { dependsOn(module, it.artifact) })
+ return false
// Update the text.
- intentionActionText = if (foundLibraries.size == 1) {
- val library = foundLibraries.single()
- val artifact = resolveArtifact(project, element.language, library.artifact)
- AndroidBundle.message("android.suggested.import.action.name.prefix", flagPreview(artifact, library.version))
- }
- else {
- familyName
- }
+ intentionActionText =
+ if (foundLibraries.size == 1) {
+ val library = foundLibraries.single()
+ val artifact = resolveArtifact(project, element.language, library.artifact)
+ AndroidBundle.message(
+ "android.suggested.import.action.name.prefix",
+ flagPreview(artifact, library.version)
+ )
+ } else {
+ familyName
+ }
return true
}
@@ -273,25 +322,33 @@
private fun Project.requestSync() {
val syncManager = getProjectSystem().getSyncManager()
if (syncManager.isSyncInProgress()) {
- listenUntilNextSync(this, object : ProjectSystemSyncManager.SyncResultListener {
- override fun syncEnded(result: ProjectSystemSyncManager.SyncResult) {
- syncManager.syncProject(ProjectSystemSyncManager.SyncReason.PROJECT_MODIFIED)
+ listenUntilNextSync(
+ this,
+ object : ProjectSystemSyncManager.SyncResultListener {
+ override fun syncEnded(result: ProjectSystemSyncManager.SyncResult) {
+ syncManager.syncProject(ProjectSystemSyncManager.SyncReason.PROJECT_MODIFIED)
+ }
}
- })
- }
- else {
+ )
+ } else {
syncManager.syncProject(ProjectSystemSyncManager.SyncReason.PROJECT_MODIFIED)
}
}
- private fun addDependency(module: Module, artifact: String, version: String?, type: DependencyType = DependencyType.IMPLEMENTATION) {
+ private fun addDependency(
+ module: Module,
+ artifact: String,
+ version: String?,
+ type: DependencyType = DependencyType.IMPLEMENTATION
+ ) {
val coordinate = getCoordinate(artifact, version) ?: return
val moduleSystem = module.getModuleSystem()
moduleSystem.registerDependency(coordinate, type)
}
private fun dependsOn(module: Module, artifact: String): Boolean {
- // To check if we depend on an artifact, we don't particularly care which version is there, just whether the library is included at all.
+ // To check if we depend on an artifact, we don't particularly care which version is there, just
+ // whether the library is included at all.
val coordinate = getCoordinate(artifact) ?: return false
val moduleSystem = module.getModuleSystem()
return moduleSystem.getRegisteredDependency(coordinate) != null
@@ -301,10 +358,10 @@
* Generates a coordinate representing the specified artifact.
*
* @param artifact requested artifact
- *
- * @param version desired version, if any. This is expected to be in the form "2.5.1". The version comes from [MavenClassRegistry], and
- * represents the minimum version of a dependency that should be added. See
- * [b/275602080](https://issuetracker.google.com/issues/275602080#comment6) for more details.
+ * @param version desired version, if any. This is expected to be in the form "2.5.1". The version
+ * comes from [MavenClassRegistry], and represents the minimum version of a dependency that
+ * should be added. See [b/275602080](https://issuetracker.google.com/issues/275602080#comment6)
+ * for more details.
*/
private fun getCoordinate(artifact: String, version: String? = null) =
if (version.isNullOrEmpty()) GradleCoordinate.parseCoordinateString("$artifact:+")
@@ -313,42 +370,47 @@
private fun findResolvable(
element: PsiElement,
caret: Int,
- resolve: (String) -> Resolvable?
+ resolve: (String, String?) -> Resolvable?
): Resolvable? {
+ // This is actually the common case.
+ fun resolveWithoutReceiver(s: String) = resolve(s, null)
if (element is PsiIdentifier || caret == 0) {
- // In Java code, if you're pointing somewhere in the middle of a fully qualified name (such as an import
- // statement to a library that isn't available), the unresolved symbol won't be the final class, it will be the
- // first unavailable package segment. In these cases, search down the chain for the actual imported class symbol
- // and scan on that one instead.
- // E.g. for "androidx.camera.core.ImageCapture.OnImageSavedCallback" and "camera" is an unresolvable symbol, we
- // search first for "androidx.camera", and then "androidx.camera.core", and we stop at the first resolvable, which
- // is "androidx.camera.core.ImageCapture".
+ // In Java code, if you're pointing somewhere in the middle of a fully qualified name (such as
+ // an import statement to a library that isn't available), the unresolved symbol won't be the
+ // final class, it will be the first unavailable package segment. In these cases, search down
+ // the chain for the actual imported class symbol and scan on that one instead. E.g. for
+ // "androidx.camera.core.ImageCapture.OnImageSavedCallback" and "camera" is an unresolvable
+ // symbol, we search first for "androidx.camera", and then "androidx.camera.core", and we stop
+ // at the first resolvable, which is "androidx.camera.core.ImageCapture".
if (element.parent is PsiJavaCodeReferenceElement) {
var curr: PsiJavaCodeReferenceElement? = element.parent as PsiJavaCodeReferenceElement
while (curr != null) {
- val found = resolve(curr.text)
- if (found != null) return found
+ resolveWithoutReceiver(curr.text)?.let {
+ return it
+ }
curr = curr.parent as? PsiJavaCodeReferenceElement
}
}
- return resolve(element.text)
- }
- else if (element is LeafPsiElement && element.elementType == KtTokens.IDENTIFIER) {
- // In Kotlin code, if you're pointing somewhere in the middle of a fully qualified name (such as an import
- // statement to a library that isn't available), the unresolved symbol won't be the final class, it will be the
- // first unavailable package segment. In these cases, search down the chain for the actual imported class symbol
- // and scan on that one instead.
+ return resolveWithoutReceiver(element.text)
+ } else if (element is LeafPsiElement && element.elementType == KtTokens.IDENTIFIER) {
+ // In Kotlin code, if you're pointing somewhere in the middle of a fully qualified name (such
+ // as an import statement to a library that isn't available), the unresolved symbol won't be
+ // the final class, it will be the first unavailable package segment. In these cases, search
+ // down the chain for the actual imported class symbol and scan on that one instead.
if (element.parent is KtNameReferenceExpression) {
when (val current = element.parent.parent) {
is KtDotQualifiedExpression -> {
var curr: KtDotQualifiedExpression? = current
while (curr != null) {
- val found = curr.formText()?.let {
- resolve(it)
- }
- if (found != null) return found
+ // TODO(b/300296134): Use receiver type if available.
+ curr
+ .formText()
+ ?.let { resolveWithoutReceiver(it) }
+ ?.let {
+ return it
+ }
curr = curr.parent as? KtDotQualifiedExpression
}
@@ -356,17 +418,18 @@
is KtUserType -> {
var curr: KtUserType? = current
while (curr != null) {
- val found = resolve(curr.text)
- if (found != null) return found
+ resolveWithoutReceiver(curr.text)?.let {
+ return it
+ }
curr = curr.parent as? KtUserType
}
}
- else -> return resolve(element.text)
+ else -> return resolveWithoutReceiver(element.text)
}
}
- return resolve(element.text)
+ return resolveWithoutReceiver(element.text)
}
// When the caret is at the end of the word (which it frequently is in the unresolved symbol
@@ -374,7 +437,7 @@
// on the right of the caret, which is the next element, not the symbol element.
if (caret == element.textOffset || element is PsiWhiteSpace) {
if (element.prevSibling != null) {
- return resolve(element.prevSibling.text)
+ return resolveWithoutReceiver(element.prevSibling.text)
}
val targetOffset = caret - 1
var curr = element.parent
@@ -383,11 +446,11 @@
}
if (curr != null) {
val text = curr.findElementAt(targetOffset - curr.textOffset)?.text ?: element.text
- return resolve(text)
+ return resolveWithoutReceiver(text)
}
}
- return resolve(element.text)
+ return resolveWithoutReceiver(element.text)
}
private fun KtDotQualifiedExpression.formText(): String? {
@@ -403,9 +466,14 @@
return null
}
- private fun findLibraryData(project: Project, text: String, completionFileType: FileType?)
- : Collection<MavenClassRegistryBase.LibraryImportData> {
- return getMavenClassRegistry().findLibraryData(text, project.isAndroidx(), completionFileType)
+ private fun findLibraryData(
+ project: Project,
+ text: String,
+ receiverType: String?,
+ completionFileType: FileType?
+ ): Collection<MavenClassRegistryBase.LibraryImportData> {
+ return getMavenClassRegistry()
+ .findLibraryData(text, receiverType, project.isAndroidx(), completionFileType)
}
private fun resolveArtifact(project: Project, language: Language, artifact: String): String {
@@ -420,8 +488,7 @@
}
androidx
- }
- else {
+ } else {
artifact
}
}
@@ -429,8 +496,7 @@
private fun resolveImport(project: Project, fqn: String): String {
return if (project.isAndroidx()) {
AndroidxNameUtils.getNewName(fqn)
- }
- else {
+ } else {
fqn
}
}
@@ -456,7 +522,7 @@
// Can't access org.jetbrains.kotlin.idea.util.ImportInsertHelper
ImportInsertHelperImpl.addImport(project, file as KtFile, FqName(import))
}
- // Nothing to do in XML etc
+ // Nothing to do in XML etc
}
}
diff --git a/android/src/com/android/tools/idea/imports/MavenClassRegistry.kt b/android/src/com/android/tools/idea/imports/MavenClassRegistry.kt
index c4134bc..0494123 100644
--- a/android/src/com/android/tools/idea/imports/MavenClassRegistry.kt
+++ b/android/src/com/android/tools/idea/imports/MavenClassRegistry.kt
@@ -19,31 +19,40 @@
import com.google.gson.stream.JsonReader
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.fileTypes.FileType
-import org.jetbrains.kotlin.idea.KotlinFileType
-import org.jetbrains.kotlin.name.FqName
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
+import org.jetbrains.kotlin.idea.KotlinFileType
+import org.jetbrains.kotlin.name.FqName
/**
* Registry contains [lookup] extracted by reading indices from [GMavenIndexRepository].
*
- * Here, it covers all the latest stable versions of libraries which are explicitly marked as `Yes` to include in
- * go/studio-auto-import-packages.
+ * Here, it covers all the latest stable versions of libraries which are explicitly marked as `Yes`
+ * to include in go/studio-auto-import-packages.
*/
-class MavenClassRegistry(private val indexRepository: GMavenIndexRepository) : MavenClassRegistryBase() {
+class MavenClassRegistry(private val indexRepository: GMavenIndexRepository) :
+ MavenClassRegistryBase() {
val lookup: LookupData = generateLookup()
/**
- * Given an unresolved name, returns the likely collection of [LibraryImportData] objects for the maven.google.com artifacts containing a
- * class or function matching the name.
+ * Given an unresolved name, returns the likely collection of [LibraryImportData] objects for the
+ * maven.google.com artifacts containing a class or function matching the name.
*
* This implementation only returns results of index data from [GMavenIndexRepository].
*
- * @param name simple or fully-qualified name typed by the user. May correspond to a class name (any files) or a top-level Kotlin function
- * name (Kotlin files only).
+ * @param name simple or fully-qualified name typed by the user. May correspond to a class name
+ * (any files) or a top-level Kotlin function name (Kotlin files only).
+ * @param receiverType the fully-qualified name of the receiver type, if any,
+ * [MavenClassRegistryBase.ALL_RECEIVER_TYPES] if results for any receiver type (including no
+ * receiver) should be returned, or `null` otherwise.
*/
- override fun findLibraryData(name: String, useAndroidX: Boolean, completionFileType: FileType?): Collection<LibraryImportData> {
+ override fun findLibraryData(
+ name: String,
+ receiverType: String?,
+ useAndroidX: Boolean,
+ completionFileType: FileType?
+ ): Collection<LibraryImportData> {
// We only support projects that set android.useAndroidX=true.
if (!useAndroidX) return emptyList()
@@ -51,10 +60,17 @@
val packageName = name.substringBeforeLast('.', missingDelimiterValue = "")
val foundArtifacts = buildList {
- lookup.classNameMap[shortName]?.let { addAll(it) }
-
+ if (receiverType == null || receiverType == ALL_RECEIVER_TYPES)
+ lookup.classNameMap[shortName]?.let { addAll(it) }
// Only suggest top-level Kotlin functions when completing in a Kotlin file.
- if (completionFileType == KotlinFileType.INSTANCE) lookup.topLevelFunctionsMap[shortName]?.let { addAll(it) }
+ if (completionFileType == KotlinFileType.INSTANCE) {
+ if (receiverType == ALL_RECEIVER_TYPES) {
+ lookup.topLevelFunctionsMapAllReceivers[shortName]?.let { addAll(it) }
+ } else {
+ val functionSpecifier = FunctionSpecifier(shortName, receiverType?.let(::FqName))
+ lookup.topLevelFunctionsMap[functionSpecifier]?.let { addAll(it) }
+ }
+ }
}
if (packageName.isEmpty()) return foundArtifacts
@@ -75,8 +91,7 @@
return try {
data.use { readIndicesFromJsonFile(it) }
- }
- catch (e: Exception) {
+ } catch (e: Exception) {
logger<MavenClassRegistry>().warn("Problem reading GMaven index file: ${e.message}")
LookupData.EMPTY
}
@@ -102,7 +117,7 @@
@Throws(IOException::class)
private fun readIndexArray(reader: JsonReader): LookupData {
val classNames: MutableList<Pair<String, LibraryImportData>> = mutableListOf()
- val topLevelFunctions: MutableList<Pair<String, LibraryImportData>> = mutableListOf()
+ val topLevelFunctions: MutableList<Pair<FunctionSpecifier, LibraryImportData>> = mutableListOf()
val ktxMap: MutableMap<String, String> = mutableMapOf()
val coordinateList: MutableList<Coordinate> = mutableListOf()
@@ -114,12 +129,10 @@
classNames.addAll(indexData.getClassSimpleNamesWithLibraries())
// Get top-level function names and their associated libraries.
- topLevelFunctions.addAll(indexData.getTopLevelFunctionSimpleNamesWithLibraries())
+ topLevelFunctions.addAll(indexData.getTopLevelFunctionSpecifiersWithLibraries())
// Update "artifact to the associated KTX artifact" map.
- indexData.toKtxMapEntry()?.let {
- ktxMap[it.targetLibrary] = it.ktxLibrary
- }
+ indexData.toKtxMapEntry()?.let { ktxMap[it.targetLibrary] = it.ktxLibrary }
// Update maven artifact coordinate list.
coordinateList.add(Coordinate(indexData.groupId, indexData.artifactId, indexData.version))
@@ -140,7 +153,8 @@
var version: String? = null
var ktxTargets: Collection<String>? = null
var fqcns: Collection<FqName>? = null
- // Top-level functions aren't in the index when empty in order to save bytes. Missing is not consider malformed, so allow empty list.
+ // Top-level functions aren't in the index when empty in order to save bytes. Missing is not
+ // consider malformed, so allow empty list.
var topLevelFunctions: Collection<KotlinTopLevelFunction> = emptyList()
while (reader.hasNext()) {
when (reader.nextName()) {
@@ -168,14 +182,18 @@
}
}
- val gMavenIndex = GMavenArtifactIndex(
- groupId = groupId ?: throw MalformedIndexException("Group ID is missing($reader)."),
- artifactId = artifactId ?: throw MalformedIndexException("Artifact ID is missing($reader)."),
- version = version ?: throw MalformedIndexException("Version is missing($reader)."),
- ktxTargets = ktxTargets ?: throw MalformedIndexException("Ktx targets are missing($reader)."),
- fqcns = fqcns ?: throw MalformedIndexException("Fully qualified class names are missing($reader)."),
- topLevelFunctions = topLevelFunctions,
- )
+ val gMavenIndex =
+ GMavenArtifactIndex(
+ groupId = groupId ?: throw MalformedIndexException("Group ID is missing($reader)."),
+ artifactId = artifactId
+ ?: throw MalformedIndexException("Artifact ID is missing($reader)."),
+ version = version ?: throw MalformedIndexException("Version is missing($reader)."),
+ ktxTargets = ktxTargets
+ ?: throw MalformedIndexException("Ktx targets are missing($reader)."),
+ fqcns = fqcns
+ ?: throw MalformedIndexException("Fully qualified class names are missing($reader)."),
+ topLevelFunctions = topLevelFunctions,
+ )
reader.endObject()
return gMavenIndex
}
@@ -198,15 +216,23 @@
while (reader.hasNext()) {
reader.beginObject()
var fqName: String? = null
+ var xFqName: String? = null
+ var receiverFqName: String? = null
while (reader.hasNext()) {
when (reader.nextName()) {
"fqn" -> fqName = reader.nextString()
+ "xfqn" -> xFqName = reader.nextString()
+ "rcvr" -> receiverFqName = reader.nextString()
else -> reader.skipValue()
}
}
reader.endObject()
- if (fqName != null) add(KotlinTopLevelFunction.fromJvmQualifiedName(fqName))
+ when {
+ fqName != null -> add(KotlinTopLevelFunction.fromJvmQualifiedName(fqName))
+ xFqName != null && receiverFqName != null ->
+ add(KotlinTopLevelFunction.fromJvmQualifiedName(xFqName, receiverFqName))
+ }
}
reader.endArray()
}
@@ -233,9 +259,7 @@
}
}
-/**
- * An index of a specific [version] of GMaven Artifact.
- */
+/** An index of a specific [version] of GMaven Artifact. */
data class GMavenArtifactIndex(
val groupId: String,
val artifactId: String,
@@ -247,28 +271,31 @@
/** Gets a list of simple class names and their corresponding [LibraryImportData]s. */
fun getClassSimpleNamesWithLibraries(): List<Pair<String, LibraryImportData>> {
- return fqcns
- .map { fqName ->
- fqName.shortName().asString() to LibraryImportData(
+ return fqcns.map { fqName ->
+ fqName.shortName().asString() to
+ LibraryImportData(
artifact = "$groupId:$artifactId",
importedItemFqName = fqName.asString(),
importedItemPackageName = fqName.parent().asString(),
version = version
)
- }
+ }
}
- /** Gets a list of top-level function simple names and their corresponding [LibraryImportData]s. */
- fun getTopLevelFunctionSimpleNamesWithLibraries(): List<Pair<String, LibraryImportData>> {
- return topLevelFunctions
- .map { topLevelFunction ->
- topLevelFunction.simpleName to LibraryImportData(
+ /**
+ * Gets a list of top-level function simple names and their corresponding [LibraryImportData]s.
+ */
+ fun getTopLevelFunctionSpecifiersWithLibraries():
+ List<Pair<FunctionSpecifier, LibraryImportData>> {
+ return topLevelFunctions.map { topLevelFunction ->
+ topLevelFunction.toSpecifier() to
+ LibraryImportData(
artifact = "$groupId:$artifactId",
importedItemFqName = topLevelFunction.kotlinFqName.asString(),
importedItemPackageName = topLevelFunction.packageName,
version = version
)
- }
+ }
}
/**
@@ -279,17 +306,13 @@
fun toKtxMapEntry(): KtxMapEntry? {
if (ktxTargets.isEmpty()) return null
- // It's implicit that there's up to one target artifact that's associated to the given KTX artifact.
- return KtxMapEntry(
- ktxLibrary = "$groupId:$artifactId",
- targetLibrary = ktxTargets.first()
- )
+ // It's implicit that there's up to one target artifact that's associated to the given KTX
+ // artifact.
+ return KtxMapEntry(ktxLibrary = "$groupId:$artifactId", targetLibrary = ktxTargets.first())
}
}
-/**
- * An entry of a map from the KTX library to its decorated library.
- */
+/** An entry of a map from the KTX library to its decorated library. */
data class KtxMapEntry(val ktxLibrary: String, val targetLibrary: String)
/** A top-level Kotlin function. */
@@ -299,14 +322,22 @@
/** Package name of the function. */
val packageName: String,
/**
- * Fully-qualified name of the function in Kotlin. This does not contain the synthetic class (e.g. "FileFacadeKt") that contains the
- * function in the JVM. That makes this name appropriate to use when calling from Kotlin, but not from Java.
+ * Fully-qualified name of the function in Kotlin. This does not contain the synthetic class (e.g.
+ * "FileFacadeKt") that contains the function in the JVM. That makes this name appropriate to use
+ * when calling from Kotlin, but not from Java.
*/
val kotlinFqName: FqName,
+ /** Fully-qualified name of the function's receiver in Kotlin. */
+ val receiverFqName: FqName?,
) {
+ fun toSpecifier() = FunctionSpecifier(simpleName, receiverFqName)
+
companion object {
- fun fromJvmQualifiedName(fqName: String): KotlinTopLevelFunction {
+ fun fromJvmQualifiedName(
+ fqName: String,
+ receiverFqName: String? = null
+ ): KotlinTopLevelFunction {
require(fqName.contains('.')) {
"fqName does not have file facade class containing the function: '$fqName'"
}
@@ -317,42 +348,45 @@
val packagePrefix = if (packageName.isEmpty()) "" else "$packageName."
return KotlinTopLevelFunction(
- simpleName = functionSimpleName,
- kotlinFqName = FqName("$packagePrefix$functionSimpleName"),
- packageName = packageName)
+ simpleName = functionSimpleName,
+ packageName = packageName,
+ kotlinFqName = FqName("$packagePrefix$functionSimpleName"),
+ receiverFqName = receiverFqName?.let(::FqName),
+ )
}
}
}
-/**
- * Lookup data extracted from an index file.
- */
+/** Lookup data extracted from an index file. */
data class LookupData(
- /**
- * A map from simple class names to the corresponding [LibraryImportData] objects.
- */
+ /** A map from simple class names to the corresponding [LibraryImportData] objects. */
val classNameMap: Map<String, List<LibraryImportData>>,
- /**
- * A map from simple Kotlin top-level function names to the corresponding [LibraryImportData] objects.
- */
- val topLevelFunctionsMap: Map<String, List<LibraryImportData>>,
- /**
- * A map from non-KTX libraries to the associated KTX libraries.
- */
+ /** A map from function specifiers to the corresponding [LibraryImportData] objects. */
+ val topLevelFunctionsMap: Map<FunctionSpecifier, List<LibraryImportData>>,
+ /** A map from non-KTX libraries to the associated KTX libraries. */
val ktxMap: Map<String, String>,
- /**
- * A list of Google Maven [MavenClassRegistryBase.Coordinate].
- */
+ /** A list of Google Maven [MavenClassRegistryBase.Coordinate]. */
val coordinateList: List<MavenClassRegistryBase.Coordinate>,
) {
+ /**
+ * A map from simple names (irrespective of receiver) to corresponding [LibraryImportData]
+ * objects.
+ */
+ val topLevelFunctionsMapAllReceivers: Map<String, List<LibraryImportData>> =
+ topLevelFunctionsMap.entries.groupBy({ it.key.simpleName }, { it.value }).mapValues {
+ it.value.flatten().distinct()
+ }
+
companion object {
- @JvmStatic
- val EMPTY = LookupData(emptyMap(), emptyMap(), emptyMap(), emptyList())
+ @JvmStatic val EMPTY = LookupData(emptyMap(), emptyMap(), emptyMap(), emptyList())
}
}
-/**
- * Exception thrown when parsing malformed GMaven index file.
- */
+data class FunctionSpecifier(
+ val simpleName: String,
+ val receiverFqName: FqName?,
+)
+
+/** Exception thrown when parsing malformed GMaven index file. */
private class MalformedIndexException(message: String) : RuntimeException(message)
diff --git a/android/src/com/android/tools/idea/imports/MavenClassRegistryBase.kt b/android/src/com/android/tools/idea/imports/MavenClassRegistryBase.kt
index 02eb9b3..af87daf 100644
--- a/android/src/com/android/tools/idea/imports/MavenClassRegistryBase.kt
+++ b/android/src/com/android/tools/idea/imports/MavenClassRegistryBase.kt
@@ -18,15 +18,15 @@
import com.android.tools.idea.projectsystem.DependencyType
import com.intellij.openapi.fileTypes.FileType
-/**
- * Registry provides lookup service for Google Maven Artifacts when asked.
- */
+/** Registry provides lookup service for Google Maven Artifacts when asked. */
abstract class MavenClassRegistryBase {
/**
* Library data for importing a specific item (class or function) with its GMaven artifact.
*
- * @property artifact maven coordinate: groupId:artifactId, please note version is not included here.
- * @property importedItemFqName fully-qualified name of the item to import. Can be a class or function name.
+ * @property artifact maven coordinate: groupId:artifactId, please note version is not included
+ * here.
+ * @property importedItemFqName fully-qualified name of the item to import. Can be a class or
+ * function name.
* @property importedItemPackageName package name of the item to import.
* @property version the version of the [artifact].
*/
@@ -34,35 +34,39 @@
val artifact: String,
val importedItemFqName: String,
val importedItemPackageName: String,
- val version: String? = null)
+ val version: String? = null
+ )
- /**
- * Coordinate for Google Maven artifact.
- */
+ /** Coordinate for Google Maven artifact. */
data class Coordinate(val groupId: String, val artifactId: String, val version: String)
/**
- * Given an unresolved name, returns the likely collection of [MavenClassRegistryBase.LibraryImportData] objects for the maven.google.com
- * artifacts containing a class or function matching the name.
+ * Given an unresolved name, returns the likely collection of
+ * [MavenClassRegistryBase.LibraryImportData] objects for the maven.google.com artifacts
+ * containing a class or function matching the name.
*
- * @param name simple or fully-qualified name typed by the user. May correspond to a class name (any files) or a top-level Kotlin function
- * name (Kotlin files only).
+ * @param name simple or fully-qualified name typed by the user. May correspond to a class name
+ * (any files) or a top-level Kotlin function name (Kotlin files only).
+ * @param receiverType the fully-qualified name of the receiver type, if any, [ALL_RECEIVER_TYPES]
+ * if results for all receiver types should be returned, or `null` otherwise.
*/
- abstract fun findLibraryData(name: String, useAndroidX: Boolean, completionFileType: FileType?): Collection<LibraryImportData>
+ abstract fun findLibraryData(
+ name: String,
+ receiverType: String?,
+ useAndroidX: Boolean,
+ completionFileType: FileType?
+ ): Collection<LibraryImportData>
/**
- * For the given runtime artifact, if Kotlin is the adopted language, the corresponding ktx library is provided.
+ * For the given runtime artifact, if Kotlin is the adopted language, the corresponding ktx
+ * library is provided.
*/
abstract fun findKtxLibrary(artifact: String): String?
- /**
- * Returns a collection of [Coordinate].
- */
+ /** Returns a collection of [Coordinate]. */
abstract fun getCoordinates(): Collection<Coordinate>
- /**
- * For the given runtime artifact, if it also requires an annotation processor, provide it.
- */
+ /** For the given runtime artifact, if it also requires an annotation processor, provide it. */
fun findAnnotationProcessor(artifact: String): String? {
return when (artifact) {
"androidx.room:room-runtime",
@@ -73,17 +77,24 @@
}
/**
- * For the given artifact, if it also requires extra artifacts for proper functionality, provide it.
+ * For the given artifact, if it also requires extra artifacts for proper functionality, provide
+ * it.
*
* This is to handle those special cases. For example, for an unresolved symbol "@Preview",
- * "androidx.compose.ui:ui-tooling-preview" is one of the suggested artifacts to import based on the extracted
- * contents from the GMaven index file. However, this is not enough -"androidx.compose.ui:ui-tooling" should be added
- * on instead. So we just provide both in the end.
+ * "androidx.compose.ui:ui-tooling-preview" is one of the suggested artifacts to import based on
+ * the extracted contents from the GMaven index file. However, this is not enough
+ * -"androidx.compose.ui:ui-tooling" should be added on instead. So we just provide both in the
+ * end.
*/
fun findExtraArtifacts(artifact: String): Map<String, DependencyType> {
return when (artifact) {
- "androidx.compose.ui:ui-tooling-preview" -> mapOf("androidx.compose.ui:ui-tooling" to DependencyType.DEBUG_IMPLEMENTATION)
+ "androidx.compose.ui:ui-tooling-preview" ->
+ mapOf("androidx.compose.ui:ui-tooling" to DependencyType.DEBUG_IMPLEMENTATION)
else -> emptyMap()
}
}
-}
\ No newline at end of file
+
+ companion object {
+ const val ALL_RECEIVER_TYPES = "*"
+ }
+}
diff --git a/android/src/com/android/tools/idea/model/MergedManifestInfo.java b/android/src/com/android/tools/idea/model/MergedManifestInfo.java
index 016b94d..0cec242 100644
--- a/android/src/com/android/tools/idea/model/MergedManifestInfo.java
+++ b/android/src/com/android/tools/idea/model/MergedManifestInfo.java
@@ -23,9 +23,9 @@
import com.android.manifmerger.ManifestMerger2;
import com.android.manifmerger.MergingReport;
import com.android.manifmerger.XmlDocument;
-import com.android.tools.idea.gradle.plugin.AndroidPluginInfo;
import com.android.tools.idea.project.SyncTimestampUtil;
import com.android.tools.idea.projectsystem.AndroidModuleSystem;
+import com.android.tools.idea.projectsystem.AndroidProjectSystem;
import com.android.tools.idea.projectsystem.ManifestOverrides;
import com.android.tools.idea.projectsystem.MergedManifestContributors;
import com.android.tools.idea.projectsystem.ProjectSystemUtil;
@@ -33,7 +33,6 @@
import com.android.utils.ILogger;
import com.android.utils.NullLogger;
import com.android.utils.Pair;
-import com.android.utils.XmlUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.intellij.openapi.application.ApplicationManager;
@@ -55,6 +54,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -322,8 +322,6 @@
ManifestMerger2.Invoker manifestMergerInvoker = ManifestMerger2.newMerger(mainManifestFile, logger, mergeType);
manifestMergerInvoker.withFeatures(ManifestMerger2.Invoker.Feature.SKIP_BLAME, ManifestMerger2.Invoker.Feature.SKIP_XML_STRING, ManifestMerger2.Invoker.Feature.KEEP_GOING_AFTER_ERRORS);
- if(!isVersionAtLeast7_4_0(facet.getModule().getProject()))
- manifestMergerInvoker.withFeatures(ManifestMerger2.Invoker.Feature.DISABLE_STRIP_LIBRARY_TARGET_SDK);
manifestMergerInvoker.addFlavorAndBuildTypeManifests(VfsUtilCore.virtualToIoFiles(flavorAndBuildTypeManifests).toArray(new File[0]));
manifestMergerInvoker.addNavigationFiles(VfsUtilCore.virtualToIoFiles(navigationFiles));
manifestMergerInvoker.withProcessCancellationChecker(ProgressManager::checkCanceled);
@@ -391,6 +389,14 @@
}
});
+ Project project = facet.getModule().getProject();
+ AndroidProjectSystem projectSystem = ProjectSystemUtil.getProjectSystem(project);
+ Optional<MergedManifestInfoToken<AndroidProjectSystem>> maybeToken =
+ Arrays.stream(MergedManifestInfoToken.EP_NAME.getExtensions(project))
+ .filter(t -> t.isApplicable(projectSystem))
+ .findFirst();
+ maybeToken.ifPresent(token -> token.withProjectSystemFeatures(projectSystem, manifestMergerInvoker));
+
return manifestMergerInvoker.merge();
}
@@ -421,11 +427,4 @@
}
return null;
}
-
- private static boolean isVersionAtLeast7_4_0(Project project) {
- AndroidPluginInfo androidPluginInfo = AndroidPluginInfo.findFromModel(project);
- return androidPluginInfo != null &&
- androidPluginInfo.getPluginVersion() != null &&
- androidPluginInfo.getPluginVersion().isAtLeast(7, 4, 0);
- }
}
\ No newline at end of file
diff --git a/android/src/com/android/tools/idea/project/DefaultModuleSystem.kt b/android/src/com/android/tools/idea/project/DefaultModuleSystem.kt
index f1895f4..609291c 100644
--- a/android/src/com/android/tools/idea/project/DefaultModuleSystem.kt
+++ b/android/src/com/android/tools/idea/project/DefaultModuleSystem.kt
@@ -266,7 +266,6 @@
val applicationRClassConstantIds: Key<Boolean> = Key.create(::applicationRClassConstantIds.qualifiedName<DefaultModuleSystem>())
val testRClassConstantIds: Key<Boolean> = Key.create(::testRClassConstantIds.qualifiedName<DefaultModuleSystem>())
val useAndroidX: Key<Boolean> = Key.create(::useAndroidX.qualifiedName<DefaultModuleSystem>())
- val enableVcsInfo: Key<Boolean> = Key.create(::enableVcsInfo.qualifiedName<DefaultModuleSystem>())
}
override var usesCompose: Boolean by UserData(Keys.usesCompose, false)
@@ -302,8 +301,6 @@
override var useAndroidX: Boolean by UserData(Keys.useAndroidX, false)
- override var enableVcsInfo: Boolean by UserData(Keys.enableVcsInfo, false)
-
override val moduleDependencies: ModuleDependencies
get() = StudioModuleDependencies(module)
}
diff --git a/android/src/com/android/tools/idea/project/DefaultProjectSystem.kt b/android/src/com/android/tools/idea/project/DefaultProjectSystem.kt
index ddd5fda..0bb037f 100644
--- a/android/src/com/android/tools/idea/project/DefaultProjectSystem.kt
+++ b/android/src/com/android/tools/idea/project/DefaultProjectSystem.kt
@@ -138,7 +138,7 @@
}
}
- override fun validateRunConfiguration(runConfiguration: RunConfiguration): List<ValidationError> {
+ override fun validateRunConfiguration(runConfiguration: RunConfiguration, quickFixCallback: Runnable?): List<ValidationError> {
return emptyList()
}
diff --git a/android/src/com/android/tools/idea/res/ToggleResourceTraceAction.kt b/android/src/com/android/tools/idea/res/ToggleResourceTraceAction.kt
index 508da5d..d9fde63 100644
--- a/android/src/com/android/tools/idea/res/ToggleResourceTraceAction.kt
+++ b/android/src/com/android/tools/idea/res/ToggleResourceTraceAction.kt
@@ -15,6 +15,7 @@
*/
package com.android.tools.idea.res
+import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.ToggleAction
import com.intellij.openapi.application.ex.ApplicationInfoEx
@@ -38,6 +39,10 @@
ResourceUpdateTraceSettings.getInstance().enabled = state
}
+ override fun getActionUpdateThread(): ActionUpdateThread {
+ return ActionUpdateThread.BGT
+ }
+
override fun update(event: AnActionEvent) {
super.update(event)
event.presentation.isVisible = ApplicationInfoEx.getInstanceEx().isEAP
diff --git a/android/src/com/android/tools/idea/run/AndroidActivityRunLineMarkerContributor.kt b/android/src/com/android/tools/idea/run/AndroidActivityRunLineMarkerContributor.kt
new file mode 100644
index 0000000..bd24774
--- /dev/null
+++ b/android/src/com/android/tools/idea/run/AndroidActivityRunLineMarkerContributor.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.run
+
+import com.android.SdkConstants
+import com.android.tools.idea.run.configuration.getPsiClass
+import com.intellij.execution.JavaExecutionUtil
+import com.intellij.execution.lineMarker.ExecutorAction
+import com.intellij.execution.lineMarker.RunLineMarkerContributor
+import com.intellij.icons.AllIcons
+import com.intellij.psi.JavaTokenType
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.InheritanceUtil
+import org.jetbrains.android.util.AndroidBundle
+import org.jetbrains.kotlin.lexer.KtTokens
+
+/**
+ * Identifies classes that implement an Android Activity and adds a gutter icon to run them.
+ * (The details of the run configuration are provided by AndroidConfigurationProducer.)
+ */
+class AndroidActivityRunLineMarkerContributor : RunLineMarkerContributor() {
+ override fun getInfo(e: PsiElement): Info? {
+ if (!e.isClassToken()) {
+ return null
+ }
+
+ val psiClass = e.getPsiClass() ?: return null
+ if (psiClass.isAndroidActivitySubclass()) {
+ val activityName = psiClass.name ?: return null
+ return Info(AllIcons.RunConfigurations.TestState.Run, ExecutorAction.getActions()) {
+ AndroidBundle.message(
+ "android.run.configuration.run",
+ JavaExecutionUtil.getPresentableClassName(activityName)!!
+ )
+ }
+ }
+ return null
+ }
+
+ private fun PsiElement.isClassToken() =
+ node.elementType == KtTokens.CLASS_KEYWORD || node.elementType == JavaTokenType.CLASS_KEYWORD
+ private fun PsiClass.isAndroidActivitySubclass() =
+ InheritanceUtil.isInheritor(this, SdkConstants.CLASS_ACTIVITY)
+}
diff --git a/android/src/com/android/tools/idea/run/AndroidRunConfigurationBase.java b/android/src/com/android/tools/idea/run/AndroidRunConfigurationBase.java
index 5392779..034821d 100644
--- a/android/src/com/android/tools/idea/run/AndroidRunConfigurationBase.java
+++ b/android/src/com/android/tools/idea/run/AndroidRunConfigurationBase.java
@@ -56,7 +56,6 @@
import java.util.Collection;
import java.util.List;
import java.util.Objects;
-import java.util.Optional;
import org.jdom.Element;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.sdk.AndroidPlatforms;
@@ -132,6 +131,10 @@
* We use a separate method for the collection so the compiler prevents us from accidentally throwing.
*/
public List<ValidationError> validate(@Nullable Executor executor) {
+ return validate(executor, null);
+ }
+
+ public List<ValidationError> validate(@Nullable Executor executor, @Nullable Runnable quickFixCallback) {
List<ValidationError> errors = new ArrayList<>();
JavaRunConfigurationModule configurationModule = getConfigurationModule();
try {
@@ -198,7 +201,7 @@
}
AndroidProjectSystem projectSystem = getProjectSystem(getProject());
- errors.addAll(projectSystem.validateRunConfiguration(this));
+ errors.addAll(projectSystem.validateRunConfiguration(this, quickFixCallback));
errors.addAll(checkConfiguration(facet));
AndroidDebuggerState androidDebuggerState = myAndroidDebuggerContext.getAndroidDebuggerState();
diff --git a/android/src/com/android/tools/idea/run/AndroidRunConfigurationExecutor.kt b/android/src/com/android/tools/idea/run/AndroidRunConfigurationExecutor.kt
index 21131d84..6374899 100644
--- a/android/src/com/android/tools/idea/run/AndroidRunConfigurationExecutor.kt
+++ b/android/src/com/android/tools/idea/run/AndroidRunConfigurationExecutor.kt
@@ -170,7 +170,7 @@
private fun notifyLiveEditService(device: IDevice, applicationId: String) {
try {
AndroidLiveLiteralDeployMonitor.startMonitor(project, applicationId, device)
- LiveEditHelper().invokeLiveEdit(liveEditService, env, applicationIdProvider, apkProvider, device)
+ LiveEditHelper().invokeLiveEdit(liveEditService, env, applicationId, apkProvider, device)
} catch (e: Exception) {
// Monitoring should always start successfully.
diff --git a/android/src/com/android/tools/idea/run/LiveEditHelper.kt b/android/src/com/android/tools/idea/run/LiveEditHelper.kt
index 9ae9cd2..ed42fe5 100644
--- a/android/src/com/android/tools/idea/run/LiveEditHelper.kt
+++ b/android/src/com/android/tools/idea/run/LiveEditHelper.kt
@@ -22,9 +22,15 @@
import java.nio.file.Path
class LiveEditHelper {
- fun invokeLiveEdit(liveEditService: LiveEditService, env: ExecutionEnvironment, applicationIdProvider: ApplicationIdProvider, apkProvider: ApkProvider, device: IDevice) {
+ fun invokeLiveEdit(
+ liveEditService: LiveEditService,
+ env: ExecutionEnvironment,
+ applicationId: String,
+ apkProvider: ApkProvider,
+ device: IDevice
+ ) {
val liveEditApp = LiveEditApp(getApkPaths(apkProvider, device), device.getVersion().getApiLevel())
- liveEditService.notifyAppDeploy(env.getRunProfile(), env.getExecutor(), applicationIdProvider.packageName, device, liveEditApp)
+ liveEditService.notifyAppDeploy(env.getRunProfile(), env.getExecutor(), applicationId, device, liveEditApp)
}
fun getApkPaths(apkProvider: ApkProvider, device: IDevice): Set<Path> {
diff --git a/android/src/com/android/tools/idea/run/blaze/BlazeAndroidConfigurationExecutor.kt b/android/src/com/android/tools/idea/run/blaze/BlazeAndroidConfigurationExecutor.kt
index d624ffc..2916903 100644
--- a/android/src/com/android/tools/idea/run/blaze/BlazeAndroidConfigurationExecutor.kt
+++ b/android/src/com/android/tools/idea/run/blaze/BlazeAndroidConfigurationExecutor.kt
@@ -133,7 +133,7 @@
LiveEditHelper().invokeLiveEdit(
liveEditService,
env,
- applicationIdProvider,
+ applicationId,
apkProvider,
device
) // Notify listeners of the deployment.
diff --git a/android/src/com/android/tools/idea/run/deployment/DevicesSelectedService.java b/android/src/com/android/tools/idea/run/deployment/DevicesSelectedService.java
index f22adbd..607e9e3 100644
--- a/android/src/com/android/tools/idea/run/deployment/DevicesSelectedService.java
+++ b/android/src/com/android/tools/idea/run/deployment/DevicesSelectedService.java
@@ -210,14 +210,26 @@
return supplier.getDialogTargets();
}
- void setTargetsSelectedWithDialog(@NotNull Set<Target> targetsSelectedWithDialog) {
+ /**
+ * Updates the currently-persisted selected device state with the new set of selected targets.
+ *
+ * RunningDeviceTargets will be replaced first by any pre-existing launchable targets with the same device key, and alternatively by
+ * defaultLaunchTargets.
+ */
+ void setTargetsSelectedWithDialog(@NotNull Set<Target> targetsSelectedWithDialog, @NotNull List<Target> defaultLaunchTargets) {
var state = myPersistentStateComponent.getState(getSelectedRunConfiguration());
- TargetsForWritingSupplier supplier = new TargetsForWritingSupplier(state.getTargetsSelectedWithDialog(), targetsSelectedWithDialog);
+
+ TargetsForWritingSupplier supplier =
+ new TargetsForWritingSupplier(state.getTargetsSelectedWithDialog(), targetsSelectedWithDialog, defaultLaunchTargets);
state.setRunningDeviceTargetsSelectedWithDialog(supplier.getDialogRunningDeviceTargets());
state.setTargetsSelectedWithDialog(supplier.getDialogTargets());
}
+ void setTargetsSelectedWithDialog(@NotNull Set<Target> targetsSelectedWithDialog) {
+ setTargetsSelectedWithDialog(targetsSelectedWithDialog, Collections.emptyList());
+ }
+
@Nullable
private RunConfiguration getSelectedRunConfiguration() {
var configurationAndSettings = myRunManager.getSelectedConfiguration();
diff --git a/android/src/com/android/tools/idea/run/deployment/Heading.java b/android/src/com/android/tools/idea/run/deployment/Heading.java
index 9b7b9fd..7ca6d8a 100644
--- a/android/src/com/android/tools/idea/run/deployment/Heading.java
+++ b/android/src/com/android/tools/idea/run/deployment/Heading.java
@@ -15,6 +15,7 @@
*/
package com.android.tools.idea.run.deployment;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import org.jetbrains.annotations.NotNull;
@@ -26,6 +27,12 @@
private Heading() {
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@Override
public void update(@NotNull AnActionEvent event) {
event.getPresentation().setEnabled(false);
diff --git a/android/src/com/android/tools/idea/run/deployment/OWNERS b/android/src/com/android/tools/idea/run/deployment/OWNERS
index 7d3bdae..16ea468 100644
--- a/android/src/com/android/tools/idea/run/deployment/OWNERS
+++ b/android/src/com/android/tools/idea/run/deployment/OWNERS
@@ -1,5 +1,5 @@
estebandlc@google.com
-juancnuno@google.com
+rosej@google.com
rpaquay@google.com
# IntelliJ Platform SDK merges
diff --git a/android/src/com/android/tools/idea/run/deployment/SelectMultipleDevicesDialog.java b/android/src/com/android/tools/idea/run/deployment/SelectMultipleDevicesDialog.java
index 34cd6f5..acb94c3 100644
--- a/android/src/com/android/tools/idea/run/deployment/SelectMultipleDevicesDialog.java
+++ b/android/src/com/android/tools/idea/run/deployment/SelectMultipleDevicesDialog.java
@@ -107,7 +107,10 @@
super.doOKAction();
assert myTable != null;
- myDevicesSelectedServiceGetInstance.apply(myProject).setTargetsSelectedWithDialog(myTable.getSelectedTargets());
+
+ List<Target> defaultLaunchTargets =
+ myDevices.stream().filter(VirtualDevice.class::isInstance).<Target>map(device -> new QuickBootTarget(device.key())).toList();
+ myDevicesSelectedServiceGetInstance.apply(myProject).setTargetsSelectedWithDialog(myTable.getSelectedTargets(), defaultLaunchTargets);
}
@VisibleForTesting
@@ -153,7 +156,8 @@
}
@VisibleForTesting
- @NotNull SelectMultipleDevicesDialogTable getTable() {
+ @NotNull
+ SelectMultipleDevicesDialogTable getTable() {
assert myTable != null;
return myTable;
}
diff --git a/android/src/com/android/tools/idea/run/deployment/TargetsForWritingSupplier.java b/android/src/com/android/tools/idea/run/deployment/TargetsForWritingSupplier.java
index c67494d..84a91a6 100644
--- a/android/src/com/android/tools/idea/run/deployment/TargetsForWritingSupplier.java
+++ b/android/src/com/android/tools/idea/run/deployment/TargetsForWritingSupplier.java
@@ -15,8 +15,10 @@
*/
package com.android.tools.idea.run.deployment;
+import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -25,36 +27,45 @@
/**
* Consumes the selection from the drop down or dialog and splits it up into RunningDeviceTargets and nonRunningDeviceTargets
- * (ColdBootTarget, QuickBootTarget, BootWithSnapshotTarget) to write to DevicesSelectedService.State. For each RunningDeviceTarget in the
- * selection, the corresponding nonRunningDeviceTarget is kept from oldTargets. So the original target selection isn't lost when the device
- * is stopped.
+ * (ColdBootTarget, QuickBootTarget, BootWithSnapshotTarget) to write to DevicesSelectedService.State.
+ *
+ * For each RunningDeviceTarget in the selection, the corresponding nonRunningDeviceTarget is kept from oldTargets, so the original target
+ * selection isn't lost when the device is stopped. If there is no corresponding oldTarget, uses the default launch target (i.e. quickboot)
+ * for virtual devices.
*/
final class TargetsForWritingSupplier {
private final @NotNull Collection<RunningDeviceTarget> myRunningDeviceTargets;
private final @NotNull Collection<Target> myTargets;
TargetsForWritingSupplier(@Nullable Target oldTarget, @Nullable Target newTarget) {
- this(DeploymentCollections.toList(oldTarget), DeploymentCollections.toList(newTarget));
+ this(DeploymentCollections.toList(oldTarget), DeploymentCollections.toList(newTarget), Collections.emptyList());
}
/**
* @param oldTargets either the list from DevicesSelectedService.State.targetSelectedWithDropDown or targetsSelectedWithDialog. None of
* these will be RunningDeviceTargets.
* @param newTargets the new selection from the drop down or dialog. May contain RunningDeviceTargets.
+ * @param defaultLaunchTargets targets to be used in place of RunningDeviceTargets if there is no existing non-RunningDeviceTarget for the device.
*/
- TargetsForWritingSupplier(@NotNull Collection<Target> oldTargets, @NotNull Collection<Target> newTargets) {
+ TargetsForWritingSupplier(@NotNull Collection<Target> oldTargets, @NotNull Collection<Target> newTargets, Collection<Target> defaultLaunchTargets) {
Map<Key, Target> keyToTargetMap = oldTargets.stream().collect(Collectors.toMap(Target::getDeviceKey, target -> target));
+ Map<Key, Target> keyToDefaultLaunchTargetMap = defaultLaunchTargets.stream().collect(Collectors.toMap(Target::getDeviceKey, target -> target));
int size = newTargets.size();
myRunningDeviceTargets = new ArrayList<>(size);
myTargets = new ArrayList<>(size);
- newTargets.forEach(newTarget -> {
+ for (Target newTarget : newTargets) {
if (newTarget instanceof RunningDeviceTarget) {
Target oldTarget = keyToTargetMap.get(newTarget.getDeviceKey());
if (oldTarget != null) {
myTargets.add(oldTarget);
+ } else {
+ Target defaultTarget = keyToDefaultLaunchTargetMap.get(newTarget.getDeviceKey());
+ if (defaultTarget != null) {
+ myTargets.add(defaultTarget);
+ }
}
myRunningDeviceTargets.add((RunningDeviceTarget)newTarget);
@@ -62,7 +73,7 @@
else {
myTargets.add(newTarget);
}
- });
+ }
}
@NotNull Optional<RunningDeviceTarget> getDropDownRunningDeviceTarget() {
diff --git a/android/src/com/android/tools/idea/run/deployment/Updater.java b/android/src/com/android/tools/idea/run/deployment/Updater.java
index 2d1e39d..ec5f3ca 100644
--- a/android/src/com/android/tools/idea/run/deployment/Updater.java
+++ b/android/src/com/android/tools/idea/run/deployment/Updater.java
@@ -181,6 +181,10 @@
}
}
+ /**
+ * Given that we are in the "multiple devices" selection mode, updates the set of selected devices based on the currently-existing
+ * devices. For example, this might reduce the count of devices when a physical device is unplugged.
+ */
private void updateInToolbarForMultipleDevices() {
Set<Target> selectedTargets = myDevicesSelectedService.getTargetsSelectedWithDialog(myDevices);
@@ -190,7 +194,7 @@
.collect(Collectors.toSet());
if (selectedTargets.retainAll(targets)) {
- myDevicesSelectedService.setTargetsSelectedWithDialog(selectedTargets);
+ myDevicesSelectedService.setTargetsSelectedWithDialog(selectedTargets, Collections.emptyList());
}
if (selectedTargets.isEmpty()) {
diff --git a/android/src/com/android/tools/idea/run/editor/AndroidDebuggerPanel.java b/android/src/com/android/tools/idea/run/editor/AndroidDebuggerPanel.java
index 97bf097..49f23a8 100644
--- a/android/src/com/android/tools/idea/run/editor/AndroidDebuggerPanel.java
+++ b/android/src/com/android/tools/idea/run/editor/AndroidDebuggerPanel.java
@@ -96,7 +96,7 @@
AndroidDebuggerConfigurable<AndroidDebuggerState> configurable = getConfigurable(androidDebugger);
if (configurable != null) {
- configurable.applyTo(myAndroidDebuggerContext.getAndroidDebuggerState(androidDebugger.getId()));
+ configurable.applyTo(androidDebuggerContext.getAndroidDebuggerState(androidDebugger.getId()));
}
}
diff --git a/android/src/com/android/tools/idea/run/editor/AndroidRunConfigurationEditor.java b/android/src/com/android/tools/idea/run/editor/AndroidRunConfigurationEditor.java
index fbab885..953cf38 100644
--- a/android/src/com/android/tools/idea/run/editor/AndroidRunConfigurationEditor.java
+++ b/android/src/com/android/tools/idea/run/editor/AndroidRunConfigurationEditor.java
@@ -16,6 +16,7 @@
package com.android.tools.idea.run.editor;
import com.android.tools.idea.execution.common.debug.AndroidDebuggerContext;
+import com.android.tools.idea.flags.StudioFlags;
import com.android.tools.idea.run.AndroidRunConfigurationBase;
import com.android.tools.idea.run.AndroidRunConfigurationModule;
import com.android.tools.idea.run.ConfigurationSpecificEditor;
@@ -126,7 +127,9 @@
}
myAndroidProfilersPanel = new AndroidProfilersPanel(project, config.getProfilerState());
- myTabbedPane.add("Profiling", myAndroidProfilersPanel.getComponent());
+ if(!StudioFlags.PROFILER_TASK_BASED_UX.get()) {
+ myTabbedPane.add("Profiling", myAndroidProfilersPanel.getComponent());
+ }
myConfigurationSpecificEditor = configurationSpecificEditorFactory.apply(myModuleSelector);
Disposer.register(this, myConfigurationSpecificEditor);
@@ -138,7 +141,7 @@
myShowLogcatCheckBox.setVisible(showLogcatCheckbox);
- checkValidationResults(config.validate(null));
+ checkValidationResults(config.validate(null, this::fireEditorStateChanged));
}
/**
diff --git a/android/src/com/android/tools/idea/stats/ShowStatisticsViewerAction.java b/android/src/com/android/tools/idea/stats/ShowStatisticsViewerAction.java
index 491ea1f..13ffb44 100644
--- a/android/src/com/android/tools/idea/stats/ShowStatisticsViewerAction.java
+++ b/android/src/com/android/tools/idea/stats/ShowStatisticsViewerAction.java
@@ -15,6 +15,7 @@
*/
package com.android.tools.idea.stats;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import org.jetbrains.annotations.NotNull;
@@ -26,6 +27,12 @@
private StatisticsViewer myStatisticsViewer;
@Override
+ @NotNull
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
+ @Override
public void update(@NotNull AnActionEvent e) {
e.getPresentation().setText(StatisticsViewer.TITLE);
}
diff --git a/android/src/com/android/tools/idea/ui/resourcemanager/actions/OpenResourceManagerAction.kt b/android/src/com/android/tools/idea/ui/resourcemanager/actions/OpenResourceManagerAction.kt
index feae89f..1bf2eab 100644
--- a/android/src/com/android/tools/idea/ui/resourcemanager/actions/OpenResourceManagerAction.kt
+++ b/android/src/com/android/tools/idea/ui/resourcemanager/actions/OpenResourceManagerAction.kt
@@ -33,18 +33,13 @@
override fun update(e: AnActionEvent) {
val project = e.project
- e.presentation.isEnabledAndVisible = project != null && ProjectFacetManager.getInstance(project).hasFacets(AndroidFacet.ID)
+ e.presentation.isEnabledAndVisible = project != null
+ && ProjectFacetManager.getInstance(project).hasFacets(AndroidFacet.ID)
+ && ToolWindowManager.getInstance(project).getToolWindow(RESOURCE_EXPLORER_TOOL_WINDOW_ID) != null
}
override fun actionPerformed(e: AnActionEvent) {
- val project = e.getData(CommonDataKeys.PROJECT)
- if (project != null) {
- showResourceExplorer(project)
- }
- }
-
- private fun showResourceExplorer(project: Project) {
- val toolWindow = ToolWindowManager.getInstance(project).getToolWindow(RESOURCE_EXPLORER_TOOL_WINDOW_ID)!!
- toolWindow.show(null)
+ val project = e.getData(CommonDataKeys.PROJECT) ?: return
+ ToolWindowManager.getInstance(project).getToolWindow(RESOURCE_EXPLORER_TOOL_WINDOW_ID)?.show(null)
}
}
\ No newline at end of file
diff --git a/android/src/com/android/tools/idea/ui/validation/validators/PathValidator.kt b/android/src/com/android/tools/idea/ui/validation/validators/PathValidator.kt
index dd40a92..6fbb6f7 100644
--- a/android/src/com/android/tools/idea/ui/validation/validators/PathValidator.kt
+++ b/android/src/com/android/tools/idea/ui/validation/validators/PathValidator.kt
@@ -19,6 +19,7 @@
import com.android.tools.adtui.validation.Validator
import com.android.tools.adtui.validation.Validator.Result
import com.android.tools.adtui.validation.Validator.Severity
+import com.android.tools.idea.ui.validation.validators.PathValidator.Companion.createDefault
import com.google.common.base.CharMatcher
import com.intellij.openapi.application.Application
import com.intellij.openapi.application.ApplicationNamesInfo
@@ -32,7 +33,6 @@
import java.io.File
import java.nio.file.Path
import java.util.Locale
-import kotlin.streams.toList
private val logger: Logger get() = logger<PathValidator>()
@@ -46,13 +46,15 @@
*/
@Immutable
class PathValidator
-/**
- * Constructs a class that will validate a path against the various passed in rules, returning
- * a readable message if something goes wrong. A name describing the purpose of the path should
- * be included as it will be used in the error messages when applicable.
- */ private constructor(private val pathName: String,
+ /**
+ * Constructs a class that will validate a path against the various passed in rules, returning
+ * a readable message if something goes wrong. A name describing the purpose of the path should
+ * be included as it will be used in the error messages when applicable.
+ */
+ private constructor(val pathName: String,
@get:TestOnly val errors: Iterable<Rule>,
private val warnings: Iterable<Rule>) : Validator<Path> {
+
/**
* Validate that the target location passes all tests.
*
diff --git a/android/src/com/android/tools/idea/ui/validation/validators/StringPathValidator.kt b/android/src/com/android/tools/idea/ui/validation/validators/StringPathValidator.kt
new file mode 100644
index 0000000..12a3fb2
--- /dev/null
+++ b/android/src/com/android/tools/idea/ui/validation/validators/StringPathValidator.kt
@@ -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.ui.validation.validators
+
+import com.android.tools.adtui.validation.Validator
+import java.nio.file.InvalidPathException
+import java.nio.file.Paths
+
+class StringPathValidator(private val pathValidator: PathValidator) : Validator<String> {
+
+ override fun validate(value: String): Validator.Result {
+ val path = try {
+ Paths.get(value)
+ }
+ catch (e: InvalidPathException) {
+ return Validator.Result(Validator.Severity.ERROR, "${pathValidator.pathName} in not a valid file system path")
+ }
+ return pathValidator.validate(path)
+ }
+}
\ No newline at end of file
diff --git a/android/src/com/android/tools/idea/welcome/install/InstallableComponent.kt b/android/src/com/android/tools/idea/welcome/install/InstallableComponent.kt
index 7fbab26..213a83b 100644
--- a/android/src/com/android/tools/idea/welcome/install/InstallableComponent.kt
+++ b/android/src/com/android/tools/idea/welcome/install/InstallableComponent.kt
@@ -25,6 +25,7 @@
import com.android.tools.idea.welcome.wizard.getSizeLabel
import com.android.tools.idea.wizard.dynamic.DynamicWizardStep
import com.android.tools.idea.wizard.model.ModelWizardStep
+import com.intellij.openapi.diagnostic.thisLogger
private val PROGRESS_LOGGER = StudioLoggerProgressIndicator(InstallableComponent::class.java)
@@ -111,8 +112,11 @@
else -> isSelectedByDefault()
}
)
- isInstalled = sdkHandler != null && packagesToInstall.isEmpty() && unavailablePackages.isEmpty()
- isUnavailable = sdkHandler == null || unavailablePackages.isNotEmpty()
+ isInstalled = packagesToInstall.isEmpty() && unavailablePackages.isEmpty()
+ isUnavailable = unavailablePackages.isNotEmpty()
+ if (isUnavailable) {
+ thisLogger().warn("$name depends on the the packages that are not available: ${unavailablePackages.joinToString(", ")}")
+ }
}
override fun toggle(isSelected: Boolean) {
diff --git a/android/src/org/jetbrains/android/AndroidPlugin.java b/android/src/org/jetbrains/android/AndroidPlugin.java
index 8d8ba66..e3e4152 100644
--- a/android/src/org/jetbrains/android/AndroidPlugin.java
+++ b/android/src/org/jetbrains/android/AndroidPlugin.java
@@ -12,6 +12,7 @@
import com.google.wireless.android.sdk.stats.AndroidStudioEvent;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Anchor;
@@ -71,6 +72,11 @@
Project project = e.getProject();
e.getPresentation().setEnabledAndVisible(project != null && ProjectSystemUtil.requiresAndroidModel(project));
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
};
actionManager.registerAction(groupId, group);
((DefaultActionGroup)parentGroup).add(group, new Constraints(Anchor.BEFORE, "Android.GenerateSignedApk"), actionManager);
diff --git a/android/src/org/jetbrains/android/actions/AndroidConnectDebuggerAction.java b/android/src/org/jetbrains/android/actions/AndroidConnectDebuggerAction.java
index 3579657..7faade6 100644
--- a/android/src/org/jetbrains/android/actions/AndroidConnectDebuggerAction.java
+++ b/android/src/org/jetbrains/android/actions/AndroidConnectDebuggerAction.java
@@ -5,6 +5,7 @@
import com.android.tools.idea.IdeInfo;
import com.android.tools.idea.execution.common.debug.utils.AndroidConnectDebugger;
import com.intellij.facet.ProjectFacetManager;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
@@ -34,6 +35,12 @@
}
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
final Project project = e.getProject();
diff --git a/android/src/org/jetbrains/android/actions/AndroidToolsActionGroup.java b/android/src/org/jetbrains/android/actions/AndroidToolsActionGroup.java
index 6ac2b67..6c08fcb 100644
--- a/android/src/org/jetbrains/android/actions/AndroidToolsActionGroup.java
+++ b/android/src/org/jetbrains/android/actions/AndroidToolsActionGroup.java
@@ -18,6 +18,7 @@
import com.android.tools.idea.sdk.IdeSdks;
import com.google.common.annotations.VisibleForTesting;
import com.intellij.facet.ProjectFacetManager;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
@@ -32,6 +33,12 @@
* IDE users developing cross-platform applications (e.g. with react native) using android tools, e.g. AVD Manager.
*/
public class AndroidToolsActionGroup extends DefaultActionGroup implements DumbAware {
+
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
@Override
public void update(@NotNull AnActionEvent e) {
final Project project = e.getData(CommonDataKeys.PROJECT);
diff --git a/android/src/org/jetbrains/android/dom/inspections/MavenClassResolverUtils.kt b/android/src/org/jetbrains/android/dom/inspections/MavenClassResolverUtils.kt
index 3279737..ac86fe7 100644
--- a/android/src/org/jetbrains/android/dom/inspections/MavenClassResolverUtils.kt
+++ b/android/src/org/jetbrains/android/dom/inspections/MavenClassResolverUtils.kt
@@ -26,27 +26,21 @@
import org.jetbrains.android.refactoring.isAndroidx
/**
- * Returns a collection of [LocalQuickFix] by querying [MavenClassRegistryManager.getMavenClassRegistry].
+ * Returns a collection of [LocalQuickFix] by querying
+ * [MavenClassRegistryManager.getMavenClassRegistry].
*/
internal fun MavenClassRegistryManager.collectFixesFromMavenClassRegistry(
- className: String, project: Project, completionFileType: FileType?): List<LocalQuickFix> {
- val fixes = mutableListOf<LocalQuickFix>()
+ className: String,
+ project: Project,
+ completionFileType: FileType?
+): List<LocalQuickFix> {
val useAndroidX = project.isAndroidx()
- this.getMavenClassRegistry()
- .findLibraryData(className, useAndroidX, completionFileType)
- .asSequence()
+ return getMavenClassRegistry()
+ .findLibraryData(className, null, useAndroidX, completionFileType)
.map {
- val resolvedArtifact = if (useAndroidX) {
- AndroidxNameUtils.getCoordinateMapping(it.artifact)
- }
- else {
- it.artifact
- }
-
- fixes.add(AndroidMavenImportFix(className, resolvedArtifact, it.version))
+ val resolvedArtifact =
+ if (useAndroidX) AndroidxNameUtils.getCoordinateMapping(it.artifact) else it.artifact
+ AndroidMavenImportFix(className, resolvedArtifact, it.version)
}
- .toList()
-
- return fixes.toList()
}
diff --git a/android/src/org/jetbrains/android/intentions/AndroidCreateOnClickHandlerAction.java b/android/src/org/jetbrains/android/intentions/AndroidCreateOnClickHandlerAction.java
index 7978107..0d47296 100644
--- a/android/src/org/jetbrains/android/intentions/AndroidCreateOnClickHandlerAction.java
+++ b/android/src/org/jetbrains/android/intentions/AndroidCreateOnClickHandlerAction.java
@@ -60,7 +60,6 @@
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.asJava.classes.KtLightClass;
import org.jetbrains.kotlin.idea.KotlinLanguage;
-import org.jetbrains.kotlin.idea.core.OldGenerateUtilKt;
import org.jetbrains.kotlin.psi.KtClassOrObject;
import org.jetbrains.kotlin.psi.KtDeclaration;
import org.jetbrains.kotlin.psi.KtNamedFunction;
@@ -199,7 +198,7 @@
KtNamedFunction namedFunction = new KtPsiFactory(origin.getProject())
.createFunction("fun " + methodName + "(" + varName + ": " + methodParamType + ") {}");
KtDeclaration anchor = Iterables.getLast(origin.getDeclarations(), null);
- OldGenerateUtilKt.insertMembersAfterAndReformat(null, origin, namedFunction, anchor);
+ GenerateUtilsKt.insertMembersAfterAndReformat(null, origin, namedFunction, anchor);
}
return null;
}
diff --git a/android/src/org/jetbrains/android/intentions/generateUtils.kt b/android/src/org/jetbrains/android/intentions/generateUtils.kt
new file mode 100644
index 0000000..04c6847
--- /dev/null
+++ b/android/src/org/jetbrains/android/intentions/generateUtils.kt
@@ -0,0 +1,61 @@
+/*
+ * 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 org.jetbrains.android.intentions
+
+import com.intellij.openapi.application.runWriteAction
+import com.intellij.openapi.editor.Editor
+import com.intellij.psi.PsiElement
+import com.intellij.psi.codeStyle.CodeStyleManager
+import org.jetbrains.kotlin.idea.base.codeInsight.ShortenReferencesFacility
+import org.jetbrains.kotlin.idea.core.insertMembersAfter
+import org.jetbrains.kotlin.idea.core.moveCaretIntoGeneratedElement
+import org.jetbrains.kotlin.psi.KtClassOrObject
+import org.jetbrains.kotlin.psi.KtDeclaration
+
+internal fun <T : KtDeclaration> insertMembersAfterAndReformat(
+ editor: Editor?,
+ classOrObject: KtClassOrObject,
+ members: Collection<T>,
+ anchor: PsiElement? = null,
+ getAnchor: (KtDeclaration) -> PsiElement? = { null },
+): List<T> {
+ val codeStyleManager = CodeStyleManager.getInstance(classOrObject.project)
+ return runWriteAction {
+ val insertedMembersElementPointers = insertMembersAfter(editor, classOrObject, members, anchor, getAnchor)
+ val firstElement = insertedMembersElementPointers.firstOrNull() ?: return@runWriteAction emptyList()
+
+ fun insertedMembersElements() = insertedMembersElementPointers.mapNotNull { it.element }
+
+ for (added in insertedMembersElements()) {
+ ShortenReferencesFacility.getInstance().shorten(added)
+ }
+ if (editor != null) {
+ firstElement.element?.let { moveCaretIntoGeneratedElement(editor, it) }
+ }
+
+ insertedMembersElementPointers.onEach { it.element?.let { element -> codeStyleManager.reformat(element) } }
+ insertedMembersElements()
+ }
+}
+
+internal fun <T : KtDeclaration> insertMembersAfterAndReformat(
+ editor: Editor?,
+ classOrObject: KtClassOrObject,
+ declaration: T,
+ anchor: PsiElement? = null
+): T {
+ return insertMembersAfterAndReformat(editor, classOrObject, listOf(declaration), anchor).single()
+}
\ No newline at end of file
diff --git a/android/testSrc/com/android/tools/idea/databinding/index/BindingXmlIndexTest.kt b/android/testSrc/com/android/tools/idea/databinding/index/BindingXmlIndexTest.kt
index 8f7f241..a57b582 100644
--- a/android/testSrc/com/android/tools/idea/databinding/index/BindingXmlIndexTest.kt
+++ b/android/testSrc/com/android/tools/idea/databinding/index/BindingXmlIndexTest.kt
@@ -17,6 +17,7 @@
import com.android.tools.idea.testing.AndroidProjectRule
import com.google.common.truth.Truth.assertThat
+import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
@@ -58,7 +59,7 @@
@Test
fun indexDataBindingLayout() {
val file = fixture.configureByText("layout.xml", """
- <layout>
+ <layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class="a.b.c.CustomBinding">
<import type="C"/>
<import type="Map<D>" alias="Dee" />
@@ -91,7 +92,7 @@
fun indexDataBindingLayout_nullValue() {
// regression for b/284046638
val file = fixture.configureByText("layout.xml", """
- <layout>
+ <layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class= >
</data>
</layout>
@@ -106,7 +107,7 @@
@Test
fun indexDataBindingLayout_emptyValue() {
val file = fixture.configureByText("layout.xml", """
- <layout>
+ <layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class="">
</data>
</layout>
@@ -567,6 +568,65 @@
)
}
+ @Test
+ fun nonLayoutFilesNotReturnedFromIndex() {
+ val layoutContents =
+ // language=XML
+ """
+ <layout xmlns:android="http://schemas.android.com/apk/res/android">
+ <LinearLayout
+ android:id="@+id/root_view"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ </LinearLayout>
+ </layout>
+ """.trimIndent()
+
+ val layoutPsiFile = fixture.addFileToProject("res/layout/some_layout.xml", layoutContents)
+ val otherPsiFile = fixture.addFileToProject("res/values/some_layout.xml", layoutContents)
+
+ runReadAction {
+ assertThat(BindingXmlIndex.getDataForFile(layoutPsiFile)).isNotNull()
+ assertThat(BindingXmlIndex.getDataForFile(otherPsiFile)).isNull()
+ }
+ }
+
+ @Test
+ fun xmlFileWithoutNamespaceNotIndexed() {
+ val unrelatedContent =
+ // language=XML
+ """
+ <abc>
+ <def>
+ <ghi />
+ </def>
+ </abc>
+ """.trimIndent()
+
+ // This file will be a false positive and will be indexed, even though it's not a layout file.
+ val unrelatedContentWithNamespace =
+ // language=XML
+ """
+ <abc xmlns:android="http://schemas.android.com/apk/res/android">
+ <def>
+ <ghi />
+ </def>
+ </abc>
+ """.trimIndent()
+
+ val unrelatedFile =
+ fixture.configureByText("layout/unrelated.xml", unrelatedContent).virtualFile
+ val unrelatedFileWithNamespace =
+ fixture.configureByText("layout/unrelatedWithNamespace.xml", unrelatedContentWithNamespace).virtualFile
+
+ val map1 = BindingXmlIndex().indexer.map(FileContentImpl.createByFile(unrelatedFile))
+ assertThat(map1).isEmpty()
+
+ val map2 = BindingXmlIndex().indexer.map(FileContentImpl.createByFile(unrelatedFileWithNamespace))
+ assertThat(map2).hasSize(1)
+ }
+
/**
* asserts all views with viewIds.
* Pairs are variable name to view type
diff --git a/android/ui/testSrc/com/android/tools/idea/ui/validation/validators/StringPathValidatorTest.kt b/android/ui/testSrc/com/android/tools/idea/ui/validation/validators/StringPathValidatorTest.kt
new file mode 100644
index 0000000..339ab3d
--- /dev/null
+++ b/android/ui/testSrc/com/android/tools/idea/ui/validation/validators/StringPathValidatorTest.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.ui.validation.validators
+
+import com.android.tools.adtui.validation.Validator
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class StringPathValidatorTest {
+
+ private val validator = StringPathValidator(PathValidator.createDefault("Test path"))
+
+ @Test
+ fun testValid() {
+ val result = validator.validate("${System.getProperty("java.io.tmpdir")}/valid/path")
+ assertThat(result.severity).isEqualTo(Validator.Severity.OK)
+ }
+
+ @Test
+ fun testInvalid() {
+ val result = validator.validate("${System.getProperty("java.io.tmpdir")}/path/with/illegal/character\u0000")
+ assertThat(result.severity).isEqualTo(Validator.Severity.ERROR)
+ assertThat(result.message).isEqualTo("Test path in not a valid file system path")
+ }
+}
\ No newline at end of file
diff --git a/apkanalyzer/src/com/android/tools/idea/apk/viewer/AnalyzeApkAction.java b/apkanalyzer/src/com/android/tools/idea/apk/viewer/AnalyzeApkAction.java
index 534bb97..d082b06 100644
--- a/apkanalyzer/src/com/android/tools/idea/apk/viewer/AnalyzeApkAction.java
+++ b/apkanalyzer/src/com/android/tools/idea/apk/viewer/AnalyzeApkAction.java
@@ -19,6 +19,7 @@
import com.android.tools.idea.projectsystem.ProjectSystemUtil;
import com.intellij.facet.ProjectFacetManager;
import com.intellij.ide.util.PropertiesComponent;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooser;
@@ -56,6 +57,11 @@
}
@Override
+ public @NotNull ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
+ @Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getProject();
if (project == null) {
diff --git a/app-inspection/api/src/com/android/tools/idea/appinspection/api/process/ProcessesModel.kt b/app-inspection/api/src/com/android/tools/idea/appinspection/api/process/ProcessesModel.kt
index 7ad2727..8f355ec 100644
--- a/app-inspection/api/src/com/android/tools/idea/appinspection/api/process/ProcessesModel.kt
+++ b/app-inspection/api/src/com/android/tools/idea/appinspection/api/process/ProcessesModel.kt
@@ -136,7 +136,7 @@
synchronized(lock) {
_processes.add(process)
- if (isProcessPreferred(process) && !isProcessPreferred(selectedProcess)) {
+ if (isProcessPreferred(process)) {
setSelectedProcess(process, autoConnected = true)
}
}
diff --git a/app-inspection/api/testSrc/com/android/tools/idea/appinspection/api/process/ProcessesModelTest.kt b/app-inspection/api/testSrc/com/android/tools/idea/appinspection/api/process/ProcessesModelTest.kt
index bf26b7d..ddb65c9 100644
--- a/app-inspection/api/testSrc/com/android/tools/idea/appinspection/api/process/ProcessesModelTest.kt
+++ b/app-inspection/api/testSrc/com/android/tools/idea/appinspection/api/process/ProcessesModelTest.kt
@@ -142,7 +142,7 @@
}
@Test
- fun newProcessDoesNotCauseSelectionToChange() {
+ fun lastPreferredProcessIsSelected() {
val testNotifier = TestProcessDiscovery()
val model = ProcessesModel(testNotifier) { it.name in listOf("A", "B") }
@@ -152,9 +152,10 @@
testNotifier.fireConnected(fakeProcessA)
testNotifier.fireConnected(fakeProcessB)
+
// Verify the added target.
assertThat(model.processes.size).isEqualTo(2)
- assertThat(model.selectedProcess!!).isSameAs(fakeProcessA)
+ assertThat(model.selectedProcess!!).isSameAs(fakeProcessB)
}
@Test
diff --git a/app-inspection/api/testSrc/com/android/tools/idea/appinspection/test/AppInspectionServiceRule.kt b/app-inspection/api/testSrc/com/android/tools/idea/appinspection/test/AppInspectionServiceRule.kt
index 8a285a8..27055f7 100644
--- a/app-inspection/api/testSrc/com/android/tools/idea/appinspection/test/AppInspectionServiceRule.kt
+++ b/app-inspection/api/testSrc/com/android/tools/idea/appinspection/test/AppInspectionServiceRule.kt
@@ -105,14 +105,10 @@
override fun before(description: Description) {
client = TransportClient(grpcServer.name)
executorService = Executors.newSingleThreadExecutor()
- streamManager =
- TransportStreamManager.createManager(
- client.transportStub,
- executorService.asCoroutineDispatcher()
- )
streamChannel =
TransportStreamChannel(stream, client.transportStub, executorService.asCoroutineDispatcher())
scope = CoroutineScope(executorService.asCoroutineDispatcher() + SupervisorJob())
+ streamManager = TransportStreamManager(client.transportStub, scope)
transport = AppInspectionTransport(client, process, streamChannel)
jarCopier = AppInspectionTestUtils.TestTransportJarCopier
targetManager = AppInspectionTargetManager(client, scope)
@@ -130,7 +126,6 @@
}
override fun after(description: Description) = runBlocking {
- TransportStreamManager.unregisterManager(streamManager)
scope.coroutineContext[Job]!!.cancelAndJoin()
executorService.shutdownNow()
timer.currentTimeNs += 1
diff --git a/app-inspection/ide/src/com/android/tools/idea/appinspection/ide/AppInspectionDiscoveryService.kt b/app-inspection/ide/src/com/android/tools/idea/appinspection/ide/AppInspectionDiscoveryService.kt
index 5fdd707..3cb0274 100644
--- a/app-inspection/ide/src/com/android/tools/idea/appinspection/ide/AppInspectionDiscoveryService.kt
+++ b/app-inspection/ide/src/com/android/tools/idea/appinspection/ide/AppInspectionDiscoveryService.kt
@@ -23,14 +23,13 @@
import com.android.tools.idea.appinspection.inspector.api.AppInspectorJar
import com.android.tools.idea.appinspection.inspector.api.process.DeviceDescriptor
import com.android.tools.idea.concurrency.AndroidCoroutineScope
-import com.android.tools.idea.concurrency.AndroidDispatchers
import com.android.tools.idea.concurrency.AndroidExecutors
import com.android.tools.idea.transport.DeployableFile
import com.android.tools.idea.transport.TransportClient
import com.android.tools.idea.transport.TransportFileManager
import com.android.tools.idea.transport.TransportService
import com.android.tools.idea.transport.TransportServiceProxy
-import com.android.tools.idea.transport.manager.TransportStreamManager
+import com.android.tools.idea.transport.manager.TransportStreamManagerService
import com.google.common.util.concurrent.MoreExecutors
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
@@ -57,8 +56,7 @@
}
private val client = TransportClient(TransportService.channelName)
- private val streamManager =
- TransportStreamManager.createManager(client.transportStub, AndroidDispatchers.workerThread)
+ private val streamManager = service<TransportStreamManagerService>().streamManager
private val applicationMessageBus = ApplicationManager.getApplication().messageBus.connect(this)
@@ -113,9 +111,7 @@
get() = service()
}
- override fun dispose() {
- TransportStreamManager.unregisterManager(streamManager)
- }
+ override fun dispose() = Unit
/**
* This uses the current [AndroidDebugBridge] to locate a device described by [device]. Return
diff --git a/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/NetworkInspectorDataSeriesTest.kt b/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/NetworkInspectorDataSeriesTest.kt
index 13a9813..a3babe2 100644
--- a/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/NetworkInspectorDataSeriesTest.kt
+++ b/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/NetworkInspectorDataSeriesTest.kt
@@ -19,15 +19,12 @@
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import studio.network.inspection.NetworkInspectorProtocol.Event
-import studio.network.inspection.NetworkInspectorProtocol.SpeedEvent
class NetworkInspectorDataSeriesTest {
@Test
fun getDataForRange() {
- val event1 =
- Event.newBuilder().setTimestamp(1000).setSpeedEvent(SpeedEvent.getDefaultInstance()).build()
- val event2 =
- Event.newBuilder().setTimestamp(2000).setSpeedEvent(SpeedEvent.getDefaultInstance()).build()
+ val event1 = speedEvent(timestampNanos = 1000)
+ val event2 = speedEvent(timestampNanos = 2000)
val source = FakeNetworkInspectorDataSource(speedEventList = listOf(event1, event2))
val foundEvents = mutableListOf<Event>()
diff --git a/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/NetworkInspectorDataSourceTest.kt b/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/NetworkInspectorDataSourceTest.kt
index 3445100..1b6e272 100644
--- a/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/NetworkInspectorDataSourceTest.kt
+++ b/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/NetworkInspectorDataSourceTest.kt
@@ -26,14 +26,10 @@
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import org.junit.Test
-import studio.network.inspection.NetworkInspectorProtocol.Event
-import studio.network.inspection.NetworkInspectorProtocol.HttpConnectionEvent
-import studio.network.inspection.NetworkInspectorProtocol.SpeedEvent
class NetworkInspectorDataSourceTest {
private val executor = Executors.newSingleThreadExecutor()
@@ -41,26 +37,8 @@
@Test
fun basicSearch() = runBlocking {
- val speedEvent =
- Event.newBuilder()
- .setTimestamp(1000)
- .setSpeedEvent(SpeedEvent.newBuilder().setRxSpeed(10).setTxSpeed(20))
- .build()
- val httpEvent =
- Event.newBuilder()
- .setTimestamp(1002)
- .setHttpConnectionEvent(
- HttpConnectionEvent.newBuilder()
- .setConnectionId(1)
- .setHttpRequestStarted(
- HttpConnectionEvent.RequestStarted.newBuilder()
- .setFields("a")
- .setMethod("http")
- .setTrace("abc")
- .setUrl("www.google.com")
- )
- )
- .build()
+ val speedEvent = speedEvent(timestampNanos = 1000, rxSpeed = 10, txSpeed = 20)
+ val httpEvent = requestStarted(id = 1, timestampNanos = 1002)
val testMessenger =
TestMessenger(scope, flowOf(speedEvent.toByteArray(), httpEvent.toByteArray()))
val dataSource = NetworkInspectorDataSourceImpl(testMessenger, scope)
@@ -77,22 +55,14 @@
@Test
fun advancedSearch(): Unit = runBlocking {
- val speedEvent1 =
- Event.newBuilder().setTimestamp(1000).setSpeedEvent(SpeedEvent.getDefaultInstance()).build()
- val speedEvent2 =
- Event.newBuilder().setTimestamp(2000).setSpeedEvent(SpeedEvent.getDefaultInstance()).build()
- val speedEvent3 =
- Event.newBuilder().setTimestamp(2000).setSpeedEvent(SpeedEvent.getDefaultInstance()).build()
- val speedEvent4 =
- Event.newBuilder().setTimestamp(3000).setSpeedEvent(SpeedEvent.getDefaultInstance()).build()
- val speedEvent5 =
- Event.newBuilder().setTimestamp(3000).setSpeedEvent(SpeedEvent.getDefaultInstance()).build()
- val speedEvent6 =
- Event.newBuilder().setTimestamp(3000).setSpeedEvent(SpeedEvent.getDefaultInstance()).build()
- val speedEvent7 =
- Event.newBuilder().setTimestamp(3001).setSpeedEvent(SpeedEvent.getDefaultInstance()).build()
- val speedEvent8 =
- Event.newBuilder().setTimestamp(6000).setSpeedEvent(SpeedEvent.getDefaultInstance()).build()
+ val speedEvent1 = speedEvent(timestampNanos = 1000)
+ val speedEvent2 = speedEvent(timestampNanos = 2000)
+ val speedEvent3 = speedEvent(timestampNanos = 2000)
+ val speedEvent4 = speedEvent(timestampNanos = 3000)
+ val speedEvent5 = speedEvent(timestampNanos = 3000)
+ val speedEvent6 = speedEvent(timestampNanos = 3000)
+ val speedEvent7 = speedEvent(timestampNanos = 3001)
+ val speedEvent8 = speedEvent(timestampNanos = 6000)
val testMessenger =
TestMessenger(
@@ -162,108 +132,20 @@
@Test
fun searchHttpData(): Unit = runBlocking {
// Request that starts in the selection range but ends outside of it.
- val httpEvent =
- Event.newBuilder()
- .setTimestamp(1002)
- .setHttpConnectionEvent(
- HttpConnectionEvent.newBuilder()
- .setConnectionId(1)
- .setHttpRequestStarted(
- HttpConnectionEvent.RequestStarted.newBuilder()
- .setFields("a")
- .setMethod("http")
- .setTrace("abc")
- .setUrl("www.google.com")
- )
- )
- .build()
- val httpEvent2 =
- Event.newBuilder()
- .setTimestamp(3000)
- .setHttpConnectionEvent(
- HttpConnectionEvent.newBuilder()
- .setConnectionId(1)
- .setHttpRequestCompleted(HttpConnectionEvent.RequestCompleted.getDefaultInstance())
- )
- .build()
+ val httpEvent = requestStarted(id = 1, timestampNanos = 1002)
+ val httpEvent2 = requestCompleted(id = 1, timestampNanos = 3000)
- // Request that starts outside of the range, but ends inside of it.
- val httpEvent3 =
- Event.newBuilder()
- .setTimestamp(44)
- .setHttpConnectionEvent(
- HttpConnectionEvent.newBuilder()
- .setConnectionId(2)
- .setHttpRequestStarted(
- HttpConnectionEvent.RequestStarted.newBuilder()
- .setFields("a")
- .setMethod("http")
- .setTrace("abc")
- .setUrl("www.google.com")
- )
- )
- .build()
- val httpEvent4 =
- Event.newBuilder()
- .setTimestamp(1534)
- .setHttpConnectionEvent(
- HttpConnectionEvent.newBuilder()
- .setConnectionId(2)
- .setHttpRequestCompleted(HttpConnectionEvent.RequestCompleted.getDefaultInstance())
- )
- .build()
+ // Request that starts outside the range, but ends inside of it.
+ val httpEvent3 = requestStarted(id = 2, timestampNanos = 44)
+ val httpEvent4 = requestCompleted(id = 2, timestampNanos = 1534)
// Request that starts and ends outside the range, but spans over it.
- val httpEvent5 =
- Event.newBuilder()
- .setTimestamp(55)
- .setHttpConnectionEvent(
- HttpConnectionEvent.newBuilder()
- .setConnectionId(3)
- .setHttpRequestStarted(
- HttpConnectionEvent.RequestStarted.newBuilder()
- .setFields("a")
- .setMethod("http")
- .setTrace("abc")
- .setUrl("www.google.com")
- )
- )
- .build()
- val httpEvent6 =
- Event.newBuilder()
- .setTimestamp(4500)
- .setHttpConnectionEvent(
- HttpConnectionEvent.newBuilder()
- .setConnectionId(3)
- .setHttpRequestCompleted(HttpConnectionEvent.RequestCompleted.getDefaultInstance())
- )
- .build()
+ val httpEvent5 = requestStarted(id = 3, timestampNanos = 55)
+ val httpEvent6 = requestCompleted(id = 3, timestampNanos = 4500)
// Request that starts and ends outside and not overlap the range.
- val httpEvent7 =
- Event.newBuilder()
- .setTimestamp(58)
- .setHttpConnectionEvent(
- HttpConnectionEvent.newBuilder()
- .setConnectionId(4)
- .setHttpRequestStarted(
- HttpConnectionEvent.RequestStarted.newBuilder()
- .setFields("a")
- .setMethod("http")
- .setTrace("abc")
- .setUrl("www.google.com")
- )
- )
- .build()
- val httpEvent8 =
- Event.newBuilder()
- .setTimestamp(67)
- .setHttpConnectionEvent(
- HttpConnectionEvent.newBuilder()
- .setConnectionId(4)
- .setHttpRequestCompleted(HttpConnectionEvent.RequestCompleted.getDefaultInstance())
- )
- .build()
+ val httpEvent7 = requestStarted(id = 4, timestampNanos = 58)
+ val httpEvent8 = requestCompleted(id = 4, timestampNanos = 67)
val testMessenger =
TestMessenger(
@@ -288,24 +170,23 @@
}
@Test
- fun cleanUpChannelOnDispose() =
- runBlocking<Unit> {
- val testMessenger =
- TestMessenger(scope, flow { throw ArithmeticException("Something went wrong!") })
- val dataSource = NetworkInspectorDataSourceImpl(testMessenger, scope)
- testMessenger.await()
- try {
- dataSource.queryForSpeedData(Range(0.0, 5.0))
- fail()
- } catch (e: Throwable) {
- assertThat(e).isInstanceOf(CancellationException::class.java)
- var cause: Throwable? = e.cause
- while (cause != null && cause !is ArithmeticException) {
- cause = cause.cause
- }
- assertThat(cause).isInstanceOf(ArithmeticException::class.java)
+ fun cleanUpChannelOnDispose() = runBlocking {
+ val testMessenger =
+ TestMessenger(scope, flow { throw ArithmeticException("Something went wrong!") })
+ val dataSource = NetworkInspectorDataSourceImpl(testMessenger, scope)
+ testMessenger.await()
+ try {
+ dataSource.queryForSpeedData(Range(0.0, 5.0))
+ fail()
+ } catch (e: Throwable) {
+ assertThat(e).isInstanceOf(CancellationException::class.java)
+ var cause: Throwable? = e.cause
+ while (cause != null && cause !is ArithmeticException) {
+ cause = cause.cause
}
+ assertThat(cause).isInstanceOf(ArithmeticException::class.java)
}
+ }
}
private class TestMessenger(override val scope: CoroutineScope, val flow: Flow<ByteArray>) :
diff --git a/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/NetworkInspectorModelTest.kt b/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/NetworkInspectorModelTest.kt
index cecb3d6..4b3650f 100644
--- a/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/NetworkInspectorModelTest.kt
+++ b/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/NetworkInspectorModelTest.kt
@@ -24,13 +24,11 @@
import com.android.tools.idea.protobuf.ByteString
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors
-import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeUnit.SECONDS
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asCoroutineDispatcher
import org.junit.Before
import org.junit.Test
-import studio.network.inspection.NetworkInspectorProtocol.Event
-import studio.network.inspection.NetworkInspectorProtocol.SpeedEvent
class NetworkInspectorModelTest {
private lateinit var model: NetworkInspectorModel
@@ -46,35 +44,13 @@
FakeNetworkInspectorDataSource(
speedEventList =
listOf(
- Event.newBuilder()
- .apply {
- timestamp = 0
- speedEvent =
- SpeedEvent.newBuilder()
- .apply {
- rxSpeed = 1
- txSpeed = 2
- }
- .build()
- }
- .build(),
- Event.newBuilder()
- .apply {
- timestamp = TimeUnit.SECONDS.toNanos(10)
- speedEvent =
- SpeedEvent.newBuilder()
- .apply {
- rxSpeed = 3
- txSpeed = 4
- }
- .build()
- }
- .build()
+ speedEvent(timestampNanos = 0, rxSpeed = 1, txSpeed = 2),
+ speedEvent(timestampNanos = SECONDS.toNanos(10), rxSpeed = 3, txSpeed = 4),
)
),
CoroutineScope(MoreExecutors.directExecutor().asCoroutineDispatcher())
)
- model.timeline.viewRange.set(0.0, TimeUnit.SECONDS.toMicros(5).toDouble())
+ model.timeline.viewRange.set(0.0, SECONDS.toMicros(5).toDouble())
}
@Test
@@ -99,7 +75,7 @@
model.tooltip = (NetworkTrafficTooltipModel(model))
assertThat(model.tooltip).isInstanceOf(NetworkTrafficTooltipModel::class.java)
val tooltip = model.tooltip as NetworkTrafficTooltipModel
- val tooltipTime = TimeUnit.SECONDS.toMicros(10).toDouble()
+ val tooltipTime = SECONDS.toMicros(10).toDouble()
model.timeline.tooltipRange.set(tooltipTime, tooltipTime)
val networkLegends = tooltip.getLegends()
assertThat(networkLegends.rxLegend.name).isEqualTo("Received")
@@ -119,10 +95,10 @@
assertThat(sending.name).isEqualTo("Sending")
assertThat(receiving.series).hasSize(1)
assertThat(receiving.series[0].x).isEqualTo(0)
- assertThat(receiving.series[0].value.toLong()).isEqualTo(1)
+ assertThat(receiving.series[0].value).isEqualTo(1)
assertThat(sending.series).hasSize(1)
assertThat(sending.series[0].x).isEqualTo(0)
- assertThat(sending.series[0].value.toLong()).isEqualTo(2)
+ assertThat(sending.series[0].value).isEqualTo(2)
}
@Test
@@ -151,24 +127,18 @@
assertThat(legendsUpdated).isFalse()
assertThat(tooltipLegendsUpdated).isFalse()
- model.timeline.viewRange.set(
- TimeUnit.SECONDS.toMicros(1).toDouble(),
- TimeUnit.SECONDS.toMicros(2).toDouble()
- )
+ model.timeline.viewRange.set(SECONDS.toMicros(1).toDouble(), SECONDS.toMicros(2).toDouble())
assertThat(networkUsageUpdated).isTrue()
// Make sure the axis lerps correctly when we move the range there.
- model.timeline.dataRange.max = TimeUnit.SECONDS.toMicros(101).toDouble()
- model.timeline.viewRange.set(
- TimeUnit.SECONDS.toMicros(99).toDouble(),
- TimeUnit.SECONDS.toMicros(101).toDouble()
- )
+ model.timeline.dataRange.max = SECONDS.toMicros(101).toDouble()
+ model.timeline.viewRange.set(SECONDS.toMicros(99).toDouble(), SECONDS.toMicros(101).toDouble())
timer.tick(100)
assertThat(legendsUpdated).isTrue()
assertThat(trafficAxisUpdated).isTrue()
model.timeline.tooltipRange.set(
- TimeUnit.SECONDS.toMicros(100).toDouble(),
- TimeUnit.SECONDS.toMicros(100).toDouble()
+ SECONDS.toMicros(100).toDouble(),
+ SECONDS.toMicros(100).toDouble()
)
assertThat(tooltipLegendsUpdated).isTrue()
}
diff --git a/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/ProtoBuilder.kt b/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/ProtoBuilder.kt
new file mode 100644
index 0000000..8baae97
--- /dev/null
+++ b/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/ProtoBuilder.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.appinspection.inspectors.network.model
+
+import com.android.tools.idea.protobuf.ByteString
+import studio.network.inspection.NetworkInspectorProtocol
+import studio.network.inspection.NetworkInspectorProtocol.HttpConnectionEvent
+import studio.network.inspection.NetworkInspectorProtocol.HttpConnectionEvent.Closed
+import studio.network.inspection.NetworkInspectorProtocol.HttpConnectionEvent.Payload
+import studio.network.inspection.NetworkInspectorProtocol.HttpConnectionEvent.RequestCompleted
+import studio.network.inspection.NetworkInspectorProtocol.HttpConnectionEvent.RequestStarted
+import studio.network.inspection.NetworkInspectorProtocol.HttpConnectionEvent.ResponseCompleted
+import studio.network.inspection.NetworkInspectorProtocol.HttpConnectionEvent.ResponseStarted
+import studio.network.inspection.NetworkInspectorProtocol.SpeedEvent
+
+@Suppress("SameParameterValue")
+internal fun requestStarted(
+ id: Long,
+ timestampNanos: Long,
+ url: String = "www.url.com",
+ method: String = "GET",
+ fields: String = "",
+ trace: String = "trace",
+) =
+ httpConnectionEvent(id, timestampNanos) {
+ setHttpRequestStarted(
+ RequestStarted.newBuilder().setUrl(url).setMethod(method).setTrace(trace).setFields(fields)
+ )
+ }
+ .build()
+
+@Suppress("SameParameterValue")
+internal fun requestPayload(id: Long, timestampNanos: Long, payload: String) =
+ httpConnectionEvent(id, timestampNanos) {
+ setRequestPayload(Payload.newBuilder().setPayload(ByteString.copyFromUtf8(payload)))
+ }
+ .build()
+
+@Suppress("SameParameterValue")
+internal fun requestCompleted(id: Long, timestampNanos: Long) =
+ httpConnectionEvent(id, timestampNanos) { setHttpRequestCompleted(RequestCompleted.newBuilder()) }
+ .build()
+
+@Suppress("SameParameterValue")
+internal fun responseStarted(id: Long, timestampNanos: Long, fields: String) =
+ httpConnectionEvent(id, timestampNanos) {
+ setHttpResponseStarted(ResponseStarted.newBuilder().setFields(fields))
+ }
+ .build()
+
+@Suppress("SameParameterValue")
+internal fun responsePayload(id: Long, timestampNanos: Long, payload: String) =
+ httpConnectionEvent(id, timestampNanos) {
+ setResponsePayload(Payload.newBuilder().setPayload(ByteString.copyFromUtf8(payload)))
+ }
+ .build()
+
+@Suppress("SameParameterValue")
+internal fun responseCompleted(id: Long, timestampNanos: Long) =
+ httpConnectionEvent(id, timestampNanos) {
+ setHttpResponseCompleted(ResponseCompleted.newBuilder())
+ }
+ .build()
+
+@Suppress("SameParameterValue")
+internal fun httpClosed(id: Long, timestamp: Long, completed: Boolean) =
+ httpConnectionEvent(id, timestamp) { setHttpClosed(Closed.newBuilder().setCompleted(completed)) }
+ .build()
+
+@Suppress("SameParameterValue")
+internal fun httpThread(id: Long, timestampNanos: Long, threadId: Long, threadName: String) =
+ httpConnectionEvent(id, timestampNanos) {
+ setHttpThread(
+ HttpConnectionEvent.ThreadData.newBuilder().setThreadId(threadId).setThreadName(threadName)
+ )
+ }
+ .build()
+
+internal fun speedEvent(timestampNanos: Long, rxSpeed: Long = 0, txSpeed: Long = 0) =
+ NetworkInspectorProtocol.Event.newBuilder()
+ .setTimestamp(timestampNanos)
+ .setSpeedEvent(SpeedEvent.newBuilder().setRxSpeed(rxSpeed).setTxSpeed(txSpeed))
+ .build()
+
+private fun httpConnectionEvent(
+ id: Long,
+ timestampNanos: Long,
+ block: HttpConnectionEvent.Builder.() -> HttpConnectionEvent.Builder
+) =
+ NetworkInspectorProtocol.Event.newBuilder()
+ .setTimestamp(timestampNanos)
+ .setHttpConnectionEvent(HttpConnectionEvent.newBuilder().setConnectionId(id).block())
diff --git a/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/httpdata/HttpDataModelTest.kt b/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/httpdata/HttpDataModelTest.kt
index fd2e374..4dcc2f8 100644
--- a/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/httpdata/HttpDataModelTest.kt
+++ b/app-inspection/inspectors/network/model/testSrc/com/android/tools/idea/appinspection/inspectors/network/model/httpdata/HttpDataModelTest.kt
@@ -18,138 +18,76 @@
import com.android.tools.adtui.model.Range
import com.android.tools.idea.appinspection.inspectors.network.model.FakeNetworkInspectorDataSource
import com.android.tools.idea.appinspection.inspectors.network.model.analytics.StubNetworkInspectorTracker
+import com.android.tools.idea.appinspection.inspectors.network.model.httpClosed
+import com.android.tools.idea.appinspection.inspectors.network.model.httpThread
+import com.android.tools.idea.appinspection.inspectors.network.model.requestCompleted
+import com.android.tools.idea.appinspection.inspectors.network.model.requestPayload
+import com.android.tools.idea.appinspection.inspectors.network.model.requestStarted
+import com.android.tools.idea.appinspection.inspectors.network.model.responseCompleted
+import com.android.tools.idea.appinspection.inspectors.network.model.responsePayload
+import com.android.tools.idea.appinspection.inspectors.network.model.responseStarted
import com.android.tools.idea.protobuf.ByteString
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors
-import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeUnit.SECONDS
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asCoroutineDispatcher
import org.junit.Test
-import studio.network.inspection.NetworkInspectorProtocol.Event
-import studio.network.inspection.NetworkInspectorProtocol.HttpConnectionEvent
private const val CONNECTION_ID = 1L
+private val fakeUrl = fakeUrl(CONNECTION_ID)
+private val faceTrace = fakeStackTrace(CONNECTION_ID)
private val HTTP_DATA =
listOf(
- Event.newBuilder()
- .apply {
- timestamp = TimeUnit.SECONDS.toNanos(0)
- httpConnectionEvent =
- HttpConnectionEvent.newBuilder()
- .apply {
- connectionId = CONNECTION_ID
- httpRequestStarted =
- HttpConnectionEvent.RequestStarted.newBuilder()
- .apply {
- url = fakeUrl(CONNECTION_ID)
- method = ""
- trace = fakeStackTrace(1)
- fields = ""
- }
- .build()
- }
- .build()
- }
- .build(),
- Event.newBuilder()
- .apply {
- timestamp = TimeUnit.SECONDS.toNanos(1)
- httpConnectionEvent =
- HttpConnectionEvent.newBuilder()
- .apply {
- connectionId = CONNECTION_ID
- requestPayload =
- HttpConnectionEvent.Payload.newBuilder()
- .apply { payload = ByteString.copyFromUtf8("REQUEST_CONTENT") }
- .build()
- }
- .build()
- }
- .build(),
- Event.newBuilder()
- .apply {
- timestamp = TimeUnit.SECONDS.toNanos(1)
- httpConnectionEvent =
- HttpConnectionEvent.newBuilder()
- .apply {
- connectionId = CONNECTION_ID
- httpRequestCompleted = HttpConnectionEvent.RequestCompleted.getDefaultInstance()
- }
- .build()
- }
- .build(),
- Event.newBuilder()
- .apply {
- timestamp = TimeUnit.SECONDS.toNanos(2)
- httpConnectionEvent =
- HttpConnectionEvent.newBuilder()
- .apply {
- connectionId = CONNECTION_ID
- httpResponseStarted =
- HttpConnectionEvent.ResponseStarted.newBuilder()
- .apply { fields = fakeResponseFields(CONNECTION_ID) }
- .build()
- }
- .build()
- }
- .build(),
- Event.newBuilder()
- .apply {
- timestamp = TimeUnit.SECONDS.toNanos(3)
- httpConnectionEvent =
- HttpConnectionEvent.newBuilder()
- .apply {
- connectionId = CONNECTION_ID
- responsePayload =
- HttpConnectionEvent.Payload.newBuilder()
- .apply { payload = ByteString.copyFromUtf8("RESPONSE_CONTENT") }
- .build()
- }
- .build()
- }
- .build(),
- Event.newBuilder()
- .apply {
- timestamp = TimeUnit.SECONDS.toNanos(3)
- httpConnectionEvent =
- HttpConnectionEvent.newBuilder()
- .apply {
- connectionId = CONNECTION_ID
- httpResponseCompleted = HttpConnectionEvent.ResponseCompleted.getDefaultInstance()
- }
- .build()
- }
- .build(),
- Event.newBuilder()
- .apply {
- timestamp = TimeUnit.SECONDS.toNanos(3)
- httpConnectionEvent =
- HttpConnectionEvent.newBuilder()
- .apply {
- connectionId = CONNECTION_ID
- httpClosed =
- HttpConnectionEvent.Closed.newBuilder().apply { completed = true }.build()
- }
- .build()
- }
- .build()
+ requestStarted(
+ CONNECTION_ID,
+ timestampNanos = SECONDS.toNanos(0),
+ url = fakeUrl,
+ method = "",
+ trace = faceTrace
+ ),
+ requestPayload(CONNECTION_ID, timestampNanos = SECONDS.toNanos(1), payload = "REQUEST_CONTENT"),
+ requestCompleted(CONNECTION_ID, timestampNanos = SECONDS.toNanos(1)),
+ responseStarted(
+ CONNECTION_ID,
+ timestampNanos = SECONDS.toNanos(2),
+ fields = fakeResponseFields(CONNECTION_ID)
+ ),
+ responsePayload(
+ CONNECTION_ID,
+ timestampNanos = SECONDS.toNanos(3),
+ payload = "RESPONSE_CONTENT"
+ ),
+ responseCompleted(CONNECTION_ID, timestampNanos = SECONDS.toNanos(3)),
+ httpClosed(CONNECTION_ID, timestamp = SECONDS.toNanos(3), completed = true),
)
private val HTTP_DATA_WITH_THREAD =
- HTTP_DATA +
- listOf(
- Event.newBuilder()
- .setTimestamp(TimeUnit.SECONDS.toNanos(4))
- .setHttpConnectionEvent(
- HttpConnectionEvent.newBuilder()
- .setConnectionId(CONNECTION_ID)
- .setHttpThread(
- HttpConnectionEvent.ThreadData.newBuilder().setThreadId(1).setThreadName("thread")
- )
- )
- .build()
- )
+ listOf(
+ requestStarted(
+ CONNECTION_ID,
+ timestampNanos = SECONDS.toNanos(0),
+ url = fakeUrl,
+ method = "",
+ trace = faceTrace
+ ),
+ requestPayload(CONNECTION_ID, timestampNanos = SECONDS.toNanos(1), payload = "REQUEST_CONTENT"),
+ requestCompleted(CONNECTION_ID, timestampNanos = SECONDS.toNanos(1)),
+ responseStarted(
+ CONNECTION_ID,
+ timestampNanos = SECONDS.toNanos(2),
+ fields = fakeResponseFields(CONNECTION_ID)
+ ),
+ responsePayload(
+ CONNECTION_ID,
+ timestampNanos = SECONDS.toNanos(3),
+ payload = "RESPONSE_CONTENT"
+ ),
+ responseCompleted(CONNECTION_ID, timestampNanos = SECONDS.toNanos(3)),
+ httpClosed(CONNECTION_ID, timestamp = SECONDS.toNanos(3), completed = true),
+ httpThread(CONNECTION_ID, timestampNanos = SECONDS.toNanos(4), 1, "thread"),
+ )
class HttpDataModelTest {
@@ -158,7 +96,7 @@
val source = FakeNetworkInspectorDataSource(httpEventList = HTTP_DATA_WITH_THREAD)
val scope = CoroutineScope(MoreExecutors.directExecutor().asCoroutineDispatcher())
val model = HttpDataModelImpl(source, StubNetworkInspectorTracker(), scope)
- val httpDataList = model.getData(Range(0.0, TimeUnit.SECONDS.toMicros(5).toDouble()))
+ val httpDataList = model.getData(Range(0.0, SECONDS.toMicros(5).toDouble()))
assertThat(httpDataList).hasSize(1)
val httpData = httpDataList[0]
@@ -168,8 +106,8 @@
assertThat(httpData.responseCompleteTimeUs).isEqualTo(3000000)
assertThat(httpData.connectionEndTimeUs).isEqualTo(3000000)
assertThat(httpData.method).isEmpty()
- assertThat(httpData.url).isEqualTo(fakeUrl(CONNECTION_ID))
- assertThat(httpData.trace).isEqualTo(fakeStackTrace(CONNECTION_ID))
+ assertThat(httpData.url).isEqualTo(fakeUrl)
+ assertThat(httpData.trace).isEqualTo(faceTrace)
assertThat(httpData.requestPayload).isEqualTo(ByteString.copyFromUtf8("REQUEST_CONTENT"))
assertThat(httpData.responsePayload).isEqualTo(ByteString.copyFromUtf8("RESPONSE_CONTENT"))
assertThat(httpData.responseHeader.getField("connId")).isEqualTo("1")
@@ -180,7 +118,7 @@
val source = FakeNetworkInspectorDataSource(httpEventList = HTTP_DATA)
val scope = CoroutineScope(MoreExecutors.directExecutor().asCoroutineDispatcher())
val model = HttpDataModelImpl(source, StubNetworkInspectorTracker(), scope)
- val httpDataList = model.getData(Range(0.0, TimeUnit.SECONDS.toMicros(5).toDouble()))
+ val httpDataList = model.getData(Range(0.0, SECONDS.toMicros(5).toDouble()))
assertThat(httpDataList).isEmpty()
}
}
diff --git a/app-quality-insights/api/testSrc/com/android/tools/idea/insights/AppInsightsProjectLevelControllerRule.kt b/app-quality-insights/api/testSrc/com/android/tools/idea/insights/AppInsightsProjectLevelControllerRule.kt
index bdc682a..ee116cb 100644
--- a/app-quality-insights/api/testSrc/com/android/tools/idea/insights/AppInsightsProjectLevelControllerRule.kt
+++ b/app-quality-insights/api/testSrc/com/android/tools/idea/insights/AppInsightsProjectLevelControllerRule.kt
@@ -28,12 +28,14 @@
import com.android.tools.idea.insights.client.IssueRequest
import com.android.tools.idea.insights.client.IssueResponse
import com.android.tools.idea.insights.events.actions.AppInsightsActionQueueImpl
+import com.android.tools.idea.testing.AndroidProjectRule
import com.android.tools.idea.testing.NamedExternalResource
import com.google.common.truth.Truth.assertThat
import com.google.gct.login.GoogleLogin
import com.google.wireless.android.sdk.stats.AppQualityInsightsUsageEvent.AppQualityInsightsFetchDetails.FetchSource
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.project.Project
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.registerOrReplaceServiceInstance
@@ -56,9 +58,17 @@
private suspend fun <T> ReceiveChannel<T>.receiveWithTimeout(): T = withTimeout(5000) { receive() }
class AppInsightsProjectLevelControllerRule(
- private val projectRule: ProjectRule,
+ private val projectProvider: () -> Project,
private val onErrorAction: (String, HyperlinkListener?) -> Unit = { _, _ -> }
) : NamedExternalResource() {
+ constructor(
+ projectRule: ProjectRule,
+ onErrorAction: (String, HyperlinkListener?) -> Unit = { _, _ -> }
+ ) : this({ projectRule.project }, onErrorAction)
+ constructor(
+ androidProjectRule: AndroidProjectRule,
+ onErrorAction: (String, HyperlinkListener?) -> Unit = { _, _ -> }
+ ) : this({ androidProjectRule.project }, onErrorAction)
private val disposableRule = DisposableRule()
val disposable: Disposable
get() = disposableRule.disposable
@@ -98,7 +108,7 @@
flowStart = SharingStarted.Lazily,
tracker = tracker,
clock = clock,
- project = projectRule.project,
+ project = projectProvider(),
queue = AppInsightsActionQueueImpl(ConcurrentLinkedQueue()),
onErrorAction = onErrorAction,
defaultFilters = TEST_FILTERS,
diff --git a/app-quality-insights/ide/BUILD b/app-quality-insights/ide/BUILD
index 08b9d0a..05fe5a1 100644
--- a/app-quality-insights/ide/BUILD
+++ b/app-quality-insights/ide/BUILD
@@ -28,7 +28,10 @@
name = "intellij.android.app-quality-insights.ide.tests",
iml_files = ["intellij.android.app-quality-insights.ide.tests.iml"],
test_class = "com.android.tools.idea.insights.InsightsIdeTestSuite",
- test_data = glob(["testData/**"]),
+ test_data = [
+ ":testData",
+ "//prebuilts/studio/sdk:platforms/latest",
+ ],
test_srcs = ["testSrc"],
visibility = ["//visibility:public"],
# do not sort: must match IML order
@@ -50,5 +53,13 @@
"//tools/analytics-library/testing:android.sdktools.analytics-testing[module, test]",
"//tools/analytics-library/tracker:analytics-tracker[module, test]",
"//tools/adt/idea/.idea/libraries:studio-analytics-proto[test]",
+ "//tools/adt/idea/project-system-gradle-models:intellij.android.projectSystem.gradle.models[module, test]",
+ "//tools/adt/idea/app-quality-insights/ide/gradle:intellij.android.app-quality-insights.ide.gradle[module, test]",
],
)
+
+filegroup(
+ name = "testData",
+ srcs = glob(["testData/**"]),
+ visibility = ["//visibility:public"],
+)
diff --git a/app-quality-insights/ide/gradle/BUILD b/app-quality-insights/ide/gradle/BUILD
new file mode 100644
index 0000000..6b6d2b4
--- /dev/null
+++ b/app-quality-insights/ide/gradle/BUILD
@@ -0,0 +1,20 @@
+load("//tools/base/bazel:bazel.bzl", "iml_module")
+
+# managed by go/iml_to_build
+iml_module(
+ name = "intellij.android.app-quality-insights.ide.gradle",
+ srcs = ["src"],
+ iml_files = ["intellij.android.app-quality-insights.ide.gradle.iml"],
+ visibility = ["//visibility:public"],
+ # do not sort: must match IML order
+ deps = [
+ "//prebuilts/studio/intellij-sdk:studio-sdk",
+ "//tools/adt/idea/app-quality-insights/ide:intellij.android.app-quality-insights.ide[module]",
+ "//tools/adt/idea/project-system-gradle:intellij.android.projectSystem.gradle[module]",
+ "//tools/adt/idea/project-system:intellij.android.projectSystem[module]",
+ "//tools/adt/idea/android-common:intellij.android.common[module]",
+ "//tools/adt/idea/app-quality-insights/api:intellij.android.app-quality-insights.api[module]",
+ "//tools/base/flags:studio.android.sdktools.flags[module]",
+ "//tools/base/sdk-common:studio.android.sdktools.sdk-common[module]",
+ ],
+)
diff --git a/app-quality-insights/ide/gradle/intellij.android.app-quality-insights.ide.gradle.iml b/app-quality-insights/ide/gradle/intellij.android.app-quality-insights.ide.gradle.iml
new file mode 100644
index 0000000..d150583
--- /dev/null
+++ b/app-quality-insights/ide/gradle/intellij.android.app-quality-insights.ide.gradle.iml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="studio-sdk" level="project" />
+ <orderEntry type="module" module-name="intellij.android.app-quality-insights.ide" />
+ <orderEntry type="module" module-name="intellij.android.projectSystem.gradle" />
+ <orderEntry type="module" module-name="intellij.android.projectSystem" />
+ <orderEntry type="module" module-name="intellij.android.common" />
+ <orderEntry type="module" module-name="intellij.android.app-quality-insights.api" />
+ <orderEntry type="module" module-name="android.sdktools.flags" />
+ <orderEntry type="module" module-name="android.sdktools.sdk-common" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/app-quality-insights/ide/gradle/src/META-INF/app-insights-gradle.xml b/app-quality-insights/ide/gradle/src/META-INF/app-insights-gradle.xml
new file mode 100644
index 0000000..ae49678
--- /dev/null
+++ b/app-quality-insights/ide/gradle/src/META-INF/app-insights-gradle.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ 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.
+ -->
+<idea-plugin>
+ <extensions defaultExtensionNs="com.android.tools.idea.insights">
+ <vcsIntegrationToken implementation="com.android.tools.idea.insights.VcsIntegrationGradleToken"/>
+ </extensions>
+</idea-plugin>
diff --git a/app-quality-insights/ide/gradle/src/com/android/tools/idea/insights/VcsIntegrationGradleToken.kt b/app-quality-insights/ide/gradle/src/com/android/tools/idea/insights/VcsIntegrationGradleToken.kt
new file mode 100644
index 0000000..532dbd7
--- /dev/null
+++ b/app-quality-insights/ide/gradle/src/com/android/tools/idea/insights/VcsIntegrationGradleToken.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.insights
+
+import com.android.tools.idea.flags.StudioFlags
+import com.android.tools.idea.gradle.plugin.AndroidPluginInfo
+import com.android.tools.idea.insights.persistence.AppInsightsSettings
+import com.android.tools.idea.projectsystem.GradleToken
+import com.android.tools.idea.projectsystem.cacheInvalidatingOnSyncModifications
+import com.android.tools.idea.projectsystem.getModuleSystem
+import com.android.tools.idea.projectsystem.gradle.GradleModuleSystem
+import com.android.tools.idea.projectsystem.gradle.GradleProjectSystem
+import com.intellij.openapi.components.service
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.project.modules
+
+class VcsIntegrationGradleToken : VcsIntegrationToken<GradleProjectSystem>, GradleToken {
+ override fun canShowSuggestVcsIntegrationFeaturePanel(
+ projectSystem: GradleProjectSystem
+ ): Boolean {
+ val project = projectSystem.project
+ if (!StudioFlags.APP_INSIGHTS_VCS_SUPPORT.get()) return false
+ if (project.service<AppInsightsSettings>().isSuggestVcsIntegrationDismissed) return false
+ if (project.isVcsInfoEnabledInAgp()) return false
+
+ return project.hasRequiredAgpVersion(MIN_SUPPORTED_AGP_VERSION)
+ }
+
+ override fun isChangeAwareAnnotationEnabled(projectSystem: GradleProjectSystem): Boolean {
+ if (
+ !StudioFlags.APP_INSIGHTS_VCS_SUPPORT.get() ||
+ !StudioFlags.APP_INSIGHTS_CHANGE_AWARE_ANNOTATION_SUPPORT.get()
+ ) {
+ return false
+ }
+ return projectSystem.project.isVcsInfoEnabledInAgp()
+ }
+
+ companion object {
+ const val MIN_SUPPORTED_AGP_VERSION = "8.2.0-alpha06"
+ }
+
+ private fun Project.isVcsInfoEnabledInAgp(): Boolean {
+ return cacheInvalidatingOnSyncModifications {
+ modules
+ .asList()
+ .mapNotNull { it.getModuleSystem() as? GradleModuleSystem }
+ .any { it.enableVcsInfo }
+ }
+ }
+
+ private fun Project.hasRequiredAgpVersion(requiredAppVersion: String): Boolean {
+ val androidPluginInfo = AndroidPluginInfo.findFromModel(this)?.pluginVersion ?: return false
+
+ return androidPluginInfo >= requiredAppVersion
+ }
+}
diff --git a/app-quality-insights/ide/intellij.android.app-quality-insights.ide.tests.iml b/app-quality-insights/ide/intellij.android.app-quality-insights.ide.tests.iml
index 8ea42cc..2efd39a 100644
--- a/app-quality-insights/ide/intellij.android.app-quality-insights.ide.tests.iml
+++ b/app-quality-insights/ide/intellij.android.app-quality-insights.ide.tests.iml
@@ -24,5 +24,7 @@
<orderEntry type="module" module-name="android.sdktools.analytics-testing" scope="TEST" />
<orderEntry type="module" module-name="analytics-tracker" scope="TEST" />
<orderEntry type="library" scope="TEST" name="studio-analytics-proto" level="project" />
+ <orderEntry type="module" module-name="intellij.android.projectSystem.gradle.models" scope="TEST" />
+ <orderEntry type="module" module-name="intellij.android.app-quality-insights.ide.gradle" scope="TEST" />
</component>
</module>
\ No newline at end of file
diff --git a/app-quality-insights/ide/src/META-INF/app-insights.xml b/app-quality-insights/ide/src/META-INF/app-insights.xml
index 62b43a2..8d4db55 100644
--- a/app-quality-insights/ide/src/META-INF/app-insights.xml
+++ b/app-quality-insights/ide/src/META-INF/app-insights.xml
@@ -30,6 +30,9 @@
<extensionPoints>
<extensionPoint qualifiedName="com.android.tools.idea.insights.ui.appInsightsTabProvider"
interface="com.android.tools.idea.insights.ui.AppInsightsTabProvider"/>
+ <extensionPoint qualifiedName="com.android.tools.idea.insights.vcsIntegrationToken"
+ interface="com.android.tools.idea.insights.VcsIntegrationToken"
+ area="IDEA_PROJECT"/>
</extensionPoints>
<actions>
<action internal="true" id="Android.EnterOfflineModeInternalAction" class="com.android.tools.idea.insights.EnterOfflineModeInternalAction">
diff --git a/app-quality-insights/ide/src/com/android/tools/idea/insights/ModuleUtil.kt b/app-quality-insights/ide/src/com/android/tools/idea/insights/ModuleUtil.kt
index 75a655c..7f3b286 100644
--- a/app-quality-insights/ide/src/com/android/tools/idea/insights/ModuleUtil.kt
+++ b/app-quality-insights/ide/src/com/android/tools/idea/insights/ModuleUtil.kt
@@ -15,7 +15,6 @@
*/
package com.android.tools.idea.insights
-import com.android.tools.idea.gradle.plugin.AndroidPluginInfo
import com.android.tools.idea.model.AndroidModel
import com.android.tools.idea.projectsystem.getAndroidFacets
import com.intellij.openapi.module.Module
@@ -38,9 +37,3 @@
}
return appId
}
-
-fun Project.hasRequiredAgpVersion(requiredAppVersion: String): Boolean {
- val androidPluginInfo = AndroidPluginInfo.findFromModel(this)?.pluginVersion ?: return false
-
- return androidPluginInfo >= requiredAppVersion
-}
diff --git a/app-quality-insights/ide/src/com/android/tools/idea/insights/VcsIntegrationToken.kt b/app-quality-insights/ide/src/com/android/tools/idea/insights/VcsIntegrationToken.kt
new file mode 100644
index 0000000..6cb36ab
--- /dev/null
+++ b/app-quality-insights/ide/src/com/android/tools/idea/insights/VcsIntegrationToken.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.insights
+
+import com.android.tools.idea.projectsystem.AndroidProjectSystem
+import com.android.tools.idea.projectsystem.Token
+import com.intellij.openapi.extensions.ExtensionPointName
+
+interface VcsIntegrationToken<P : AndroidProjectSystem> : Token {
+ companion object {
+ val EP_NAME =
+ ExtensionPointName<VcsIntegrationToken<AndroidProjectSystem>>(
+ "com.android.tools.idea.insights.vcsIntegrationToken"
+ )
+ }
+ fun canShowSuggestVcsIntegrationFeaturePanel(projectSystem: P): Boolean
+ fun isChangeAwareAnnotationEnabled(projectSystem: P): Boolean
+}
diff --git a/app-quality-insights/ide/src/com/android/tools/idea/insights/inspection/AppInsightsExternalAnnotator.kt b/app-quality-insights/ide/src/com/android/tools/idea/insights/inspection/AppInsightsExternalAnnotator.kt
index 1d75f2d..9bab900 100644
--- a/app-quality-insights/ide/src/com/android/tools/idea/insights/inspection/AppInsightsExternalAnnotator.kt
+++ b/app-quality-insights/ide/src/com/android/tools/idea/insights/inspection/AppInsightsExternalAnnotator.kt
@@ -17,6 +17,7 @@
import com.android.tools.idea.insights.AppInsight
import com.android.tools.idea.insights.AppInsightsModel
+import com.android.tools.idea.insights.VcsIntegrationToken
import com.android.tools.idea.insights.analysis.StackTraceAnalyzer
import com.android.tools.idea.insights.analytics.AppInsightsPerformanceTracker
import com.android.tools.idea.insights.inspection.AppInsightsExternalAnnotator.AnnotationResult
@@ -24,6 +25,7 @@
import com.android.tools.idea.insights.ui.AppInsightsGutterRenderer
import com.android.tools.idea.insights.ui.AppInsightsTabProvider
import com.android.tools.idea.insights.ui.AppInsightsToolWindowFactory
+import com.android.tools.idea.projectsystem.getProjectSystem
import com.intellij.codeInsight.daemon.LineMarkerProviderDescriptor
import com.intellij.codeInsight.daemon.LineMarkerSettings
import com.intellij.lang.annotation.AnnotationHolder
@@ -76,9 +78,14 @@
collectedInfo ?: return null
val project = collectedInfo.project
+ val projectSystem = project.getProjectSystem()
+ val changeAwareToken =
+ VcsIntegrationToken.EP_NAME.getExtensions(project).firstOrNull {
+ it.isApplicable(projectSystem)
+ }
val insights = collectedInfo.insights
- if (!project.isChangeAwareAnnotationEnabled()) {
+ if (changeAwareToken?.isChangeAwareAnnotationEnabled(projectSystem) != true) {
return AnnotationResult(insights)
}
diff --git a/app-quality-insights/ide/src/com/android/tools/idea/insights/inspection/LineNumberMapperUtils.kt b/app-quality-insights/ide/src/com/android/tools/idea/insights/inspection/LineNumberMapperUtils.kt
index 8032f44..9127c67 100644
--- a/app-quality-insights/ide/src/com/android/tools/idea/insights/inspection/LineNumberMapperUtils.kt
+++ b/app-quality-insights/ide/src/com/android/tools/idea/insights/inspection/LineNumberMapperUtils.kt
@@ -15,12 +15,10 @@
*/
package com.android.tools.idea.insights.inspection
-import com.android.tools.idea.flags.StudioFlags
import com.android.tools.idea.insights.AppInsight
import com.android.tools.idea.insights.AppVcsInfo
import com.android.tools.idea.insights.vcs.createVcsDocument
import com.android.tools.idea.insights.vcs.getVcsManager
-import com.android.tools.idea.insights.vcs.isVcsInfoEnabledInAgp
import com.android.tools.idea.insights.vcs.locateRepository
import com.intellij.diff.comparison.iterables.FairDiffIterable
import com.intellij.diff.tools.util.text.LineOffsetsUtil
@@ -32,17 +30,6 @@
import com.intellij.openapi.vcs.ex.compareLines
import com.intellij.openapi.vfs.VirtualFile
-fun Project.isChangeAwareAnnotationEnabled(): Boolean {
- if (
- !StudioFlags.APP_INSIGHTS_VCS_SUPPORT.get() ||
- !StudioFlags.APP_INSIGHTS_CHANGE_AWARE_ANNOTATION_SUPPORT.get()
- ) {
- return false
- }
-
- return isVcsInfoEnabledInAgp()
-}
-
fun AppInsight.tryCreateVcsDocumentOrNull(contextVFile: VirtualFile, project: Project): Document? {
return try {
createVcsDocument(contextVFile, project)
diff --git a/app-quality-insights/ide/src/com/android/tools/idea/insights/vcs/SuggestVcsIntegrationFeaturePanel.kt b/app-quality-insights/ide/src/com/android/tools/idea/insights/vcs/SuggestVcsIntegrationFeaturePanel.kt
index 7af1ba7..317d5ad 100644
--- a/app-quality-insights/ide/src/com/android/tools/idea/insights/vcs/SuggestVcsIntegrationFeaturePanel.kt
+++ b/app-quality-insights/ide/src/com/android/tools/idea/insights/vcs/SuggestVcsIntegrationFeaturePanel.kt
@@ -15,15 +15,10 @@
*/
package com.android.tools.idea.insights.vcs
-import com.android.tools.idea.flags.StudioFlags
-import com.android.tools.idea.insights.hasRequiredAgpVersion
import com.android.tools.idea.insights.persistence.AppInsightsSettings
-import com.android.tools.idea.projectsystem.cacheInvalidatingOnSyncModifications
-import com.android.tools.idea.projectsystem.getModuleSystem
import com.intellij.ide.BrowserUtil
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
-import com.intellij.openapi.project.modules
import com.intellij.ui.EditorNotificationPanel
import com.intellij.util.ui.JBUI.CurrentTheme.Banner
@@ -46,22 +41,4 @@
isVisible = true
}
-
- companion object {
- const val MIN_SUPPORTED_AGP_VERSION = "8.2.0-alpha06"
-
- fun canShow(project: Project): Boolean {
- if (!StudioFlags.APP_INSIGHTS_VCS_SUPPORT.get()) return false
- if (project.service<AppInsightsSettings>().isSuggestVcsIntegrationDismissed) return false
- if (project.isVcsInfoEnabledInAgp()) return false
-
- return project.hasRequiredAgpVersion(MIN_SUPPORTED_AGP_VERSION)
- }
- }
-}
-
-fun Project.isVcsInfoEnabledInAgp(): Boolean {
- return cacheInvalidatingOnSyncModifications {
- modules.asList().any { it.getModuleSystem().enableVcsInfo }
- }
}
diff --git a/app-quality-insights/ide/testSrc/com/android/tools/idea/insights/inspection/AppInsightsExternalAnnotatorOnChangeTest.kt b/app-quality-insights/ide/testSrc/com/android/tools/idea/insights/inspection/AppInsightsExternalAnnotatorOnChangeTest.kt
index d346520..19dab95 100644
--- a/app-quality-insights/ide/testSrc/com/android/tools/idea/insights/inspection/AppInsightsExternalAnnotatorOnChangeTest.kt
+++ b/app-quality-insights/ide/testSrc/com/android/tools/idea/insights/inspection/AppInsightsExternalAnnotatorOnChangeTest.kt
@@ -25,19 +25,34 @@
import com.android.tools.idea.insights.VCS_CATEGORY
import com.android.tools.idea.insights.ui.AppInsightsGutterRenderer
import com.android.tools.idea.insights.vcs.InsightsVcsTestRule
-import com.android.tools.idea.insights.vcs.updateVcsInfoFlagInModel
import com.android.tools.idea.testing.AndroidProjectRule
+import com.android.tools.idea.testing.JavaLibraryDependency
+import com.android.tools.idea.testing.buildAgpProjectFlagsStub
+import com.android.tools.idea.testing.createAndroidProjectBuilderForDefaultTestProjectStructure
+import com.android.tools.tests.AdtTestKotlinArtifacts
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
class AppInsightsExternalAnnotatorOnChangeTest {
- private val projectRule = AndroidProjectRule.onDisk()
+ private val projectRule =
+ AndroidProjectRule.withAndroidModel(
+ createAndroidProjectBuilderForDefaultTestProjectStructure()
+ .withAgpProjectFlags { buildAgpProjectFlagsStub().copy(enableVcsInfo = true) }
+ // TODO(b/300170256): Remove this once 2023.3 merges and we no longer need kotlin-stdlib for
+ // every Kotlin test.
+ .withJavaLibraryDependencyList {
+ listOf(JavaLibraryDependency.forJar(AdtTestKotlinArtifacts.kotlinStdlib))
+ }
+ )
private val vcsInsightsRule = InsightsVcsTestRule(projectRule)
- private val flagRule = FlagRule(StudioFlags.APP_INSIGHTS_CHANGE_AWARE_ANNOTATION_SUPPORT, true)
+ private val changeAwareFlagRule =
+ FlagRule(StudioFlags.APP_INSIGHTS_CHANGE_AWARE_ANNOTATION_SUPPORT, true)
- @get:Rule val rule = RuleChain.outerRule(projectRule).around(vcsInsightsRule).around(flagRule)
+ @get:Rule
+ val rule: RuleChain =
+ RuleChain.outerRule(projectRule).around(vcsInsightsRule).around(changeAwareFlagRule)
private lateinit var appVcsInfo: AppVcsInfo
@@ -46,8 +61,6 @@
@Before
fun setUp() {
- projectRule.fixture.module.updateVcsInfoFlagInModel(true)
-
appVcsInfo =
AppVcsInfo(
listOf(
diff --git a/app-quality-insights/ide/testSrc/com/android/tools/idea/insights/inspection/AppInsightsExternalAnnotatorTest.kt b/app-quality-insights/ide/testSrc/com/android/tools/idea/insights/inspection/AppInsightsExternalAnnotatorTest.kt
index dad455f..caf091d 100644
--- a/app-quality-insights/ide/testSrc/com/android/tools/idea/insights/inspection/AppInsightsExternalAnnotatorTest.kt
+++ b/app-quality-insights/ide/testSrc/com/android/tools/idea/insights/inspection/AppInsightsExternalAnnotatorTest.kt
@@ -22,8 +22,11 @@
import com.android.tools.idea.insights.REPO_INFO
import com.android.tools.idea.insights.ui.AppInsightsGutterRenderer
import com.android.tools.idea.insights.vcs.InsightsVcsTestRule
-import com.android.tools.idea.insights.vcs.updateVcsInfoFlagInModel
import com.android.tools.idea.testing.AndroidProjectRule
+import com.android.tools.idea.testing.JavaLibraryDependency
+import com.android.tools.idea.testing.buildAgpProjectFlagsStub
+import com.android.tools.idea.testing.createAndroidProjectBuilderForDefaultTestProjectStructure
+import com.android.tools.tests.AdtTestKotlinArtifacts
import com.intellij.codeInsight.daemon.GutterIconDescriptor
import com.intellij.codeInsight.daemon.LineMarkerSettings
import com.intellij.openapi.application.ApplicationManager
@@ -37,13 +40,23 @@
@RunWith(Parameterized::class)
class AppInsightsExternalAnnotatorTest(private val enableChangeAwareAnnotation: Boolean) {
- private val projectRule = AndroidProjectRule.onDisk()
+ private val projectRule =
+ AndroidProjectRule.withAndroidModel(
+ createAndroidProjectBuilderForDefaultTestProjectStructure()
+ .withAgpProjectFlags { buildAgpProjectFlagsStub().copy(enableVcsInfo = true) }
+ // TODO(b/300170256): Remove this once 2023.3 merges and we no longer need kotlin-stdlib for
+ // every Kotlin test.
+ .withJavaLibraryDependencyList {
+ listOf(JavaLibraryDependency.forJar(AdtTestKotlinArtifacts.kotlinStdlib))
+ }
+ )
private val vcsInsightsRule = InsightsVcsTestRule(projectRule)
- private val flagRule =
+ private val changeAwareFlagRule =
FlagRule(StudioFlags.APP_INSIGHTS_CHANGE_AWARE_ANNOTATION_SUPPORT, enableChangeAwareAnnotation)
@get:Rule
- val rule: RuleChain = RuleChain.outerRule(projectRule).around(vcsInsightsRule).around(flagRule)
+ val rule: RuleChain =
+ RuleChain.outerRule(projectRule).around(vcsInsightsRule).around(changeAwareFlagRule)
companion object {
@JvmStatic @Parameterized.Parameters(name = "{0}") fun data() = listOf(true, false)
@@ -56,7 +69,6 @@
@Before
fun setUp() {
- projectRule.fixture.module.updateVcsInfoFlagInModel(enableChangeAwareAnnotation)
appVcsInfo = AppVcsInfo(listOf(REPO_INFO))
}
diff --git a/app-quality-insights/play-vitals/view/src/com/android/tools/idea/vitals/ui/VitalsIssueDetailsPanel.kt b/app-quality-insights/play-vitals/view/src/com/android/tools/idea/vitals/ui/VitalsIssueDetailsPanel.kt
index 93333a4..1b4b4a7 100644
--- a/app-quality-insights/play-vitals/view/src/com/android/tools/idea/vitals/ui/VitalsIssueDetailsPanel.kt
+++ b/app-quality-insights/play-vitals/view/src/com/android/tools/idea/vitals/ui/VitalsIssueDetailsPanel.kt
@@ -164,7 +164,7 @@
@VisibleForTesting val stackTraceConsole = StackTraceConsole(controller, project, tracker)
// Title
- private val header = DetailsPanelHeader(stackTraceConsole.consoleView.editor)
+ private val header = DetailsPanelHeader(stackTraceConsole.consoleView.editor, controller, false)
// TODO(b/290647605): add back device label
// Events, users, affected api levels
@@ -228,7 +228,11 @@
mainPanel,
if (issue != null) MAIN_CARD else EMPTY_CARD
)
- header.updateWithIssue(issue)
+ if (issue == null) {
+ header.clear()
+ } else {
+ header.updateWithIssue(issue)
+ }
}
}
diff --git a/app-quality-insights/ui/BUILD b/app-quality-insights/ui/BUILD
index 9adff0f..d25f799 100644
--- a/app-quality-insights/ui/BUILD
+++ b/app-quality-insights/ui/BUILD
@@ -19,6 +19,7 @@
"//tools/base/testutils:studio.android.sdktools.testutils[module, test]",
"//tools/adt/idea/.idea/libraries:mockito[test]",
"//tools/base/sdklib:studio.android.sdktools.sdklib[module]",
+ "//tools/adt/idea/adt-ui-model:intellij.android.adt.ui.model[module]",
],
)
@@ -45,5 +46,9 @@
"//tools/adt/idea/adt-testutils:intellij.android.adt.testutils[module, test]",
"//tools/adt/idea/artwork:intellij.android.artwork[module, test]",
"//tools/adt/idea/.idea/libraries:studio-analytics-proto[test]",
+ "//tools/adt/idea/adt-ui-model:intellij.android.adt.ui.model[module, test]",
+ "//tools/base/flags:studio.android.sdktools.flags[module, test]",
+ "//tools/adt/idea/.idea/libraries:jetbrains.kotlinx.coroutines.test[test]",
+ "//tools/adt/idea/.idea/libraries:kotlin-test[test]",
],
)
diff --git a/app-quality-insights/ui/intellij.android.app-quality-insights.ui.iml b/app-quality-insights/ui/intellij.android.app-quality-insights.ui.iml
index 71b987d..84f6021 100644
--- a/app-quality-insights/ui/intellij.android.app-quality-insights.ui.iml
+++ b/app-quality-insights/ui/intellij.android.app-quality-insights.ui.iml
@@ -18,5 +18,6 @@
<orderEntry type="module" module-name="android.sdktools.testutils" scope="TEST" />
<orderEntry type="library" scope="TEST" name="mockito" level="project" />
<orderEntry type="module" module-name="android.sdktools.sdklib" />
+ <orderEntry type="module" module-name="intellij.android.adt.ui.model" />
</component>
</module>
\ No newline at end of file
diff --git a/app-quality-insights/ui/intellij.android.app-quality-insights.ui.tests.iml b/app-quality-insights/ui/intellij.android.app-quality-insights.ui.tests.iml
index 277600e..1569738 100644
--- a/app-quality-insights/ui/intellij.android.app-quality-insights.ui.tests.iml
+++ b/app-quality-insights/ui/intellij.android.app-quality-insights.ui.tests.iml
@@ -20,5 +20,9 @@
<orderEntry type="module" module-name="intellij.android.adt.testutils" scope="TEST" />
<orderEntry type="module" module-name="intellij.android.artwork" scope="TEST" />
<orderEntry type="library" scope="TEST" name="studio-analytics-proto" level="project" />
+ <orderEntry type="module" module-name="intellij.android.adt.ui.model" scope="TEST" />
+ <orderEntry type="module" module-name="android.sdktools.flags" scope="TEST" />
+ <orderEntry type="library" scope="TEST" name="jetbrains.kotlinx.coroutines.test" level="project" />
+ <orderEntry type="library" scope="TEST" name="kotlin-test" level="project" />
</component>
</module>
\ No newline at end of file
diff --git a/app-quality-insights/ui/src/com/android/tools/idea/insights/ui/DetailsPanelHeader.kt b/app-quality-insights/ui/src/com/android/tools/idea/insights/ui/DetailsPanelHeader.kt
index 2d1734f..fdec8e2 100644
--- a/app-quality-insights/ui/src/com/android/tools/idea/insights/ui/DetailsPanelHeader.kt
+++ b/app-quality-insights/ui/src/com/android/tools/idea/insights/ui/DetailsPanelHeader.kt
@@ -15,8 +15,13 @@
*/
package com.android.tools.idea.insights.ui
+import com.android.tools.adtui.common.AdtUiUtils
import com.android.tools.adtui.util.ActionToolbarUtil
import com.android.tools.idea.insights.AppInsightsIssue
+import com.android.tools.idea.insights.AppInsightsProjectLevelController
+import com.android.tools.idea.insights.IssueVariant
+import com.android.tools.idea.insights.LoadingState
+import com.android.tools.idea.insights.Selection
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionToolbar
import com.intellij.openapi.actionSystem.ActionUpdateThread
@@ -27,17 +32,33 @@
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actions.AbstractToggleUseSoftWrapsAction
import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces
+import com.intellij.openapi.ui.getUserData
+import com.intellij.openapi.ui.putUserData
+import com.intellij.openapi.util.Key
import com.intellij.ui.JBColor
import com.intellij.ui.components.JBLabel
import com.intellij.ui.scale.JBUIScale
+import com.intellij.ui.util.preferredWidth
import com.intellij.util.ui.JBUI
import java.awt.BorderLayout
import java.awt.Dimension
+import java.awt.Font
+import java.awt.event.ComponentAdapter
+import java.awt.event.ComponentEvent
+import javax.swing.Box
+import javax.swing.BoxLayout
import javax.swing.JPanel
import javax.swing.border.CompoundBorder
+import kotlinx.coroutines.flow.MutableStateFlow
import org.jetbrains.annotations.VisibleForTesting
-class DetailsPanelHeader(editor: Editor) : JPanel(BorderLayout()) {
+private val KEY = Key.create<Pair<String, String>>("android.aqi.details.header")
+
+class DetailsPanelHeader(
+ editor: Editor,
+ private val controller: AppInsightsProjectLevelController,
+ private val supportsVariants: Boolean
+) : JPanel(BorderLayout()) {
@VisibleForTesting val titleLabel = JBLabel()
private val wrapAction =
@@ -54,9 +75,27 @@
ActionManager.getInstance()
.createActionToolbar("StackTraceToolbar", DefaultActionGroup(wrapAction), true)
+ @VisibleForTesting
+ val comboBoxStateFlow = MutableStateFlow<VariantComboBoxState>(DisabledComboBoxState.empty)
+ private val variantComboBox = VariantComboBox(controller.coroutineScope, comboBoxStateFlow)
+ @VisibleForTesting
+ val variantPanel =
+ transparentPanel(BorderLayout()).apply {
+ isVisible = false
+ add(JBLabel("|").apply { border = JBUI.Borders.empty(0, 5) }, BorderLayout.WEST)
+ add(variantComboBox, BorderLayout.CENTER)
+ }
+ private val contentPanel =
+ transparentPanel().apply {
+ layout = BoxLayout(this, BoxLayout.X_AXIS)
+ add(titleLabel)
+ add(Box.createHorizontalStrut(5))
+ add(variantPanel)
+ }
+
init {
border = JBUI.Borders.empty()
- add(titleLabel, BorderLayout.WEST)
+ add(contentPanel, BorderLayout.WEST)
toolbar.targetComponent = this
toolbar.layoutPolicy = ActionToolbar.NOWRAP_LAYOUT_POLICY
toolbar.setReservePlaceAutoPopupIcon(false)
@@ -69,22 +108,97 @@
border =
CompoundBorder(JBUI.Borders.customLineBottom(JBColor.border()), JBUI.Borders.emptyLeft(8))
preferredSize = Dimension(0, JBUIScale.scale(28))
+ variantComboBox.renderer = variantComboBoxListCellRenderer
+ variantComboBox.addItemListener { itemEvent ->
+ (itemEvent.item as? VariantRow)?.let { controller.selectIssueVariant(it.issueVariant) }
+ }
+ addComponentListener(
+ object : ComponentAdapter() {
+ override fun componentResized(e: ComponentEvent) {
+ val (className, methodName) = titleLabel.getUserData(KEY) ?: return
+ titleLabel.text = generateTitleLabelText(className, methodName)
+ }
+ }
+ )
}
- fun updateWithIssue(issue: AppInsightsIssue?) {
+ fun clear() {
titleLabel.icon = null
titleLabel.text = null
toolbar.component.isVisible = false
+ variantPanel.isVisible = false
+ }
- if (issue == null) return
-
+ fun updateWithIssue(issue: AppInsightsIssue) {
titleLabel.icon = issue.issueDetails.fatality.getIcon()
val (className, methodName) = issue.issueDetails.getDisplayTitle()
- val methodString =
- if (methodName.isNotEmpty()) {
- ".<B>$methodName</B>"
- } else ""
- titleLabel.text = "<html>$className$methodString</html>"
toolbar.component.isVisible = true
+ if (supportsVariants) {
+ comboBoxStateFlow.value = DisabledComboBoxState.loading
+ variantPanel.isVisible = true
+ titleLabel.putUserData(KEY, Pair(className, methodName))
+ titleLabel.text = generateTitleLabelText(className, methodName)
+ } else {
+ val methodString =
+ if (methodName.isNotEmpty()) {
+ ".<B>$methodName</B>"
+ } else ""
+ titleLabel.text = "<html>$className$methodString</html>"
+ }
+ }
+
+ fun updateComboBox(
+ issue: AppInsightsIssue,
+ variants: LoadingState.Done<Selection<IssueVariant>?>
+ ) {
+ require(supportsVariants)
+ when (variants) {
+ is LoadingState.Ready -> {
+ comboBoxStateFlow.value =
+ if (variants.value?.items.isNullOrEmpty()) DisabledComboBoxState.empty
+ else PopulatedComboBoxState(issue, variants.value!!)
+ }
+ is LoadingState.Failure -> {
+ comboBoxStateFlow.value = DisabledComboBoxState.failure
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun generateTitleLabelText(className: String, methodName: String): String {
+ val contentWidth = width - toolbar.component.width
+ var remainingWidth = contentWidth - 5 - variantPanel.preferredWidth - 20
+ if (remainingWidth <= 0) return "<html></html>"
+ val shrunkenMethodText =
+ if (methodName.isNotEmpty()) {
+ val methodFontMetrics = getFontMetrics(titleLabel.font.deriveFont(Font.BOLD))
+ AdtUiUtils.shrinkToFit(
+ methodName,
+ methodFontMetrics,
+ remainingWidth.toFloat(),
+ AdtUiUtils.ShrinkDirection.TRUNCATE_START
+ )
+ .also { remainingWidth -= methodFontMetrics.stringWidth(it) }
+ } else {
+ ""
+ }
+
+ val shrunkenClassText =
+ if (remainingWidth > 0) {
+ val classFontMetrics = getFontMetrics(titleLabel.font)
+ AdtUiUtils.shrinkToFit(
+ "$className.",
+ classFontMetrics,
+ remainingWidth.toFloat(),
+ AdtUiUtils.ShrinkDirection.TRUNCATE_START
+ )
+ .also { remainingWidth -= classFontMetrics.stringWidth(it) }
+ } else ""
+
+ val methodString =
+ if (shrunkenMethodText.isNotEmpty()) {
+ "<B>$shrunkenMethodText</B>"
+ } else ""
+ return "<html>$shrunkenClassText$methodString</html>"
}
}
diff --git a/app-quality-insights/ui/src/com/android/tools/idea/insights/ui/VariantComboBox.kt b/app-quality-insights/ui/src/com/android/tools/idea/insights/ui/VariantComboBox.kt
new file mode 100644
index 0000000..ca2c7ac
--- /dev/null
+++ b/app-quality-insights/ui/src/com/android/tools/idea/insights/ui/VariantComboBox.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.insights.ui
+
+import com.android.tools.adtui.model.stdui.DefaultCommonComboBoxModel
+import com.android.tools.adtui.stdui.CommonComboBox
+import com.android.tools.idea.insights.AppInsightsIssue
+import com.android.tools.idea.insights.IssueVariant
+import com.android.tools.idea.insights.Selection
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import org.jetbrains.annotations.VisibleForTesting
+
+/** Represents the different states the variant combobox could have. */
+sealed interface VariantComboBoxState
+
+data class DisabledComboBoxState(val message: String) : VariantComboBoxState {
+ companion object {
+ val loading = DisabledComboBoxState("Loading variants...")
+ val empty = DisabledComboBoxState("No variants available.")
+ val failure = DisabledComboBoxState("Failed to load variants.")
+ }
+}
+
+data class PopulatedComboBoxState(
+ val issue: AppInsightsIssue,
+ val variants: Selection<IssueVariant>
+) : VariantComboBoxState
+
+class VariantComboBox(scope: CoroutineScope, flow: Flow<VariantComboBoxState>) :
+ CommonComboBox<Row, DefaultCommonComboBoxModel<Row>>(
+ DefaultCommonComboBoxModel<Row>("All variants").apply { editable = false }
+ ) {
+ private var isDisabledIndex = false
+ private var currentVariantSelection: Selection<IssueVariant>? = null
+
+ init {
+ flow
+ .distinctUntilChanged()
+ .onEach { state ->
+ when (state) {
+ is DisabledComboBoxState -> {
+ model.removeAllElements()
+ model.addElement(DisabledTextRow(state.message))
+ model.enabled = false
+ }
+ is PopulatedComboBoxState -> {
+ val variantSize = state.variants.items.size
+ if (currentVariantSelection?.items != state.variants.items) {
+ model.removeAllElements()
+ val allItem = state.issue.toVariantRow(variantSize)
+ model.addElement(HeaderRow)
+ model.addElement(allItem)
+ model.addAll(state.variants.items.map { it.toVariantRow() })
+ model.selectedItem = allItem
+ }
+ if (currentVariantSelection?.selected != state.variants.selected) {
+ if (state.variants.selected == null) {
+ model.selectedItem = state.issue.toVariantRow(variantSize)
+ } else {
+ model.selectedItem = state.variants.selected!!.toVariantRow()
+ }
+ }
+ currentVariantSelection = state.variants
+ model.enabled = true
+ }
+ }
+ }
+ .launchIn(scope)
+ }
+
+ // Disable selection on header row
+ override fun setPopupVisible(visible: Boolean) {
+ if (!visible && isDisabledIndex) {
+ isDisabledIndex = false
+ } else {
+ super.setPopupVisible(visible)
+ }
+ }
+
+ override fun setSelectedIndex(anIndex: Int) {
+ if (getItemAt(anIndex) is HeaderRow) {
+ isDisabledIndex = true
+ } else {
+ super.setSelectedIndex(anIndex)
+ }
+ }
+
+ override fun setSelectedItem(anObject: Any?) {
+ if (anObject is HeaderRow) {
+ isDisabledIndex = true
+ } else {
+ super.setSelectedItem(anObject)
+ }
+ }
+}
+
+@VisibleForTesting
+fun AppInsightsIssue.toVariantRow(size: Int) =
+ VariantRow(
+ "All ($size variant${if (size > 1) "s" else ""})",
+ issueDetails.eventsCount,
+ issueDetails.impactedDevicesCount,
+ null
+ )
+
+@VisibleForTesting
+fun IssueVariant.toVariantRow() =
+ VariantRow("Variant ${id.take(4)}", eventsCount, impactedDevicesCount, this)
diff --git a/app-quality-insights/ui/src/com/android/tools/idea/insights/ui/VariantComboBoxListCellRenderer.kt b/app-quality-insights/ui/src/com/android/tools/idea/insights/ui/VariantComboBoxListCellRenderer.kt
new file mode 100644
index 0000000..ce35f08
--- /dev/null
+++ b/app-quality-insights/ui/src/com/android/tools/idea/insights/ui/VariantComboBoxListCellRenderer.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.insights.ui
+
+import com.android.tools.idea.insights.IssueVariant
+import com.intellij.ui.components.JBLabel
+import icons.StudioIcons
+import java.awt.BorderLayout
+import java.awt.Component
+import javax.swing.Box
+import javax.swing.BoxLayout
+import javax.swing.DefaultListCellRenderer
+import javax.swing.Icon
+import javax.swing.JPanel
+import javax.swing.ListCellRenderer
+import javax.swing.SwingConstants
+
+/** Represents a row to be rendered in the variants dropdown. */
+sealed interface Row {
+ fun getRendererComponent(): Component
+}
+
+/** A row containing variant information. */
+data class VariantRow(
+ val name: String,
+ val eventCount: Long,
+ val userCount: Long,
+ val issueVariant: IssueVariant?
+) : Row {
+ override fun getRendererComponent(): Component {
+ textLabel.update(name, null)
+ eventCountLabel.update(
+ eventCount.formatNumberToPrettyString(),
+ StudioIcons.AppQualityInsights.ISSUE
+ )
+ userCountLabel.update(
+ userCount.formatNumberToPrettyString(),
+ StudioIcons.LayoutEditor.Palette.QUICK_CONTACT_BADGE
+ )
+ return rendererComponent
+ }
+ companion object {
+ private val textLabel = JBLabel()
+ private val eventCountLabel = JBLabel()
+ private val userCountLabel = JBLabel()
+ private val rendererComponent =
+ JPanel().apply {
+ layout = BoxLayout(this, BoxLayout.X_AXIS)
+ isOpaque = false
+ add(textLabel)
+ add(Box.createHorizontalGlue())
+ add(eventCountLabel)
+ add(Box.createHorizontalStrut(4))
+ add(userCountLabel)
+ }
+
+ private fun JBLabel.update(value: String, icon: Icon?) {
+ removeAll()
+ this.icon = icon
+ toolTipText = value
+ text = value
+ }
+ }
+}
+
+/** Shown when the dropdown has no variant information. */
+data class DisabledTextRow(val text: String) : Row {
+ override fun getRendererComponent(): Component {
+ textComponent.text = text
+ return textComponent
+ }
+ companion object {
+ private val textComponent =
+ JBLabel().apply {
+ isEnabled = false
+ isOpaque = false
+ }
+ }
+}
+
+/** Represents the header of the variants list. */
+object HeaderRow : Row {
+ private val rendererComponent =
+ JPanel(BorderLayout()).apply {
+ add(
+ JBLabel("VARIANTS").apply {
+ isEnabled = false
+ isOpaque = false
+ },
+ BorderLayout.WEST
+ )
+ add(
+ JBLabel("IMPACT").apply {
+ isEnabled = false
+ isOpaque = false
+ horizontalAlignment = SwingConstants.RIGHT
+ },
+ BorderLayout.EAST
+ )
+ isOpaque = false
+ isEnabled = false
+ }
+
+ override fun getRendererComponent() = rendererComponent
+}
+
+private val defaultRenderer = DefaultListCellRenderer()
+
+val variantComboBoxListCellRenderer =
+ ListCellRenderer<Row> { _, value, _, _, _ ->
+ when (value) {
+ is Row -> value.getRendererComponent()
+ else -> defaultRenderer
+ }
+ }
diff --git a/app-quality-insights/ui/testSrc/com/android/tools/idea/insights/ui/DetailsPanelHeaderTest.kt b/app-quality-insights/ui/testSrc/com/android/tools/idea/insights/ui/DetailsPanelHeaderTest.kt
index 44f4535..a8e0152 100644
--- a/app-quality-insights/ui/testSrc/com/android/tools/idea/insights/ui/DetailsPanelHeaderTest.kt
+++ b/app-quality-insights/ui/testSrc/com/android/tools/idea/insights/ui/DetailsPanelHeaderTest.kt
@@ -15,10 +15,17 @@
*/
package com.android.tools.idea.insights.ui
+import com.android.tools.idea.insights.FakeAppInsightsProjectLevelController
import com.android.tools.idea.insights.ISSUE1
+import com.android.tools.idea.insights.ISSUE_VARIANT
+import com.android.tools.idea.insights.LoadingState
+import com.android.tools.idea.insights.Selection
import com.google.common.truth.Truth.assertThat
import com.intellij.openapi.editor.Editor
import com.intellij.testFramework.ApplicationRule
+import java.awt.Dimension
+import kotlin.test.assertFailsWith
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.mock
@@ -29,14 +36,18 @@
@Test
fun `header updates with issue`() {
- val detailsPanelHeader = DetailsPanelHeader(mock(Editor::class.java))
+ val detailsPanelHeader =
+ DetailsPanelHeader(mock(Editor::class.java), FakeAppInsightsProjectLevelController(), true)
+
+ detailsPanelHeader.size = Dimension(500, 200)
+ detailsPanelHeader.toolbar.component.size = Dimension(50, 50)
detailsPanelHeader.updateWithIssue(ISSUE1)
assertThat(detailsPanelHeader.titleLabel.text).isEqualTo("<html>crash.<B>Crash</B></html>")
assertThat(detailsPanelHeader.toolbar.component.isVisible).isTrue()
- detailsPanelHeader.updateWithIssue(null)
+ detailsPanelHeader.clear()
assertThat(detailsPanelHeader.toolbar.component.isVisible).isFalse()
assertThat(detailsPanelHeader.titleLabel.text).isNull()
@@ -45,8 +56,67 @@
@Test
fun `header is shown with bottom border`() {
- val detailsPanelHeader = DetailsPanelHeader(mock(Editor::class.java))
+ val detailsPanelHeader =
+ DetailsPanelHeader(mock(Editor::class.java), FakeAppInsightsProjectLevelController(), true)
assertThat(detailsPanelHeader.border.getBorderInsets(detailsPanelHeader).bottom).isEqualTo(1)
}
+
+ @Test
+ fun `header passes issue updates to combobox state flow`() {
+ val detailsPanelHeader =
+ DetailsPanelHeader(mock(Editor::class.java), FakeAppInsightsProjectLevelController(), true)
+ assertThat(detailsPanelHeader.variantPanel.isVisible).isFalse()
+
+ detailsPanelHeader.updateWithIssue(ISSUE1)
+ assertThat(detailsPanelHeader.variantPanel.isVisible).isTrue()
+ assertThat(detailsPanelHeader.comboBoxStateFlow.value).isEqualTo(DisabledComboBoxState.loading)
+
+ detailsPanelHeader.updateComboBox(ISSUE1, LoadingState.Ready(Selection.emptySelection()))
+ assertThat(detailsPanelHeader.variantPanel.isVisible).isTrue()
+ assertThat(detailsPanelHeader.comboBoxStateFlow.value).isEqualTo(DisabledComboBoxState.empty)
+
+ val selection = Selection(null, listOf(ISSUE_VARIANT))
+ detailsPanelHeader.updateComboBox(ISSUE1, LoadingState.Ready(selection))
+ assertThat(detailsPanelHeader.variantPanel.isVisible).isTrue()
+ assertThat(detailsPanelHeader.comboBoxStateFlow.value)
+ .isEqualTo(PopulatedComboBoxState(ISSUE1, selection))
+
+ detailsPanelHeader.updateComboBox(ISSUE1, LoadingState.NetworkFailure("failed"))
+ assertThat(detailsPanelHeader.variantPanel.isVisible).isTrue()
+ assertThat(detailsPanelHeader.comboBoxStateFlow.value).isEqualTo(DisabledComboBoxState.failure)
+
+ detailsPanelHeader.clear()
+ assertThat(detailsPanelHeader.variantPanel.isVisible).isFalse()
+ }
+
+ @Ignore("Investigate sizing discrepancies on different platforms")
+ @Test
+ fun `header width affects class name and method name in title label`() {
+ val detailsPanelHeader =
+ DetailsPanelHeader(mock(Editor::class.java), FakeAppInsightsProjectLevelController(), true)
+
+ detailsPanelHeader.size = Dimension(300, 200)
+ detailsPanelHeader.toolbar.component.size = Dimension(50, 50)
+ assertThat(detailsPanelHeader.generateTitleLabelText("DetailsPanelTest", "testMethod"))
+ .isEqualTo("<html><B>...ethod</B></html>")
+
+ detailsPanelHeader.size = Dimension(350, 200)
+ assertThat(detailsPanelHeader.generateTitleLabelText("DetailsPanelTest", "testMethod"))
+ .isEqualTo("<html>...st.<B>testMethod</B></html>")
+ }
+
+ @Test
+ fun `header should not show variants if variants not supported`() {
+ val detailsPanelHeader =
+ DetailsPanelHeader(mock(Editor::class.java), FakeAppInsightsProjectLevelController(), false)
+ assertThat(detailsPanelHeader.variantPanel.isVisible).isFalse()
+
+ detailsPanelHeader.updateWithIssue(ISSUE1)
+ assertThat(detailsPanelHeader.variantPanel.isVisible).isFalse()
+
+ assertFailsWith<IllegalArgumentException> {
+ detailsPanelHeader.updateComboBox(ISSUE1, LoadingState.Ready(Selection.emptySelection()))
+ }
+ }
}
diff --git a/app-quality-insights/ui/testSrc/com/android/tools/idea/insights/ui/VariantComboBoxTest.kt b/app-quality-insights/ui/testSrc/com/android/tools/idea/insights/ui/VariantComboBoxTest.kt
new file mode 100644
index 0000000..e5b5eaa
--- /dev/null
+++ b/app-quality-insights/ui/testSrc/com/android/tools/idea/insights/ui/VariantComboBoxTest.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.tools.idea.insights.ui
+
+import com.android.tools.idea.concurrency.createChildScope
+import com.android.tools.idea.insights.ISSUE1
+import com.android.tools.idea.insights.ISSUE_VARIANT
+import com.android.tools.idea.insights.Selection
+import com.google.common.truth.Truth.assertThat
+import com.intellij.testFramework.DisposableRule
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+
+class VariantComboBoxTest {
+
+ @get:Rule val disposableRule = DisposableRule()
+
+ @Test
+ fun `selection of header row is disabled in combobox`() = runTest {
+ val scope = createChildScope()
+ val flow = MutableSharedFlow<VariantComboBoxState>(1)
+ val comboBox = VariantComboBox(scope, flow)
+
+ flow.emit(PopulatedComboBoxState(ISSUE1, Selection(null, listOf(ISSUE_VARIANT))))
+
+ advanceUntilIdle()
+
+ val headerRow = comboBox.model.getElementAt(0)
+ assertThat(headerRow).isSameAs(HeaderRow)
+ assertThat(comboBox.selectedIndex).isEqualTo(1)
+
+ // Verify selected item doesn't change when selecting a header row
+ comboBox.selectedIndex = 0
+ assertThat(comboBox.selectedIndex).isEqualTo(1)
+
+ comboBox.selectedItem = headerRow
+ assertThat(comboBox.selectedIndex).isEqualTo(1)
+
+ scope.cancel()
+ }
+
+ @Test
+ fun `combo box shows disabled text when no variants are available`() = runTest {
+ val scope = createChildScope()
+ val flow = MutableSharedFlow<VariantComboBoxState>(1)
+ val comboBox = VariantComboBox(scope, flow)
+
+ flow.emit(DisabledComboBoxState.empty)
+
+ advanceUntilIdle()
+
+ assertThat(comboBox.selectedItem).isEqualTo(DisabledTextRow("No variants available."))
+
+ scope.cancel()
+ }
+
+ @Test
+ fun `combo box shows disabled text when variants fail to load`() = runTest {
+ val scope = createChildScope()
+ val flow = MutableSharedFlow<VariantComboBoxState>(1)
+ val comboBox = VariantComboBox(scope, flow)
+
+ flow.emit(DisabledComboBoxState.failure)
+
+ advanceUntilIdle()
+
+ assertThat(comboBox.selectedItem).isEqualTo(DisabledTextRow("Failed to load variants."))
+
+ scope.cancel()
+ }
+
+ @Test
+ fun `combo box shows loading text when in between requests`() = runTest {
+ val scope = createChildScope()
+ val flow = MutableSharedFlow<VariantComboBoxState>(1)
+ val comboBox = VariantComboBox(scope, flow)
+
+ flow.emit(DisabledComboBoxState.loading)
+
+ advanceUntilIdle()
+
+ assertThat(comboBox.selectedItem).isEqualTo(DisabledTextRow("Loading variants..."))
+
+ scope.cancel()
+ }
+
+ @Test
+ fun `combo box shows selection of variants when they exist`() = runTest {
+ val scope = createChildScope()
+ val flow = MutableSharedFlow<VariantComboBoxState>(1)
+ val comboBox = VariantComboBox(scope, flow)
+
+ val variant1 = ISSUE_VARIANT
+ val variant2 = ISSUE_VARIANT.copy(id = "variant2")
+
+ flow.emit(PopulatedComboBoxState(ISSUE1, Selection(variant2, listOf(variant1, variant2))))
+
+ advanceUntilIdle()
+
+ // Verify list of variants and selected variant.
+ assertThat(comboBox.model.getElementAt(0)).isSameAs(HeaderRow)
+ assertThat(comboBox.model.getElementAt(1)).isEqualTo(ISSUE1.toVariantRow(2))
+ assertThat(comboBox.model.getElementAt(2)).isEqualTo(variant1.toVariantRow())
+ assertThat(comboBox.model.getElementAt(3)).isEqualTo(variant2.toVariantRow())
+ assertThat(comboBox.selectedIndex).isEqualTo(3)
+
+ scope.cancel()
+ }
+}
diff --git a/build-attribution/src/com/android/build/attribution/ui/OpenBuildAnalyzerAction.kt b/build-attribution/src/com/android/build/attribution/ui/OpenBuildAnalyzerAction.kt
index dfcb8f9..f94b650 100644
--- a/build-attribution/src/com/android/build/attribution/ui/OpenBuildAnalyzerAction.kt
+++ b/build-attribution/src/com/android/build/attribution/ui/OpenBuildAnalyzerAction.kt
@@ -17,10 +17,16 @@
package com.android.build.attribution.ui
import com.android.build.attribution.BuildAnalyzerStorageManager
import com.android.build.attribution.ui.analytics.BuildAttributionUiAnalytics
+import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
class OpenBuildAnalyzerAction : AnAction("Analyze Build Performance") {
+
+ override fun getActionUpdateThread(): ActionUpdateThread {
+ return ActionUpdateThread.BGT
+ }
+
override fun update(e: AnActionEvent) {
val project = e.project
if(project == null) {
diff --git a/build-attribution/src/com/android/build/attribution/ui/OpenBuildAnalyzerResultsAction.kt b/build-attribution/src/com/android/build/attribution/ui/OpenBuildAnalyzerResultsAction.kt
index 8ef5620..f197a55 100644
--- a/build-attribution/src/com/android/build/attribution/ui/OpenBuildAnalyzerResultsAction.kt
+++ b/build-attribution/src/com/android/build/attribution/ui/OpenBuildAnalyzerResultsAction.kt
@@ -18,6 +18,7 @@
import com.android.build.attribution.BuildAnalyzerStorageManager
import com.android.tools.idea.flags.StudioFlags
+import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.ui.popup.JBPopupFactory
@@ -28,6 +29,11 @@
* Opens window with a list of previous Build Analyses results
*/
class OpenBuildAnalyzerResultsAction : AnAction("Show Results Analysis of Previous Builds") {
+
+ override fun getActionUpdateThread(): ActionUpdateThread {
+ return ActionUpdateThread.BGT
+ }
+
override fun update(e: AnActionEvent) {
val project = e.project
if (!StudioFlags.BUILD_ANALYZER_HISTORY.get() || project == null) {
diff --git a/compose-designer/src/META-INF/compose-designer.xml b/compose-designer/src/META-INF/compose-designer.xml
index a7650d9..11cd899 100644
--- a/compose-designer/src/META-INF/compose-designer.xml
+++ b/compose-designer/src/META-INF/compose-designer.xml
@@ -231,6 +231,12 @@
use-shortcut-of="ForceRefresh" >
<add-to-group group-id="Android.Designer.CommonActions" />
</action>
+
+ <action id="Android.Designer.ReRunUiCheckModeAction"
+ icon="AllIcons.Actions.Refresh"
+ class="com.android.tools.idea.compose.preview.actions.ReRunUiCheckModeAction">
+ <add-to-group group-id="Android.Designer.IssuePanel.ToolbarActions" anchor="first"/>
+ </action>
</actions>
</idea-plugin>
\ No newline at end of file
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewBundle.properties b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewBundle.properties
index bc1515d..9f3af96 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewBundle.properties
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewBundle.properties
@@ -192,6 +192,8 @@
vertical.layout=Vertical Layout
grid.layout=Grid Layout
+vertical.groups=Vertical Groups
+grid.groups=Grid Groups
new.list.layout.title=List
new.grid.layout.title=Grid
gallery.mode.title=Gallery
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewManager.kt b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewManager.kt
index 98a994b..274d909 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewManager.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewManager.kt
@@ -109,6 +109,9 @@
/** Flag to indicate if the preview filter is enabled or not. */
var isFilterEnabled: Boolean
+ /** Flag to indicate if the UI Check filter is enabled or not. */
+ var isUiCheckFilterEnabled: Boolean
+
/** Flag to indicate whether ATF checks should be run on the preview. */
val atfChecksEnabled: Boolean
get() = (currentOrNextMode as? PreviewMode.UiCheck)?.atfChecksEnabled ?: false
@@ -147,6 +150,7 @@
override val previewedFile: PsiFile? = null
override var isInspectionTooltipEnabled: Boolean = false
override var isFilterEnabled: Boolean = false
+ override var isUiCheckFilterEnabled: Boolean = false
override var mode: PreviewMode = PreviewMode.Default
override fun setMode(newMode: PreviewMode.Settable) {
mode = newMode
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewRepresentationProvider.kt b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewRepresentationProvider.kt
index ecf752d..f670ea1 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewRepresentationProvider.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewRepresentationProvider.kt
@@ -19,6 +19,7 @@
import com.android.tools.idea.common.editor.ToolbarActionGroups
import com.android.tools.idea.common.surface.DesignSurface
import com.android.tools.idea.common.type.DesignerTypeRegistrar
+import com.android.tools.idea.compose.preview.actions.ComposeColorBlindAction
import com.android.tools.idea.compose.preview.actions.ComposeFilterShowHistoryAction
import com.android.tools.idea.compose.preview.actions.ComposeFilterTextAction
import com.android.tools.idea.compose.preview.actions.ComposeNotificationGroup
@@ -28,7 +29,9 @@
import com.android.tools.idea.compose.preview.actions.ShowDebugBoundaries
import com.android.tools.idea.compose.preview.actions.StopAnimationInspectorAction
import com.android.tools.idea.compose.preview.actions.StopUiCheckPreviewAction
+import com.android.tools.idea.compose.preview.actions.UiCheckFilteringAction
import com.android.tools.idea.compose.preview.actions.visibleOnlyInComposeDefaultPreview
+import com.android.tools.idea.compose.preview.actions.visibleOnlyInUiCheck
import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.editors.sourcecode.isKotlinFileType
import com.android.tools.idea.flags.StudioFlags
@@ -46,6 +49,7 @@
import com.android.tools.idea.uibuilder.editor.multirepresentation.PreviewRepresentationProvider
import com.android.tools.idea.uibuilder.editor.multirepresentation.TextEditorWithMultiRepresentationPreview
import com.android.tools.idea.uibuilder.surface.LayoutManagerSwitcher
+import com.android.tools.idea.uibuilder.surface.NlDesignSurface
import com.android.tools.preview.ComposePreviewElementInstance
import com.google.wireless.android.sdk.stats.LayoutEditorState
import com.intellij.openapi.actionSystem.ActionGroup
@@ -109,9 +113,28 @@
it.setMode(PreviewMode.Default)
}
}
+ },
+ additionalActionProvider = {
+ if (StudioFlags.COMPOSE_COLORBLIND_MODE.get() && surface is NlDesignSurface)
+ ComposeColorBlindAction(surface)
+ else null
}
)
.visibleOnlyInStaticPreview(),
+ ComposeViewControlAction(
+ layoutManagerSwitcher = surface.sceneViewLayoutManager as LayoutManagerSwitcher,
+ layoutManagers = BASE_LAYOUT_MANAGER_OPTIONS,
+ isSurfaceLayoutActionEnabled = {
+ !isPreviewRefreshing(it.dataContext) &&
+ // If Essentials Mode is enabled, it should not be possible to switch layout.
+ !ComposePreviewEssentialsModeManager.isEssentialsModeEnabled
+ },
+ onSurfaceLayoutSelected = { _, _ -> },
+ additionalActionProvider = { dataContext ->
+ dataContext.getData(COMPOSE_PREVIEW_MANAGER)?.let { UiCheckFilteringAction(it) }
+ }
+ )
+ .visibleOnlyInUiCheck(),
StudioFlags.COMPOSE_DEBUG_BOUNDS.ifEnabled { ShowDebugBoundaries() },
)
) {
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewViewImpl.kt b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewViewImpl.kt
index a4bb4a7..106afc3 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewViewImpl.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/ComposePreviewViewImpl.kt
@@ -35,6 +35,7 @@
import com.android.tools.idea.editors.notifications.NotificationPanel
import com.android.tools.idea.editors.shortcuts.asString
import com.android.tools.idea.editors.shortcuts.getBuildAndRefreshShortcut
+import com.android.tools.idea.preview.navigation.PreviewNavigationHandler
import com.android.tools.idea.preview.refreshExistingPreviewElements
import com.android.tools.idea.preview.updatePreviewsAndRefresh
import com.android.tools.idea.projectsystem.requestBuild
@@ -148,6 +149,7 @@
onRenderCompleted: () -> Unit,
previewElementModelAdapter: ComposePreviewElementModelAdapter,
modelUpdater: NlModel.NlModelUpdaterInterface,
+ navigationHandler: PreviewNavigationHandler,
configureLayoutlibSceneManager:
(PreviewDisplaySettings, LayoutlibSceneManager) -> LayoutlibSceneManager
): List<ComposePreviewElementInstance> {
@@ -165,6 +167,7 @@
onRenderCompleted,
previewElementModelAdapter,
modelUpdater,
+ navigationHandler,
configureLayoutlibSceneManager
)
}
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/Preview.kt b/compose-designer/src/com/android/tools/idea/compose/preview/Preview.kt
index 4be390c..2db1630 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/Preview.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/Preview.kt
@@ -24,6 +24,7 @@
import com.android.tools.idea.common.model.DefaultModelUpdater
import com.android.tools.idea.common.model.NlModel
import com.android.tools.idea.common.surface.DelegateInteractionHandler
+import com.android.tools.idea.common.surface.updateSceneViewVisibilities
import com.android.tools.idea.compose.ComposePreviewElementsModel
import com.android.tools.idea.compose.preview.animation.ComposePreviewAnimationManager
import com.android.tools.idea.compose.preview.designinfo.hasDesignInfoProviders
@@ -81,6 +82,7 @@
import com.android.tools.idea.uibuilder.scene.accessibilityBasedHierarchyParser
import com.android.tools.idea.uibuilder.surface.LayoutManagerSwitcher
import com.android.tools.idea.uibuilder.surface.NlDesignSurface
+import com.android.tools.idea.uibuilder.visual.visuallint.VisualLintIssueProvider
import com.android.tools.idea.uibuilder.visual.visuallint.VisualLintMode
import com.android.tools.idea.util.toDisplayString
import com.android.tools.preview.ComposePreviewElementInstance
@@ -118,8 +120,13 @@
import com.intellij.problems.WolfTheProblemSolver
import com.intellij.psi.PsiFile
import com.intellij.psi.SmartPointerManager
-import com.intellij.psi.SmartPsiElementPointer
import com.intellij.util.ui.UIUtil
+import java.io.File
+import java.time.Duration
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicReference
+import javax.swing.JComponent
+import kotlin.properties.Delegates
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
@@ -149,11 +156,6 @@
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.base.util.module
import org.jetbrains.kotlin.psi.KtFile
-import java.io.File
-import java.time.Duration
-import java.util.concurrent.atomic.AtomicBoolean
-import java.util.concurrent.atomic.AtomicReference
-import javax.swing.JComponent
/** [Notification] group ID. Must match the `groupNotification` entry of `compose-designer.xml`. */
const val PREVIEW_NOTIFICATION_GROUP_ID = "Compose Preview Notification"
@@ -536,7 +538,25 @@
override val availableGroupsFlow: MutableStateFlow<Set<PreviewGroup.Named>> =
MutableStateFlow(setOf())
- private val navigationHandler = ComposePreviewNavigationHandler()
+ @VisibleForTesting
+ val navigationHandler =
+ ComposePreviewNavigationHandler().apply {
+ Disposer.register(this@ComposePreviewRepresentation, this)
+ }
+
+ override var isUiCheckFilterEnabled: Boolean by
+ Delegates.observable(false) { _, oldValue, newValue ->
+ if (oldValue == newValue) return@observable
+ launch(uiThread) {
+ if (newValue) {
+ surface.updateSceneViewVisibilities {
+ it.sceneManager.model in uiCheckFilterFlow.value.modelsWithErrors
+ }
+ } else {
+ surface.updateSceneViewVisibilities { true }
+ }
+ }
+ }
private val previewElementModelAdapter =
object : ComposePreviewElementModelAdapter() {
@@ -587,13 +607,35 @@
uiCheckFilterFlow.value = UiCheckModeFilter.Enabled(instance)
surface.background = Colors.INTERACTIVE_BACKGROUND_COLOR
withContext(uiThread) {
- IssuePanelService.getInstance(project)
- .startUiCheck(
- this@ComposePreviewRepresentation,
- instance.instanceId,
- instance.displaySettings.name,
- surface
- )
+ IssuePanelService.getInstance(project).startUiCheck(
+ this@ComposePreviewRepresentation,
+ instance.instanceId,
+ instance.displaySettings.name,
+ surface,
+ {
+ val models = mutableSetOf<NlModel>()
+ surface.visualLintIssueProvider
+ .getIssues()
+ .map { it.source }
+ .filterIsInstance<VisualLintIssueProvider.VisualLintIssueSource>()
+ .filter { models.addAll(it.models) }
+ uiCheckFilterFlow.value.modelsWithErrors = models
+ if (isUiCheckFilterEnabled) {
+ ApplicationManager.getApplication().invokeLater {
+ surface.updateSceneViewVisibilities { it.sceneManager.model in models }
+ surface.repaint()
+ }
+ }
+ }
+ ) {
+ // Pass preview manager and instance to the tab created for this UI Check preview.
+ // This enables restarting the UI Check mode from an action inside the tab.
+ when (it) {
+ COMPOSE_PREVIEW_MANAGER.name -> this@ComposePreviewRepresentation
+ COMPOSE_PREVIEW_ELEMENT_INSTANCE.name -> instance
+ else -> null
+ }
+ }
}
forceRefresh().join()
}
@@ -643,13 +685,13 @@
}
}
private fun getSlowData(dataId: String): Any? {
- return when {
- // The Compose preview NlModels do not point to the actual file but to a synthetic file
- // generated for Layoutlib. This ensures we return the right file.
- CommonDataKeys.VIRTUAL_FILE.`is`(dataId) -> psiFilePointer.virtualFile
- else -> null
+ return when {
+ // The Compose preview NlModels do not point to the actual file but to a synthetic file
+ // generated for Layoutlib. This ensures we return the right file.
+ CommonDataKeys.VIRTUAL_FILE.`is`(dataId) -> psiFilePointer.virtualFile
+ else -> null
+ }
}
-}
private val delegateInteractionHandler = DelegateInteractionHandler()
private val sceneComponentProvider = ComposeSceneComponentProvider()
@@ -683,6 +725,7 @@
composeWorkBench.mainSurface,
NavigatingInteractionHandler(
composeWorkBench.mainSurface,
+ navigationHandler,
isSelectionEnabled = { StudioFlags.COMPOSE_PREVIEW_SELECTION.get() }
)
)
@@ -1256,6 +1299,7 @@
previewElementModelAdapter,
if (atfChecksEnabled || visualLintingEnabled) accessibilityModelUpdater
else defaultModelUpdater,
+ navigationHandler,
this::configureLayoutlibSceneManagerForPreviewElement
)
if (progressIndicator.isCanceled) return // Return early if user has cancelled the refresh
@@ -1380,7 +1424,7 @@
withContext(workerThread) {
filteredPreviewElementsInstancesFlow.value.toList().sortByDisplayAndSourcePosition()
}
- composeWorkBench.hasContent = previewsToRender.isNotEmpty()
+ composeWorkBench.hasContent = previewsToRender.isNotEmpty() || isUiCheckPreview
if (!needsFullRefresh) {
requestLogger.debug(
"No updates on the PreviewElements, just refreshing the existing ones"
@@ -1599,6 +1643,7 @@
* and generate multiple previews, one per reference device for the user to check.
*/
sealed class UiCheckModeFilter {
+ var modelsWithErrors: Set<NlModel> = emptySet()
abstract val basePreviewInstance: ComposePreviewElementInstance?
abstract fun filterPreviewInstances(
previewInstances: Collection<ComposePreviewElementInstance>
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/PreviewDesignSurface.kt b/compose-designer/src/com/android/tools/idea/compose/preview/PreviewDesignSurface.kt
index 158b5cd..79d9a0c 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/PreviewDesignSurface.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/PreviewDesignSurface.kt
@@ -84,9 +84,23 @@
DesignSurface.SceneViewAlignment.LEFT,
)
-/** List of available layouts for the Compose Preview Surface. */
-internal val PREVIEW_LAYOUT_MANAGER_OPTIONS =
- if (!StudioFlags.COMPOSE_NEW_PREVIEW_LAYOUT.get()) {
+internal val BASE_LAYOUT_MANAGER_OPTIONS =
+ if (StudioFlags.COMPOSE_PREVIEW_GROUP_LAYOUT.get()) {
+ listOf(
+ SurfaceLayoutManagerOption(
+ // TODO(b/289994157) Change name to "List"
+ message("vertical.groups"),
+ GroupedListSurfaceLayoutManager(5, PREVIEW_FRAME_PADDING_PROVIDER, NO_GROUP_TRANSFORM),
+ DesignSurface.SceneViewAlignment.LEFT
+ ),
+ SurfaceLayoutManagerOption(
+ // TODO(b/289994157) Change name to "Grid"
+ message("grid.groups"),
+ GroupedGridSurfaceLayoutManager(5, PREVIEW_FRAME_PADDING_PROVIDER, NO_GROUP_TRANSFORM),
+ DesignSurface.SceneViewAlignment.LEFT,
+ )
+ )
+ } else if (!StudioFlags.COMPOSE_NEW_PREVIEW_LAYOUT.get()) {
listOf(
SurfaceLayoutManagerOption(
message("vertical.layout"),
@@ -107,8 +121,7 @@
NlConstants.SCREEN_DELTA
),
DesignSurface.SceneViewAlignment.LEFT
- ),
- PREVIEW_LAYOUT_GALLERY_OPTION
+ )
)
} else {
listOf(
@@ -121,11 +134,14 @@
message("new.grid.layout.title"),
GroupedGridSurfaceLayoutManager(5, PREVIEW_FRAME_PADDING_PROVIDER, NO_GROUP_TRANSFORM),
DesignSurface.SceneViewAlignment.LEFT,
- ),
- PREVIEW_LAYOUT_GALLERY_OPTION
+ )
)
}
+/** List of available layouts for the Compose Preview Surface. */
+internal val PREVIEW_LAYOUT_MANAGER_OPTIONS =
+ BASE_LAYOUT_MANAGER_OPTIONS + PREVIEW_LAYOUT_GALLERY_OPTION
+
/** Default layout manager selected in the preview. */
internal val DEFAULT_PREVIEW_LAYOUT_MANAGER = PREVIEW_LAYOUT_MANAGER_OPTIONS.first().layoutManager
@@ -146,8 +162,7 @@
screenViewProvider: ScreenViewProvider
): NlDesignSurface.Builder =
NlDesignSurface.builder(project, parentDisposable)
- .setNavigationHandler(navigationHandler)
- .setActionManagerProvider { surface -> PreviewSurfaceActionManager(surface) }
+ .setActionManagerProvider { surface -> PreviewSurfaceActionManager(surface, navigationHandler) }
.setInteractionHandlerProvider { delegateInteractionHandler }
.setActionHandler { surface -> PreviewSurfaceActionHandler(surface) }
.setSceneManagerProvider { surface, model ->
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/actions/ActionUtils.kt b/compose-designer/src/com/android/tools/idea/compose/preview/actions/ActionUtils.kt
index ff7f945..18f8376 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/actions/ActionUtils.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/actions/ActionUtils.kt
@@ -37,6 +37,16 @@
}
}
+private class ComposePreviewUiCheckWrapper(actions: List<AnAction>) : DefaultActionGroup(actions) {
+ override fun update(e: AnActionEvent) {
+ super.update(e)
+
+ e.getData(COMPOSE_PREVIEW_MANAGER)?.let {
+ e.presentation.isVisible = it.mode is PreviewMode.UiCheck
+ }
+ }
+}
+
/**
* Makes the given action only visible when the Compose preview is not in interactive or animation
* modes. Returns an [ActionGroup] that handles the visibility.
@@ -45,6 +55,13 @@
ComposePreviewDefaultWrapper(listOf(this))
/**
+ * Makes the given action only visible when the Compose preview is in UI Check mode. Returns an
+ * [ActionGroup] that handles the visibility.
+ */
+internal fun AnAction.visibleOnlyInUiCheck(): ActionGroup =
+ ComposePreviewUiCheckWrapper(listOf(this))
+
+/**
* The given disables the actions if any surface is refreshing or if the [sceneView] contains
* errors.
*/
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/actions/ComposeViewControlAction.kt b/compose-designer/src/com/android/tools/idea/compose/preview/actions/ComposeViewControlAction.kt
index 9f696ca..1763fcf 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/actions/ComposeViewControlAction.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/actions/ComposeViewControlAction.kt
@@ -27,7 +27,6 @@
import com.android.tools.idea.compose.preview.message
import com.android.tools.idea.flags.StudioFlags
import com.android.tools.idea.uibuilder.surface.LayoutManagerSwitcher
-import com.android.tools.idea.uibuilder.surface.NlDesignSurface
import com.google.common.annotations.VisibleForTesting
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.ActionUpdateThread
@@ -46,7 +45,8 @@
private val layoutManagerSwitcher: LayoutManagerSwitcher,
private val layoutManagers: List<SurfaceLayoutManagerOption>,
private val isSurfaceLayoutActionEnabled: (AnActionEvent) -> Boolean = { true },
- private val onSurfaceLayoutSelected: (SurfaceLayoutManagerOption, DataContext) -> Unit
+ private val onSurfaceLayoutSelected: (SurfaceLayoutManagerOption, DataContext) -> Unit,
+ private val additionalActionProvider: (DataContext) -> AnAction?
) :
DropDownAction(
message("action.scene.view.control.title"),
@@ -98,11 +98,9 @@
// TODO(263038548): Implement Zoom-to-selection when preview is selectable.
addSeparator()
add(ShowInspectionTooltipsAction(context))
- if (StudioFlags.COMPOSE_COLORBLIND_MODE.get()) {
- (context.getData(DESIGN_SURFACE) as? NlDesignSurface)?.let { surface ->
- addSeparator()
- add(ComposeColorBlindAction(surface))
- }
+ additionalActionProvider(context)?.let {
+ addSeparator()
+ add(it)
}
return true
}
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/actions/JumpToDefinitionAction.kt b/compose-designer/src/com/android/tools/idea/compose/preview/actions/JumpToDefinitionAction.kt
index 4c24f64..18bf62a 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/actions/JumpToDefinitionAction.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/actions/JumpToDefinitionAction.kt
@@ -18,9 +18,9 @@
import com.android.tools.idea.common.surface.DesignSurface
import com.android.tools.idea.common.surface.SceneView
import com.android.tools.idea.compose.preview.message
-import com.android.tools.idea.compose.preview.navigation.ComposePreviewNavigationHandler
import com.android.tools.idea.concurrency.AndroidCoroutineScope
import com.android.tools.idea.uibuilder.scene.LayoutlibSceneManager
+import com.android.tools.idea.uibuilder.surface.NavigationHandler
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
@@ -34,7 +34,7 @@
*/
class JumpToDefinitionAction(
surface: DesignSurface<LayoutlibSceneManager>,
- private val composePreviewNavigationHandler: ComposePreviewNavigationHandler,
+ private val navigationHandler: NavigationHandler,
private val sceneView: SceneView,
title: String = message("action.jump.to.definition")
) : AnAction(title) {
@@ -63,8 +63,6 @@
}
override fun actionPerformed(e: AnActionEvent) {
- scope.launch {
- composePreviewNavigationHandler.handleNavigateWithCoordinates(sceneView, x, y, true)
- }
+ scope.launch { navigationHandler.handleNavigateWithCoordinates(sceneView, x, y, true) }
}
}
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/actions/PreviewSurfaceActionManager.kt b/compose-designer/src/com/android/tools/idea/compose/preview/actions/PreviewSurfaceActionManager.kt
index d67d364..bda53ff 100644
--- a/compose-designer/src/com/android/tools/idea/compose/preview/actions/PreviewSurfaceActionManager.kt
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/actions/PreviewSurfaceActionManager.kt
@@ -19,15 +19,18 @@
import com.android.tools.idea.common.editor.ActionManager
import com.android.tools.idea.common.model.NlComponent
import com.android.tools.idea.common.surface.DesignSurface
+import com.android.tools.idea.common.surface.InteractiveLabelPanel
+import com.android.tools.idea.common.surface.LabelPanel
+import com.android.tools.idea.common.surface.LayoutData
import com.android.tools.idea.common.surface.SceneView
import com.android.tools.idea.compose.preview.essentials.ComposePreviewEssentialsModeManager
import com.android.tools.idea.compose.preview.message
-import com.android.tools.idea.compose.preview.navigation.ComposePreviewNavigationHandler
import com.android.tools.idea.preview.actions.EnableInteractiveAction
import com.android.tools.idea.preview.actions.createStatusIcon
import com.android.tools.idea.preview.actions.hideIfRenderErrors
import com.android.tools.idea.preview.actions.visibleOnlyInStaticPreview
import com.android.tools.idea.uibuilder.scene.LayoutlibSceneManager
+import com.android.tools.idea.uibuilder.surface.NavigationHandler
import com.android.tools.idea.uibuilder.surface.NlDesignSurface
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.actionSystem.IdeActions
@@ -38,7 +41,8 @@
/** [ActionManager] to be used by the Compose Preview. */
internal class PreviewSurfaceActionManager(
- private val surface: DesignSurface<LayoutlibSceneManager>
+ private val surface: DesignSurface<LayoutlibSceneManager>,
+ private val navigationHandler: NavigationHandler,
) : ActionManager<DesignSurface<LayoutlibSceneManager>>(surface) {
private val sceneManagerProvider: () -> LayoutlibSceneManager? = {
@@ -58,6 +62,14 @@
registerAction(copyResultImageAction, IdeActions.ACTION_COPY, component)
}
+ override fun createSceneViewLabel(sceneView: SceneView): LabelPanel {
+ return InteractiveLabelPanel(
+ LayoutData.fromSceneView(sceneView),
+ surface,
+ suspend { navigationHandler.handleNavigate(sceneView, false) }
+ )
+ }
+
override fun getPopupMenuActions(leafComponent: NlComponent?): DefaultActionGroup {
// Copy Image
val actionGroup = DefaultActionGroup().apply { add(copyResultImageAction) }
@@ -68,9 +80,8 @@
actionGroup.add(ZoomToSelectionAction(surface, sceneView))
}
// Jump to Definition
- ((surface as? NlDesignSurface)?.navigationHandler as? ComposePreviewNavigationHandler)?.let {
- actionGroup.add(JumpToDefinitionAction(surface, it, sceneView))
- }
+ actionGroup.add(JumpToDefinitionAction(surface, navigationHandler, sceneView))
+
return actionGroup
}
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/actions/ReRunUiCheckModeAction.kt b/compose-designer/src/com/android/tools/idea/compose/preview/actions/ReRunUiCheckModeAction.kt
new file mode 100644
index 0000000..0ef57f7
--- /dev/null
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/actions/ReRunUiCheckModeAction.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.compose.preview.actions
+
+import com.android.tools.idea.compose.preview.COMPOSE_PREVIEW_ELEMENT_INSTANCE
+import com.android.tools.idea.compose.preview.COMPOSE_PREVIEW_MANAGER
+import com.android.tools.idea.compose.preview.ComposePreviewManager
+import com.android.tools.idea.preview.modes.PreviewMode
+import com.android.tools.preview.ComposePreviewElementInstance
+import com.intellij.analysis.problemsView.toolWindow.ProblemsView
+import com.intellij.ide.impl.DataManagerImpl
+import com.intellij.openapi.actionSystem.ActionUpdateThread
+import com.intellij.openapi.actionSystem.AnAction
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.openapi.actionSystem.DataProvider
+import com.intellij.openapi.fileEditor.FileEditorManager
+import com.intellij.openapi.project.Project
+
+private const val DISABLED_TEXT =
+ "UI Check is running in the background for this composable. Stop UI Check mode."
+private const val ENABLED_TEXT = "Restart UI Check and background linting for this composable."
+
+class ReRunUiCheckModeAction : AnAction() {
+
+ override fun update(e: AnActionEvent) {
+ val project = e.project ?: return
+ val dataContext = uiTabDataContext(project) ?: return
+ val manager = dataContext.getData(COMPOSE_PREVIEW_MANAGER.name) as? ComposePreviewManager
+ e.presentation.isVisible = manager != null
+ manager?.let {
+ if (it.isUiCheckPreview) {
+ e.presentation.isEnabled = false
+ e.presentation.text = DISABLED_TEXT
+ } else {
+ e.presentation.isEnabled = true
+ e.presentation.text = ENABLED_TEXT
+ }
+ }
+ e.presentation.isEnabled = manager?.isUiCheckPreview?.not() ?: false
+
+ super.update(e)
+ }
+
+ override fun actionPerformed(e: AnActionEvent) {
+ val project = e.project ?: return
+ val dataContext = uiTabDataContext(project) ?: return
+ val manager =
+ dataContext.getData(COMPOSE_PREVIEW_MANAGER.name) as? ComposePreviewManager ?: return
+ val instance =
+ dataContext.getData(COMPOSE_PREVIEW_ELEMENT_INSTANCE.name) as? ComposePreviewElementInstance
+ ?: return
+ manager.setMode(PreviewMode.UiCheck(selected = instance))
+ instance.containingFile?.let {
+ FileEditorManager.getInstance(project).openFile(it.virtualFile, true, true)
+ }
+ }
+
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+
+ private fun uiTabDataContext(project: Project): DataProvider? {
+ val tabComponent =
+ ProblemsView.getToolWindow(project)?.contentManagerIfCreated?.selectedContent?.component
+ ?: return null
+ return DataManagerImpl.getDataProviderEx(tabComponent)
+ }
+}
diff --git a/compose-designer/src/com/android/tools/idea/compose/preview/actions/UiCheckFilteringAction.kt b/compose-designer/src/com/android/tools/idea/compose/preview/actions/UiCheckFilteringAction.kt
new file mode 100644
index 0000000..380c8b8
--- /dev/null
+++ b/compose-designer/src/com/android/tools/idea/compose/preview/actions/UiCheckFilteringAction.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.compose.preview.actions
+
+import com.android.tools.idea.compose.preview.ComposePreviewManager
+import com.intellij.openapi.actionSystem.ActionUpdateThread
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.openapi.actionSystem.ToggleAction
+
+internal class UiCheckFilteringAction(private val previewManager: ComposePreviewManager) :
+ ToggleAction("Show Previews With Problems Only") {
+ override fun isSelected(e: AnActionEvent) = previewManager.isUiCheckFilterEnabled
+
+ override fun setSelected(e: AnActionEvent, state: Boolean) {
+ previewManager.isUiCheckFilterEnabled = state
+ }
+
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+}
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/gradle/datasource/ParametrizedPreviewTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/gradle/datasource/ParametrizedPreviewTest.kt
index 29598e0..002c686 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/gradle/datasource/ParametrizedPreviewTest.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/gradle/datasource/ParametrizedPreviewTest.kt
@@ -23,7 +23,6 @@
import com.android.tools.idea.compose.preview.SIMPLE_COMPOSE_PROJECT_PATH
import com.android.tools.idea.compose.preview.SimpleComposeAppPaths
import com.android.tools.idea.compose.preview.TestComposePreviewView
-import com.android.tools.idea.compose.preview.navigation.ComposePreviewNavigationHandler
import com.android.tools.idea.compose.preview.renderer.renderPreviewElementForResult
import com.android.tools.idea.concurrency.awaitStatus
import com.android.tools.idea.editors.build.ProjectStatus
@@ -278,11 +277,8 @@
.resolve()
assertEquals(3, elements.count())
- val navigationHandler = ComposePreviewNavigationHandler()
val mainSurface =
- NlDesignSurface.builder(project, projectRule.fixture.testRootDisposable)
- .setNavigationHandler(navigationHandler)
- .build()
+ NlDesignSurface.builder(project, projectRule.fixture.testRootDisposable).build()
val composeView = TestComposePreviewView(mainSurface)
val preview =
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/gradle/preview/RenderErrorTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/gradle/preview/RenderErrorTest.kt
index e317ea6..a0c4d9f 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/gradle/preview/RenderErrorTest.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/gradle/preview/RenderErrorTest.kt
@@ -26,6 +26,7 @@
import com.android.tools.idea.compose.preview.ComposePreviewRepresentation
import com.android.tools.idea.compose.preview.SIMPLE_COMPOSE_PROJECT_PATH
import com.android.tools.idea.compose.preview.SimpleComposeAppPaths
+import com.android.tools.idea.flags.StudioFlags
import com.android.tools.idea.preview.modes.PreviewMode
import com.android.tools.idea.uibuilder.editor.multirepresentation.PreferredVisibility
import com.android.tools.idea.uibuilder.scene.hasRenderErrors
@@ -196,9 +197,10 @@
// The visible/invisible state before the update shouldn't affect the final result
for (visibleBefore in listOf(true, false)) {
// The animation preview action shouldn't be visible because the preview being used doesn't
- // contain animations, but the interactive and deploy to device actions should be visible as
- // there are no render errors.
- assertEquals(2, countVisibleActions(actions, visibleBefore))
+ // contain animations, but the interactive, ui check and deploy to device actions should be
+ // visible as there are no render errors.
+ val visibleActionCount = if (StudioFlags.NELE_COMPOSE_UI_CHECK_MODE.get()) 3 else 2
+ assertEquals(visibleActionCount, countVisibleActions(actions, visibleBefore))
}
}
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/preview/ComposePreviewRepresentationTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/preview/ComposePreviewRepresentationTest.kt
index d5910d6..1e18329 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/preview/ComposePreviewRepresentationTest.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/preview/ComposePreviewRepresentationTest.kt
@@ -20,8 +20,9 @@
import com.android.tools.idea.common.surface.DesignSurface
import com.android.tools.idea.common.surface.DesignSurfaceListener
import com.android.tools.idea.compose.ComposeProjectRule
+import com.android.tools.idea.compose.preview.actions.ReRunUiCheckModeAction
import com.android.tools.idea.compose.preview.gallery.ComposeGalleryMode
-import com.android.tools.idea.compose.preview.navigation.ComposePreviewNavigationHandler
+import com.android.tools.idea.concurrency.AndroidDispatchers.uiThread
import com.android.tools.idea.concurrency.AndroidDispatchers.workerThread
import com.android.tools.idea.concurrency.awaitStatus
import com.android.tools.idea.editors.build.ProjectStatus
@@ -47,6 +48,7 @@
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.wm.RegisterToolWindowTask
import com.intellij.openapi.wm.ToolWindowManager
+import com.intellij.testFramework.TestActionEvent
import com.intellij.testFramework.assertInstanceOf
import com.intellij.testFramework.runInEdtAndWait
import java.util.UUID
@@ -55,6 +57,7 @@
import javax.swing.JPanel
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.junit.After
@@ -104,7 +107,7 @@
Logger.getInstance(FastPreviewManager::class.java).setLevel(LogLevel.ALL)
Logger.getInstance(ProjectStatus::class.java).setLevel(LogLevel.ALL)
logger.info("setup")
- val testProjectSystem = TestProjectSystem(project)
+ val testProjectSystem = TestProjectSystem(project).apply { usesCompose = true }
runInEdtAndWait { testProjectSystem.useInTests() }
logger.info("setup complete")
ToolWindowManager.getInstance(project)
@@ -145,11 +148,7 @@
)
}
- val navigationHandler = ComposePreviewNavigationHandler()
- val mainSurface =
- NlDesignSurface.builder(project, fixture.testRootDisposable)
- .setNavigationHandler(navigationHandler)
- .build()
+ val mainSurface = NlDesignSurface.builder(project, fixture.testRootDisposable).build()
val modelRenderedLatch = CountDownLatch(2)
mainSurface.addListener(
@@ -181,7 +180,9 @@
delayWhileRefreshingOrDumb(preview)
}
- mainSurface.models.forEach { assertTrue(navigationHandler.defaultNavigationMap.contains(it)) }
+ mainSurface.models.forEach {
+ assertTrue(preview.navigationHandler.defaultNavigationMap.contains(it))
+ }
assertThat(preview.availableGroupsFlow.value.map { it.displayName }).containsExactly("groupA")
@@ -224,11 +225,7 @@
)
}
- val navigationHandler = ComposePreviewNavigationHandler()
- val mainSurface =
- NlDesignSurface.builder(project, fixture.testRootDisposable)
- .setNavigationHandler(navigationHandler)
- .build()
+ val mainSurface = NlDesignSurface.builder(project, fixture.testRootDisposable).build()
val modelRenderedLatch = CountDownLatch(2)
mainSurface.addListener(
@@ -322,6 +319,13 @@
// Check that the UI Check tab has been created
assertEquals(2, contentManager.contents.size)
assertNotNull(contentManager.findContent(uiCheckElement.displaySettings.name))
+ val rerunAction = ReRunUiCheckModeAction()
+ run {
+ val actionEvent = TestActionEvent.createTestEvent()
+ rerunAction.update(actionEvent)
+ assertTrue(actionEvent.presentation.isVisible)
+ assertFalse(actionEvent.presentation.isEnabled)
+ }
// Stop UI Check mode
preview.setMode(PreviewMode.Default)
@@ -354,6 +358,53 @@
// Check that the UI Check tab is still present
assertEquals(2, contentManager.contents.size)
assertNotNull(contentManager.findContent(uiCheckElement.displaySettings.name))
+ run {
+ val actionEvent = TestActionEvent.createTestEvent()
+ rerunAction.update(actionEvent)
+ assertTrue(actionEvent.presentation.isEnabledAndVisible)
+ }
+
+ // Re-run UI check with the problems panel action
+ launch(uiThread) { rerunAction.actionPerformed(TestActionEvent.createTestEvent()) }
+ delayUntilCondition(250) { preview.isUiCheckPreview }
+ preview.filteredPreviewElementsInstancesFlowForTest().awaitStatus(
+ "Failed set uiCheckMode",
+ 5.seconds
+ ) {
+ it.size > 2
+ }
+ assertEquals(
+ """
+ TestKt.Preview1
+ spec:id=reference_phone,shape=Normal,width=411,height=891,unit=dp,dpi=420
+ PreviewDisplaySettings(name=Preview1 - _device_class_phone, group=Screen sizes, showDecoration=true, showBackground=false, backgroundColor=null, displayPositioning=NORMAL)
+
+ TestKt.Preview1
+ spec:shape=Normal,width=673,height=841,unit=dp,dpi=480
+ PreviewDisplaySettings(name=Preview1 - _device_class_foldable, group=Screen sizes, showDecoration=true, showBackground=false, backgroundColor=null, displayPositioning=NORMAL)
+
+ TestKt.Preview1
+ spec:shape=Normal,width=1280,height=800,unit=dp,dpi=420
+ PreviewDisplaySettings(name=Preview1 - _device_class_tablet, group=Screen sizes, showDecoration=true, showBackground=false, backgroundColor=null, displayPositioning=NORMAL)
+
+ TestKt.Preview1
+ spec:shape=Normal,width=1920,height=1080,unit=dp,dpi=420
+ PreviewDisplaySettings(name=Preview1 - _device_class_desktop, group=Screen sizes, showDecoration=true, showBackground=false, backgroundColor=null, displayPositioning=NORMAL)
+
+ TestKt.Preview1
+ spec:parent=_device_class_phone,orientation=landscape
+ PreviewDisplaySettings(name=Preview1 - _device_class_phone-landscape, group=Screen sizes, showDecoration=true, showBackground=false, backgroundColor=null, displayPositioning=NORMAL)
+
+ """
+ .trimIndent(),
+ preview.filteredPreviewElementsInstancesFlowForTest().value.joinToString("\n") {
+ "${it.methodFqn}\n${it.configuration.deviceSpec}\n${it.displaySettings}\n"
+ }
+ )
+
+ // Stop UI Check mode
+ preview.setMode(PreviewMode.Default)
+ delayUntilCondition(250) { preview.isInNormalMode }
// Restart UI Check mode on the same preview
preview.setMode(PreviewMode.UiCheck(uiCheckElement))
@@ -391,11 +442,7 @@
)
}
- val navigationHandler = ComposePreviewNavigationHandler()
- val mainSurface =
- NlDesignSurface.builder(project, fixture.testRootDisposable)
- .setNavigationHandler(navigationHandler)
- .build()
+ val mainSurface = NlDesignSurface.builder(project, fixture.testRootDisposable).build()
val modelRenderedLatch = CountDownLatch(2)
mainSurface.addListener(
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/preview/ComposePreviewViewImplTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/preview/ComposePreviewViewImplTest.kt
index f22a4b0e..c962290 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/preview/ComposePreviewViewImplTest.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/preview/ComposePreviewViewImplTest.kt
@@ -254,6 +254,7 @@
},
testPreviewElementModelAdapter,
DefaultModelUpdater(),
+ navigationHandler = ComposePreviewNavigationHandler(),
::configureLayoutlibSceneManagerForPreviewElement
)
}
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/preview/TestComposePreviewManager.kt b/compose-designer/testSrc/com/android/tools/idea/compose/preview/TestComposePreviewManager.kt
index 37272e7..40dcc59 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/preview/TestComposePreviewManager.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/preview/TestComposePreviewManager.kt
@@ -51,6 +51,8 @@
override var isFilterEnabled: Boolean = false
+ override var isUiCheckFilterEnabled: Boolean = false
+
override var atfChecksEnabled: Boolean = false
override var mode: PreviewMode = PreviewMode.Default
diff --git a/compose-designer/testSrc/com/android/tools/idea/compose/preview/actions/ComposeViewControlActionTest.kt b/compose-designer/testSrc/com/android/tools/idea/compose/preview/actions/ComposeViewControlActionTest.kt
index 799862d..3a8bfe4 100644
--- a/compose-designer/testSrc/com/android/tools/idea/compose/preview/actions/ComposeViewControlActionTest.kt
+++ b/compose-designer/testSrc/com/android/tools/idea/compose/preview/actions/ComposeViewControlActionTest.kt
@@ -93,7 +93,8 @@
ComposeViewControlAction(
EmptyLayoutManagerSwitcher,
options,
- onSurfaceLayoutSelected = { _, _ -> }
+ onSurfaceLayoutSelected = { _, _ -> },
+ additionalActionProvider = { _ -> ComposeColorBlindAction(designSurfaceMock) }
)
viewControlAction.updateActions(context)
@@ -146,7 +147,8 @@
ComposeViewControlAction(
EmptyLayoutManagerSwitcher,
options,
- onSurfaceLayoutSelected = { _, _ -> }
+ onSurfaceLayoutSelected = { _, _ -> },
+ additionalActionProvider = { _ -> ComposeColorBlindAction(designSurfaceMock) }
)
viewControlAction.updateActions(context)
@@ -206,7 +208,8 @@
ComposeViewControlAction(
EmptyLayoutManagerSwitcher,
options,
- onSurfaceLayoutSelected = { _, _ -> }
+ onSurfaceLayoutSelected = { _, _ -> },
+ additionalActionProvider = { _ -> ComposeColorBlindAction(designSurfaceMock) }
)
viewControlAction.updateActions(context)
@@ -269,7 +272,8 @@
ComposeViewControlAction(
EmptyLayoutManagerSwitcher,
listOf(createOption("Layout A", EmptySurfaceLayoutManager())),
- onSurfaceLayoutSelected = { _, _ -> }
+ onSurfaceLayoutSelected = { _, _ -> },
+ additionalActionProvider = { _ -> null }
)
manager.currentStatus = nonRefreshingStatus
@@ -300,7 +304,7 @@
val option = listOf(SurfaceLayoutManagerOption("Layout A", EmptySurfaceLayoutManager()))
var enabled = true
- val action = ComposeViewControlAction(switcher, option, { enabled }) { _, _ -> }
+ val action = ComposeViewControlAction(switcher, option, { enabled }, { _, _ -> }) { _ -> null }
val presentation = Presentation()
// It should always not be multi-choice no matter it is enabled or not.
diff --git a/compose-ide-plugin/resources/messages/ComposeBundle.properties b/compose-ide-plugin/resources/messages/ComposeBundle.properties
index c02d985..e367be5 100644
--- a/compose-ide-plugin/resources/messages/ComposeBundle.properties
+++ b/compose-ide-plugin/resources/messages/ComposeBundle.properties
@@ -33,6 +33,8 @@
separate.preview.usages=Separate @Preview Usages
usage.group.in.preview.function=@Preview usages
usage.group.in.nonpreview.function=Production usages
+compose.color.picker.name=Compose color picker
+compose.color.picker.tooltip=Change color
# British spelling is used to be consistent with KotlinDebuggerCoroutinesBundle.properties optimised.variable.message
recomposition.optimised.variable.message={0} was optimised out
diff --git a/compose-ide-plugin/src/META-INF/plugin.k2.xml b/compose-ide-plugin/src/META-INF/plugin.k2.xml
index ef6dbc7..1def879 100644
--- a/compose-ide-plugin/src/META-INF/plugin.k2.xml
+++ b/compose-ide-plugin/src/META-INF/plugin.k2.xml
@@ -17,6 +17,5 @@
<idea-plugin>
<extensions defaultExtensionNs="org.jetbrains.kotlin">
<codeinsight.quickfix.registrar implementation="com.android.tools.compose.aa.intentions.ComposeIdePluginQuickFixRegistrar"/>
- <!-- TODO (b/279035026): K2 Implementation -->
</extensions>
</idea-plugin>
diff --git a/compose-ide-plugin/src/META-INF/plugin.xml b/compose-ide-plugin/src/META-INF/plugin.xml
index 74a40d9..187ff98 100644
--- a/compose-ide-plugin/src/META-INF/plugin.xml
+++ b/compose-ide-plugin/src/META-INF/plugin.xml
@@ -31,7 +31,6 @@
<extensions defaultExtensionNs="com.intellij">
<dependencySupport coordinate="androidx.compose.runtime:runtime" kind="java" displayName="Jetpack Compose"/>
- <annotator language="kotlin" implementationClass="com.android.tools.compose.ComposeColorAnnotator"/>
<annotator language="kotlin" implementationClass="com.android.tools.compose.code.ComposeStateReadAnnotator"/>
<additionalTextAttributes scheme="Default" file="colorschemes/ComposableHighlighterExtensionColorSchemeDefault.xml"/>
@@ -156,6 +155,8 @@
<codeInsight.lineMarkerProvider language="kotlin"
implementationClass="com.android.tools.compose.code.ComposeLineMarkerProviderDescriptor" />
+ <codeInsight.lineMarkerProvider language="kotlin"
+ implementationClass="com.android.tools.compose.ComposeColorLineMarkerProviderDescriptor" />
<usageGroupingRuleProvider implementation="com.android.tools.compose.ComposeUsageGroupingRuleProvider" />
</extensions>
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposableElementRefactoringElementListenerProvider.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposableElementRefactoringElementListenerProvider.kt
index 22f6154..44291a9 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposableElementRefactoringElementListenerProvider.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposableElementRefactoringElementListenerProvider.kt
@@ -24,17 +24,17 @@
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction
-/**
- * Renames KtFile if a @Composable function with the same name was renamed.
- */
+/** Renames KtFile if a @Composable function with the same name was renamed. */
class ComposableElementAutomaticRenamerFactory : AutomaticRenamerFactory {
override fun isApplicable(element: PsiElement): Boolean {
- if (element.getModuleSystem()?.usesCompose != true ||
+ if (
+ element.getModuleSystem()?.usesCompose != true ||
element !is KtNamedFunction ||
element.parent !is KtFile ||
!element.isComposableFunction()
- ) return false
+ )
+ return false
val virtualFile = element.containingKtFile.virtualFile
return virtualFile?.nameWithoutExtension == element.name
@@ -46,7 +46,11 @@
override fun setEnabled(enabled: Boolean) {}
- override fun createRenamer(element: PsiElement, newName: String?, usages: MutableCollection<UsageInfo>?): AutomaticRenamer {
+ override fun createRenamer(
+ element: PsiElement,
+ newName: String?,
+ usages: MutableCollection<UsageInfo>?
+ ): AutomaticRenamer {
return object : AutomaticRenamer() {
init {
val file = element.containingFile
@@ -56,9 +60,10 @@
override fun getDialogTitle() = ComposeBundle.message("rename.file")
- override fun getDialogDescription() = ComposeBundle.message("rename.files.with.following.names")
+ override fun getDialogDescription() =
+ ComposeBundle.message("rename.files.with.following.names")
override fun entityName() = ComposeBundle.message("file.name")
}
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposableFunctionExtractableAnalyser.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposableFunctionExtractableAnalyser.kt
index e67ae09..4ff7460 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposableFunctionExtractableAnalyser.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposableFunctionExtractableAnalyser.kt
@@ -31,20 +31,24 @@
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
/**
- * Adds [COMPOSABLE_FQ_NAME] annotation to a function when it's extracted from a function annotated with [COMPOSABLE_FQ_NAME]
- * or Composable context.
+ * Adds [COMPOSABLE_FQ_NAME] annotation to a function when it's extracted from a function annotated
+ * with [COMPOSABLE_FQ_NAME] or Composable context.
*/
class ComposableFunctionExtractableAnalyser : AdditionalExtractableAnalyser {
/**
- * Returns @Composable annotation of type of given KtLambdaArgument if there is any otherwise returns null.
+ * Returns @Composable annotation of type of given KtLambdaArgument if there is any otherwise
+ * returns null.
*
- * Example: fun myFunction(context: @Composable () -> Unit)
- * If given [KtLambdaArgument] corresponds to context parameter function returns [AnnotationDescriptor] for @Composable.
+ * Example: fun myFunction(context: @Composable () -> Unit) If given [KtLambdaArgument]
+ * corresponds to context parameter function returns [AnnotationDescriptor] for @Composable.
*/
- private fun KtLambdaArgument.getComposableAnnotation(bindingContext: BindingContext): AnnotationDescriptor? {
+ private fun KtLambdaArgument.getComposableAnnotation(
+ bindingContext: BindingContext
+ ): AnnotationDescriptor? {
val callExpression = parent as KtCallExpression
val resolvedCall = callExpression.getResolvedCall(bindingContext)
- val argument = (resolvedCall?.getArgumentMapping(this) as? ArgumentMatch)?.valueParameter ?: return null
+ val argument =
+ (resolvedCall?.getArgumentMapping(this) as? ArgumentMatch)?.valueParameter ?: return null
return argument.type.annotations.findAnnotation(ComposeFqNames.Composable)
}
@@ -56,13 +60,15 @@
val bindingContext = descriptor.extractionData.bindingContext ?: return descriptor
val sourceFunction = descriptor.extractionData.targetSibling
if (sourceFunction is KtAnnotated) {
- val composableAnnotation = sourceFunction.findAnnotation(ComposeFqNames.Composable)?.resolveToDescriptorIfAny()
+ val composableAnnotation =
+ sourceFunction.findAnnotation(ComposeFqNames.Composable)?.resolveToDescriptorIfAny()
if (composableAnnotation != null) {
return descriptor.copy(annotations = descriptor.annotations + composableAnnotation)
}
}
val outsideLambda = descriptor.extractionData.commonParent.parentOfType<KtLambdaArgument>(true)
- val composableAnnotation = outsideLambda?.getComposableAnnotation(bindingContext) ?: return descriptor
+ val composableAnnotation =
+ outsideLambda?.getComposableAnnotation(bindingContext) ?: return descriptor
return descriptor.copy(annotations = descriptor.annotations + composableAnnotation)
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposableHighlighterExtension.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposableHighlighterExtension.kt
index c46a77a..e685b8e 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposableHighlighterExtension.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposableHighlighterExtension.kt
@@ -29,35 +29,56 @@
/**
* Used to apply styles for calls to @Composable functions.
*
- * JetBrains documentation recommends doing highlighting such as this using [com.intellij.lang.annotation.Annotator] (guidance available at
- * https://plugins.jetbrains.com/docs/intellij/syntax-highlighting-and-error-highlighting.html#annotator). But it turns out that the Kotlin
- * plugin is running its syntax highlighting using a different mechanism which can run in parallel with annotators. That doesn't matter for
- * annotators that don't conflict with the built-in highlighting, but in the case of Compose we are overriding some of the standard function
- * colors, and so we need to ensure that Compose highlighting takes precedence.
+ * JetBrains documentation recommends doing highlighting such as this using
+ * [com.intellij.lang.annotation.Annotator] (guidance available at
+ * https://plugins.jetbrains.com/docs/intellij/syntax-highlighting-and-error-highlighting.html#annotator).
+ * But it turns out that the Kotlin plugin is running its syntax highlighting using a different
+ * mechanism which can run in parallel with annotators. That doesn't matter for annotators that
+ * don't conflict with the built-in highlighting, but in the case of Compose we are overriding some
+ * of the standard function colors, and so we need to ensure that Compose highlighting takes
+ * precedence.
*
- * Luckily, the Kotlin plugin provides its own extension mechanism, which is implemented here with [HighlighterExtension]. When this code
- * returns Composable function highlighting for a given method call, it will always be used instead of the default Kotlin highlighting.
+ * Luckily, the Kotlin plugin provides its own extension mechanism, which is implemented here with
+ * [HighlighterExtension]. When this code returns Composable function highlighting for a given
+ * method call, it will always be used instead of the default Kotlin highlighting.
*/
class ComposableHighlighterExtension : KotlinHighlightingVisitorExtension() {
companion object {
const val COMPOSABLE_CALL_TEXT_ATTRIBUTES_NAME = "ComposableCallTextAttributes"
val COMPOSABLE_CALL_TEXT_ATTRIBUTES_KEY: TextAttributesKey =
- TextAttributesKey.createTextAttributesKey(COMPOSABLE_CALL_TEXT_ATTRIBUTES_NAME, DefaultLanguageHighlighterColors.FUNCTION_CALL)
- val COMPOSABLE_CALL_TEXT_TYPE: HighlightInfoType = HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, COMPOSABLE_CALL_TEXT_ATTRIBUTES_KEY, false)
+ TextAttributesKey.createTextAttributesKey(
+ COMPOSABLE_CALL_TEXT_ATTRIBUTES_NAME,
+ DefaultLanguageHighlighterColors.FUNCTION_CALL
+ )
+ val COMPOSABLE_CALL_TEXT_TYPE: HighlightInfoType =
+ HighlightInfoType.HighlightInfoTypeImpl(
+ HighlightInfoType.SYMBOL_TYPE_SEVERITY,
+ COMPOSABLE_CALL_TEXT_ATTRIBUTES_KEY,
+ false
+ )
}
- override fun highlightDeclaration(elementToHighlight: PsiElement, descriptor: DeclarationDescriptor): HighlightInfoType? = null
+ override fun highlightDeclaration(
+ elementToHighlight: PsiElement,
+ descriptor: DeclarationDescriptor
+ ): HighlightInfoType? = null
- override fun highlightCall(elementToHighlight: PsiElement, resolvedCall: ResolvedCall<*>): HighlightInfoType? {
+ override fun highlightCall(
+ elementToHighlight: PsiElement,
+ resolvedCall: ResolvedCall<*>
+ ): HighlightInfoType? {
if (!resolvedCall.isComposableInvocation()) return null
// For composable invocations, highlight if either:
// 1. compose is enabled for the current module, or
// 2. the file is part of a library's source code.
- return if (isComposeEnabled(elementToHighlight) || isInLibrarySource(elementToHighlight)) COMPOSABLE_CALL_TEXT_TYPE else null
+ return if (isComposeEnabled(elementToHighlight) || isInLibrarySource(elementToHighlight))
+ COMPOSABLE_CALL_TEXT_TYPE
+ else null
}
private fun isInLibrarySource(element: PsiElement) =
element.containingFile.virtualFile != null &&
- ProjectFileIndex.getInstance(element.project).isInLibrarySource(element.containingFile.virtualFile)
+ ProjectFileIndex.getInstance(element.project)
+ .isInLibrarySource(element.containingFile.virtualFile)
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposableIconProvider.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposableIconProvider.kt
index 51aa796..7a5b12a 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposableIconProvider.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposableIconProvider.kt
@@ -19,16 +19,17 @@
import com.intellij.psi.PsiElement
import com.intellij.ui.RowIcon
import icons.StudioIcons.Compose.Editor.COMPOSABLE_FUNCTION
+import javax.swing.Icon
import org.jetbrains.kotlin.idea.KotlinIconProvider
import org.jetbrains.kotlin.idea.util.hasMatchingExpected
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
-import javax.swing.Icon
/**
- * Returns Composable function icon for [KtFunction] elements that are composable, or null otherwise to allow fallback to any
- * other providers. This may be used in various places across the IDE; one example is in the "Add Import" menu.
+ * Returns Composable function icon for [KtFunction] elements that are composable, or null otherwise
+ * to allow fallback to any other providers. This may be used in various places across the IDE; one
+ * example is in the "Add Import" menu.
*/
class ComposableIconProvider : KotlinIconProvider() {
@@ -48,8 +49,9 @@
return declaration.hasActualModifier() && declaration.hasMatchingExpected()
}
- private fun createRowIcon(baseIcon: Icon, visibilityIcon: Icon): RowIcon = RowIcon(2).apply {
- setIcon(baseIcon, /* layer = */ 0)
- setIcon(visibilityIcon, /* layer = */ 1)
- }
+ private fun createRowIcon(baseIcon: Icon, visibilityIcon: Icon): RowIcon =
+ RowIcon(2).apply {
+ setIcon(baseIcon, /* layer = */ 0)
+ setIcon(visibilityIcon, /* layer = */ 1)
+ }
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposableItemPresentationProvider.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposableItemPresentationProvider.kt
index 1434e6a..9ecc30c 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposableItemPresentationProvider.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposableItemPresentationProvider.kt
@@ -22,10 +22,11 @@
import org.jetbrains.kotlin.psi.KtFunction
/**
- * [ItemPresentationProvider] which overrides default behavior for displaying a method in a menu (eg, in "Add Imports").
+ * [ItemPresentationProvider] which overrides default behavior for displaying a method in a menu
+ * (eg, in "Add Imports").
*
- * Composable methods will be displayed as "@Composable FunctionName". Other functions will maintain default display text (ie,
- * "functionName(arg1, arg2)".
+ * Composable methods will be displayed as "@Composable FunctionName". Other functions will maintain
+ * default display text (ie, "functionName(arg1, arg2)".
*/
class ComposableItemPresentationProvider : ItemPresentationProvider<KtFunction> {
override fun getPresentation(function: KtFunction): ItemPresentation? {
@@ -37,10 +38,11 @@
}
/**
- * Presentation for composable functions. Based on the default [KotlinFunctionPresentation], with modifications to how the function name
- * is presented.
- * */
- private class ComposableFunctionPresentation(private val function: KtFunction) : KotlinFunctionPresentation(function) {
+ * Presentation for composable functions. Based on the default [KotlinFunctionPresentation], with
+ * modifications to how the function name is presented.
+ */
+ private class ComposableFunctionPresentation(private val function: KtFunction) :
+ KotlinFunctionPresentation(function) {
override fun getPresentableText(): String {
return buildString {
append("@Composable")
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposeAutoDocumentation.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposeAutoDocumentation.kt
index 4a671e8..0dd9f54 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposeAutoDocumentation.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposeAutoDocumentation.kt
@@ -33,32 +33,33 @@
import com.intellij.openapi.startup.StartupActivity
import com.intellij.psi.PsiElement
import com.intellij.util.Alarm
+import java.beans.PropertyChangeListener
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.containingClass
-import java.beans.PropertyChangeListener
-/**
- * Automatically shows quick documentation for Compose functions during code completion
- */
+/** Automatically shows quick documentation for Compose functions during code completion */
class ComposeAutoDocumentation(private val project: Project) {
private var documentationOpenedByCompose = false
private val lookupListener = PropertyChangeListener { evt ->
- if (LookupManager.PROP_ACTIVE_LOOKUP == evt.propertyName &&
- evt.newValue is Lookup) {
+ if (LookupManager.PROP_ACTIVE_LOOKUP == evt.propertyName && evt.newValue is Lookup) {
val lookup = evt.newValue as Lookup
- val moduleSystem = FileDocumentManager.getInstance().getFile(lookup.editor.document)
- ?.let { ModuleUtilCore.findModuleForFile(it, lookup.project) }
- ?.getModuleSystem()
+ val moduleSystem =
+ FileDocumentManager.getInstance()
+ .getFile(lookup.editor.document)
+ ?.let { ModuleUtilCore.findModuleForFile(it, lookup.project) }
+ ?.getModuleSystem()
if (moduleSystem?.usesCompose == true) {
- lookup.addLookupListener(object : LookupListener {
- override fun currentItemChanged(event: LookupEvent) {
- showJavaDoc(lookup)
+ lookup.addLookupListener(
+ object : LookupListener {
+ override fun currentItemChanged(event: LookupEvent) {
+ showJavaDoc(lookup)
+ }
}
- })
+ )
}
}
}
@@ -75,15 +76,17 @@
companion object {
@JvmStatic
- fun getInstance(project: Project): ComposeAutoDocumentation = project.getService(ComposeAutoDocumentation::class.java)
+ fun getInstance(project: Project): ComposeAutoDocumentation =
+ project.getService(ComposeAutoDocumentation::class.java)
@VisibleForTesting
internal fun PsiElement?.shouldShowDocumentation(): Boolean =
when {
this == null -> false
isComposableFunction() -> true
- this is KtNamedFunction -> receiverTypeReference?.text == "androidx.compose.ui.Modifier" ||
- containingClass()?.fqName?.asString() == "androidx.compose.ui.Modifier"
+ this is KtNamedFunction ->
+ receiverTypeReference?.text == "androidx.compose.ui.Modifier" ||
+ containingClass()?.fqName?.asString() == "androidx.compose.ui.Modifier"
else -> false
}
}
@@ -99,18 +102,26 @@
return
}
- // If we open doc when lookup is not visible, doc will have wrong parent window (editor window instead of lookup).
+ // If we open doc when lookup is not visible, doc will have wrong parent window (editor window
+ // instead of lookup).
if ((lookup as? LookupImpl)?.isVisible != true) {
- Alarm().addRequest({ showJavaDoc(lookup) }, CodeInsightSettings.getInstance().JAVADOC_INFO_DELAY)
+ Alarm()
+ .addRequest({ showJavaDoc(lookup) }, CodeInsightSettings.getInstance().JAVADOC_INFO_DELAY)
return
}
val docManager = DocumentationManager.getInstance(project)
- val psiElement = lookup.currentItem?.let { it.psiElement ?: (it.`object` as? DeclarationLookupObject)?.psiElement }
+ val psiElement =
+ lookup.currentItem?.let {
+ it.psiElement ?: (it.`object` as? DeclarationLookupObject)?.psiElement
+ }
if (!psiElement.shouldShowDocumentation()) {
- // Close documentation for not composable function if it was opened by [AndroidComposeAutoDocumentation].
- // Case docManager.docInfoHint?.isFocused == true: user clicked on doc window and after that clicked on lookup and selected another
- // element. Due to bug docManager.docInfoHint?.isFocused == true even after clicking on lookup element, in that case if we close
+ // Close documentation for not composable function if it was opened by
+ // [AndroidComposeAutoDocumentation].
+ // Case docManager.docInfoHint?.isFocused == true: user clicked on doc window and after that
+ // clicked on lookup and selected another
+ // element. Due to bug docManager.docInfoHint?.isFocused == true even after clicking on lookup
+ // element, in that case if we close
// docManager.docInfoHint, lookup will be closed as well.
if (documentationOpenedByCompose && docManager.docInfoHint?.isFocused == false) {
docManager.docInfoHint?.cancel()
@@ -120,18 +131,20 @@
}
// It's composable function and documentation already opened
- if (docManager.docInfoHint != null) return // will auto-update
+ if (docManager.docInfoHint != null) return // will auto-update
val currentItem = lookup.currentItem
- if (currentItem != null && currentItem.isValid && CompletionService.getCompletionService().currentCompletion != null) {
+ if (
+ currentItem != null &&
+ currentItem.isValid &&
+ CompletionService.getCompletionService().currentCompletion != null
+ ) {
try {
docManager.showJavaDocInfo(lookup.editor, lookup.psiFile, false) {
documentationOpenedByCompose = false
}
documentationOpenedByCompose = true
- }
- catch (ignored: IndexNotReadyException) {
- }
+ } catch (ignored: IndexNotReadyException) {}
}
}
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposeBundle.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposeBundle.kt
index 97720aa..44f41f7 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposeBundle.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposeBundle.kt
@@ -16,10 +16,10 @@
package com.android.tools.compose
import com.intellij.AbstractBundle
-import org.jetbrains.annotations.PropertyKey
import java.lang.ref.Reference
import java.lang.ref.SoftReference
import java.util.ResourceBundle
+import org.jetbrains.annotations.PropertyKey
private const val BUNDLE_NAME = "messages.ComposeBundle"
@@ -37,8 +37,11 @@
}
@JvmStatic
- fun message(@PropertyKey(resourceBundle = BUNDLE_NAME) key: String, vararg params: Any?): String {
+ fun message(
+ @PropertyKey(resourceBundle = BUNDLE_NAME) key: String,
+ vararg params: Any?
+ ): String {
return AbstractBundle.message(getBundle(), key, *params)
}
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposeColorAnnotator.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposeColorAnnotator.kt
deleted file mode 100644
index 4a36664..0000000
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposeColorAnnotator.kt
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2019 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.compose
-
-import com.android.ide.common.rendering.api.ResourceReference
-import com.android.tools.adtui.LightCalloutPopup
-import com.android.tools.idea.projectsystem.getModuleSystem
-import com.android.tools.idea.ui.resourcechooser.colorpicker2.ColorPickerBuilder
-import com.android.tools.idea.ui.resourcechooser.colorpicker2.ColorPickerListener
-import com.android.tools.idea.ui.resourcechooser.colorpicker2.internal.MaterialColorPaletteProvider
-import com.android.tools.idea.ui.resourcechooser.colorpicker2.internal.MaterialGraphicalColorPipetteProvider
-import com.intellij.lang.annotation.AnnotationHolder
-import com.intellij.lang.annotation.Annotator
-import com.intellij.lang.annotation.HighlightSeverity
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.actionSystem.CommonDataKeys
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.command.WriteCommandAction
-import com.intellij.openapi.editor.markup.GutterIconRenderer
-import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiTypes
-import com.intellij.util.ui.ColorIcon
-import org.jetbrains.annotations.VisibleForTesting
-import org.jetbrains.kotlin.analysis.api.analyze
-import org.jetbrains.kotlin.analysis.api.components.KtConstantEvaluationMode
-import org.jetbrains.kotlin.idea.base.plugin.isK2Plugin
-import org.jetbrains.kotlin.idea.inspections.AbstractRangeInspection.Companion.constantValueOrNull
-import org.jetbrains.kotlin.psi.KtCallElement
-import org.jetbrains.kotlin.psi.KtCallExpression
-import org.jetbrains.kotlin.psi.KtExpression
-import org.jetbrains.kotlin.psi.KtPsiFactory
-import org.jetbrains.kotlin.psi.KtValueArgument
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.UExpression
-import org.jetbrains.uast.UastCallKind
-import org.jetbrains.uast.toUElement
-import java.awt.Color
-import java.awt.MouseInfo
-import java.util.Locale
-import javax.swing.Icon
-
-private const val ICON_SIZE = 8
-
-/**
- * [Annotator] to place color gutter icons for compose color declarations.
- * It does this by looking at the parameters of the Color() method and so does not work is the parameters are references.
- * It also does not work predefined colors. eg. Color.White
- */
-class ComposeColorAnnotator : Annotator {
-
- override fun annotate(element: PsiElement, holder: AnnotationHolder) {
- when {
- element.getModuleSystem()?.usesCompose != true -> return
- element is KtCallElement -> {
- val uElement = element.toUElement(UCallExpression::class.java) ?: return
- val returnType = uElement.returnType ?: return
- if (uElement.kind != UastCallKind.METHOD_CALL || returnType != PsiTypes.longType() || COLOR_METHOD != uElement.methodName) {
- return
- }
-
- // Resolve the MethodCall expression after the faster checks
- val fqName = uElement.resolve()?.containingClass?.qualifiedName ?: return
- if (fqName == COMPOSE_COLOR_CLASS) {
- val color = getColor(uElement) ?: return
- holder.newSilentAnnotation(HighlightSeverity.INFORMATION)
- .gutterIconRenderer(ColorIconRenderer(uElement, color))
- .create()
- }
- }
- }
- }
-
- private fun getColor(uElement: UElement): Color? {
- val callElement = uElement as? UCallExpression ?: return null
- val arguments = (uElement.sourcePsi as? KtCallExpression)?.valueArguments ?: return null
- return when (getConstructorType(callElement.valueArguments)) {
- ComposeColorConstructor.INT -> getColorInt(arguments)
- ComposeColorConstructor.LONG -> getColorLong(arguments)
- ComposeColorConstructor.INT_X3 -> getColorIntX3(arguments)
- ComposeColorConstructor.INT_X4 -> getColorIntX4(arguments)
- ComposeColorConstructor.FLOAT_X3 -> getColorFloatX3(arguments)
- ComposeColorConstructor.FLOAT_X4 -> getColorFloatX4(arguments)
- // TODO: Provide the color preview for ComposeColorConstructor.FLOAT_X4_COLORSPACE constructor.
- ComposeColorConstructor.FLOAT_X4_COLORSPACE -> null
- else -> null
- }
- }
-}
-
-/**
- * Simplified version of [AndroidAnnotatorUtil.ColorRenderer] that does not work on [ResourceReference] but still displays the same color
- * picker.
- * TODO(lukeegan): Implement for ComposeColorConstructor.FLOAT_X4_COLORSPACE Color parameter
- */
-data class ColorIconRenderer(val element: UCallExpression, val color: Color) : GutterIconRenderer() {
-
- override fun getIcon(): Icon {
- return ColorIcon(ICON_SIZE, color)
- }
-
- override fun getClickAction(): AnAction? {
- val project = element.sourcePsi?.project ?: return null
- val setColorTask: (Color) -> Unit = getSetColorTask() ?: return null
-
- val pickerListener = ColorPickerListener { color, _ ->
- ApplicationManager.getApplication().invokeLater({
- WriteCommandAction.runWriteCommandAction(project, "Change Color", null, { setColorTask.invoke(color) })
- }, project.disposed)
- }
- return object : AnAction() {
- override fun actionPerformed(e: AnActionEvent) {
- val editor = e.getData(CommonDataKeys.EDITOR)
- if (editor != null) {
- val dialog = LightCalloutPopup()
- val colorPicker = ColorPickerBuilder()
- .setOriginalColor(color)
- .addSaturationBrightnessComponent()
- .addColorAdjustPanel(MaterialGraphicalColorPipetteProvider())
- .addColorValuePanel().withFocus()
- .addSeparator()
- .addCustomComponent(MaterialColorPaletteProvider)
- .addColorPickerListener(pickerListener)
- .focusWhenDisplay(true)
- .setFocusCycleRoot(true)
- .build()
- dialog.show(colorPicker, null, MouseInfo.getPointerInfo().location)
- }
- }
- }
- }
-
- @VisibleForTesting
- fun getSetColorTask(): ((Color) -> Unit)? {
- val ktCallExpression = element.sourcePsi as? KtCallExpression ?: return null
- val constructorType = getConstructorType(element.valueArguments) ?: return null
- // No matter what the original format is, we make the format become one of:
- // - (0xAARRGGBB)
- // - (color = 0xAARRGGBB)
- // or
- // - ([0..255], [0..255], [0..255], [0.255])
- // - (red = [0..255], green = [0..255], blue = [0..255], alpha = [0.255])
- // or
- // - ([0x00..0xFF], [0x00..0xFF], [0x00..0xFF], [0x00..0xFF])
- // - (red = [0x00..0xFF], green =[0x00..0xFF], blue = [0x00..0xFF], alpha = [0x00..0xFF])
- // or
- // - ([0.0f..1.0f], [0.0f..1.0f], [0.0f..1.0f], [0.0f..1.0f])
- // - (red = [0.0f..1.0f], green = [0.0f..1.0f], blue = [0.0f..1.0f], alpha = [0.0f..1.0f])
- // , depends on the original value type and numeral system.
- return when(constructorType) {
- ComposeColorConstructor.INT,
- ComposeColorConstructor.LONG -> { color: Color ->
- val valueArgumentList = ktCallExpression.valueArgumentList
- if (valueArgumentList != null) {
- val needsArgumentName = valueArgumentList.arguments.any { it.getArgumentName() != null }
- val hexString = "0x${String.format("%08X", color.rgb)}"
- val argumentText = if (needsArgumentName) "(color = $hexString)" else "($hexString)"
- valueArgumentList.replace(KtPsiFactory(ktCallExpression.project).createCallArguments(argumentText))
- }
- }
- ComposeColorConstructor.INT_X3,
- ComposeColorConstructor.INT_X4 -> { color: Color ->
- val valueArgumentList = ktCallExpression.valueArgumentList
- if (valueArgumentList != null) {
- val needsArgumentName = valueArgumentList.arguments.any { it.getArgumentName() != null }
- val hasHexFormat = valueArgumentList.arguments.any { it.getArgumentExpression()?.text?.startsWith("0x") ?: false }
- val red = if (hasHexFormat) color.red.toHexString() else color.red.toString()
- val green = if (hasHexFormat) color.green.toHexString() else color.green.toString()
- val blue = if (hasHexFormat) color.blue.toHexString() else color.blue.toString()
- val alpha = if (hasHexFormat) color.alpha.toHexString() else color.alpha.toString()
-
- val argumentText =
- if (needsArgumentName) "(red = $red, green = $green, blue = $blue, alpha = $alpha)" else "($red, $green, $blue, $alpha)"
- valueArgumentList.replace(KtPsiFactory(ktCallExpression.project).createCallArguments(argumentText))
- }
- }
- ComposeColorConstructor.FLOAT_X3,
- ComposeColorConstructor.FLOAT_X4 -> { color: Color ->
- val valueArgumentList = ktCallExpression.valueArgumentList
- if (valueArgumentList != null) {
- val needsArgumentName = valueArgumentList.arguments.any { it.getArgumentName() != null }
- val red = (color.red / 255f).toRoundString()
- val green = (color.green / 255f).toRoundString()
- val blue = (color.blue / 255f).toRoundString()
- val alpha = (color.alpha / 255f).toRoundString()
-
- val argumentText =
- if (needsArgumentName) "(red = ${red}f, green = ${green}f, blue = ${blue}f, alpha = ${alpha}f)"
- else "(${red}f, ${green}f, ${blue}f, ${alpha}f)"
- valueArgumentList.replace(KtPsiFactory(ktCallExpression.project).createCallArguments(argumentText))
- }
- }
- ComposeColorConstructor.FLOAT_X4_COLORSPACE -> null // TODO: support ComposeColorConstructor.FLOAT_X4_COLORSPACE in the future.
- }
- }
-}
-
-private const val COLOR_METHOD = "Color"
-private const val COMPOSE_COLOR_CLASS = "androidx.compose.ui.graphics.ColorKt"
-
-private const val ARG_NAME_RED = "red"
-private const val ARG_NAME_GREEN = "green"
-private const val ARG_NAME_BLUE = "blue"
-private const val ARG_NAME_ALPHA = "alpha"
-
-private val ARGS_RGB = listOf(ARG_NAME_RED, ARG_NAME_GREEN, ARG_NAME_BLUE)
-private val ARGS_RGBA = listOf(ARG_NAME_RED, ARG_NAME_GREEN, ARG_NAME_BLUE, ARG_NAME_ALPHA)
-
-enum class ComposeColorConstructor {
- INT, LONG, INT_X3, INT_X4, FLOAT_X3, FLOAT_X4, FLOAT_X4_COLORSPACE
-}
-
-private fun getColorInt(arguments: List<KtValueArgument>): Color? {
- val colorValue = arguments.first().getArgumentExpression()?.evaluateToConstantOrNull<Int>() ?: return null
- return Color(colorValue, true)
-}
-
-private fun getColorLong(arguments: List<KtValueArgument>): Color? {
- val colorValue = arguments.first().getArgumentExpression()?.evaluateToConstantOrNull<Long>() ?: return null
- return Color(colorValue.toInt(), true)
-}
-
-private fun getColorIntX3(arguments: List<KtValueArgument>): Color? {
- val rgbValues = getNamedValues<Int>(ARGS_RGB, arguments) ?: return null
- return intColorMapToColor(rgbValues)
-}
-
-private fun getColorIntX4(arguments: List<KtValueArgument>): Color? {
- val rgbaValues = getNamedValues<Int>(ARGS_RGBA, arguments) ?: return null
- return intColorMapToColor(rgbaValues)
-}
-
-private fun getColorFloatX3(arguments: List<KtValueArgument>): Color? {
- val rgbValues = getNamedValues<Float>(ARGS_RGB, arguments) ?: return null
- return floatColorMapToColor(rgbValues)
-}
-
-private fun getColorFloatX4(arguments: List<KtValueArgument>): Color? {
- val rgbaValues = getNamedValues<Float>(ARGS_RGBA, arguments) ?: return null
- return floatColorMapToColor(rgbaValues)
-}
-
-/**
- * This function return the name-value pair for the request arguments names by extracting the given ktValueArguments.
- */
-private inline fun <reified T> getNamedValues(requestArgumentNames: List<String>, ktValueArgument: List<KtValueArgument>): Map<String, T>? {
- val namedValues = mutableMapOf<String, T>()
-
- val unnamedValue = mutableListOf<T>()
- for (argument in ktValueArgument) {
- val (name, value) = getArgumentNameValuePair<T>(argument) ?: return null
- if (name != null) {
- namedValues[name] = value
- }
- else {
- unnamedValue.add(value)
- }
- }
-
- val unnamedArgument = requestArgumentNames.filterNot { it in namedValues.keys }.toList()
- if (unnamedArgument.size != unnamedValue.size) {
- // The number of argument values doesn't match the given KtValueArgument.
- return null
- }
-
- for (index in unnamedArgument.indices) {
- // Fill the unnamed argument value from KtValueArgument.
- namedValues[unnamedArgument[index]] = unnamedValue[index]
- }
- if (namedValues.keys != requestArgumentNames.toSet()) {
- // Has the redundant or missed argument(s).
- return null
- }
- return namedValues
-}
-
-private inline fun <reified T> getArgumentNameValuePair(valueArgument: KtValueArgument): Pair<String?, T>? {
- val name = valueArgument.getArgumentName()?.asName?.asString()
- val value = valueArgument.getArgumentExpression()?.evaluateToConstantOrNull<T>() ?: return null
- return name to value
-}
-
-private inline fun <reified T> KtExpression.evaluateToConstantOrNull(): T? {
- return if (isK2Plugin()) {
- analyze(this) {
- evaluate(KtConstantEvaluationMode.CONSTANT_EXPRESSION_EVALUATION)?.value as? T ?: return null
- }
- } else {
- constantValueOrNull()?.value as? T ?: return null
- }
-}
-
-private fun Int.toHexString(): String = "0x${(Integer.toHexString(this)).uppercase(Locale.getDefault())}"
-
-// Note: toFloat() then toString() is for removing the tail zero(s).
-private fun Float.toRoundString(decimals: Int = 3): String = "%.${decimals}f".format(this).toFloat().toString()
-
-private typealias IntColorMap = Map<String, Int>
-private fun intColorMapToColor(intColorMap: IntColorMap): Color? {
- val red = intColorMap[ARG_NAME_RED] ?: return null
- val green = intColorMap[ARG_NAME_GREEN] ?: return null
- val blue = intColorMap[ARG_NAME_BLUE] ?: return null
- val alpha = intColorMap[ARG_NAME_ALPHA]
- return if (alpha == null) Color(red, green, blue) else Color(red, green, blue, alpha)
-}
-
-private typealias FloatColorMap = Map<String, Float>
-private fun floatColorMapToColor(floatColorMap: FloatColorMap): Color? {
- val red = floatColorMap[ARG_NAME_RED] ?: return null
- val green = floatColorMap[ARG_NAME_GREEN] ?: return null
- val blue = floatColorMap[ARG_NAME_BLUE] ?: return null
- val alpha = floatColorMap[ARG_NAME_ALPHA]
- return if (alpha == null) Color(red, green, blue) else Color(red, green, blue, alpha)
-}
-
-private fun getConstructorType(arguments: List<UExpression>): ComposeColorConstructor? {
- val paramType = arguments.firstOrNull()?.getExpressionType() ?: return null
- return when (arguments.size) {
- 1 -> if (PsiTypes.intType() == paramType) ComposeColorConstructor.INT else ComposeColorConstructor.LONG
- 3 -> if (PsiTypes.intType() == paramType) ComposeColorConstructor.INT_X3 else ComposeColorConstructor.FLOAT_X3
- 4 -> if (PsiTypes.intType() == paramType) ComposeColorConstructor.INT_X4 else ComposeColorConstructor.FLOAT_X4
- 5 -> ComposeColorConstructor.FLOAT_X4_COLORSPACE
- else -> null
- }
-}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposeColorLineMarkerProviderDescriptor.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposeColorLineMarkerProviderDescriptor.kt
new file mode 100644
index 0000000..deb3303
--- /dev/null
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposeColorLineMarkerProviderDescriptor.kt
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2019 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.compose
+
+import com.android.ide.common.rendering.api.ResourceReference
+import com.android.tools.adtui.LightCalloutPopup
+import com.android.tools.idea.ui.resourcechooser.colorpicker2.ColorPickerBuilder
+import com.android.tools.idea.ui.resourcechooser.colorpicker2.ColorPickerListener
+import com.android.tools.idea.ui.resourcechooser.colorpicker2.internal.MaterialColorPaletteProvider
+import com.android.tools.idea.ui.resourcechooser.colorpicker2.internal.MaterialGraphicalColorPipetteProvider
+import com.intellij.codeInsight.daemon.GutterIconNavigationHandler
+import com.intellij.codeInsight.daemon.LineMarkerInfo
+import com.intellij.codeInsight.daemon.LineMarkerProviderDescriptor
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.command.WriteCommandAction
+import com.intellij.openapi.editor.markup.GutterIconRenderer
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiTypes
+import com.intellij.psi.util.elementType
+import com.intellij.util.ui.ColorIcon
+import java.awt.Color
+import java.awt.MouseInfo
+import java.awt.event.MouseEvent
+import java.util.Locale
+import org.jetbrains.annotations.VisibleForTesting
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.analysis.api.components.KtConstantEvaluationMode
+import org.jetbrains.kotlin.idea.base.plugin.isK2Plugin
+import org.jetbrains.kotlin.idea.editor.fixers.range
+import org.jetbrains.kotlin.idea.inspections.AbstractRangeInspection.Companion.constantValueOrNull
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.KtCallExpression
+import org.jetbrains.kotlin.psi.KtExpression
+import org.jetbrains.kotlin.psi.KtPsiFactory
+import org.jetbrains.kotlin.psi.KtValueArgument
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UastCallKind
+import org.jetbrains.uast.toUElement
+
+private const val ICON_SIZE = 8
+
+class ComposeColorLineMarkerProviderDescriptor : LineMarkerProviderDescriptor() {
+ override fun getName() = ComposeBundle.message("compose.color.picker.name")
+
+ override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? {
+ if (element.elementType != KtTokens.IDENTIFIER || !isComposeEnabled(element)) return null
+
+ val uElement =
+ (element.parent.parent as? KtCallExpression)?.toUElement(UCallExpression::class.java)
+ ?: return null
+ if (!uElement.isColorCall()) return null
+
+ val color = getColor(uElement) ?: return null
+ val iconRenderer = ColorIconRenderer(uElement, color)
+ return LineMarkerInfo(
+ element,
+ element.range,
+ iconRenderer.icon,
+ { ComposeBundle.message("compose.color.picker.tooltip") },
+ iconRenderer,
+ GutterIconRenderer.Alignment.RIGHT,
+ { ComposeBundle.message("compose.color.picker.tooltip") },
+ )
+ }
+
+ private fun UCallExpression.isColorCall() =
+ kind == UastCallKind.METHOD_CALL &&
+ returnType == PsiTypes.longType() &&
+ COLOR_METHOD == methodName &&
+ // Resolve the MethodCall expression after the faster checks
+ resolve()?.containingClass?.qualifiedName == COMPOSE_COLOR_CLASS
+
+ private fun getColor(uElement: UCallExpression): Color? {
+ val arguments = (uElement.sourcePsi as? KtCallExpression)?.valueArguments ?: return null
+ return when (getConstructorType(uElement.valueArguments)) {
+ ComposeColorConstructor.INT -> getColorInt(arguments)
+ ComposeColorConstructor.LONG -> getColorLong(arguments)
+ ComposeColorConstructor.INT_X3 -> getColorIntX3(arguments)
+ ComposeColorConstructor.INT_X4 -> getColorIntX4(arguments)
+ ComposeColorConstructor.FLOAT_X3 -> getColorFloatX3(arguments)
+ ComposeColorConstructor.FLOAT_X4 -> getColorFloatX4(arguments)
+ // TODO: Provide the color preview for ComposeColorConstructor.FLOAT_X4_COLORSPACE
+ // constructor.
+ ComposeColorConstructor.FLOAT_X4_COLORSPACE -> null
+ else -> null
+ }
+ }
+}
+
+/**
+ * Simplified version of [AndroidAnnotatorUtil.ColorRenderer] that does not work on
+ * [ResourceReference] but still displays the same color picker.
+ *
+ * TODO(lukeegan): Implement for ComposeColorConstructor.FLOAT_X4_COLORSPACE Color parameter
+ */
+data class ColorIconRenderer(val element: UCallExpression, val color: Color) :
+ GutterIconNavigationHandler<PsiElement> {
+
+ val icon = ColorIcon(ICON_SIZE, color)
+
+ override fun navigate(e: MouseEvent?, elt: PsiElement?) {
+ val project = element.sourcePsi?.project ?: return
+ val setColorTask: (Color) -> Unit = getSetColorTask() ?: return
+
+ val pickerListener = ColorPickerListener { color, _ ->
+ ApplicationManager.getApplication()
+ .invokeLater(
+ {
+ WriteCommandAction.runWriteCommandAction(
+ project,
+ "Change Color",
+ null,
+ { setColorTask.invoke(color) }
+ )
+ },
+ project.disposed
+ )
+ }
+
+ val dialog = LightCalloutPopup()
+ val colorPicker =
+ ColorPickerBuilder()
+ .setOriginalColor(color)
+ .addSaturationBrightnessComponent()
+ .addColorAdjustPanel(MaterialGraphicalColorPipetteProvider())
+ .addColorValuePanel()
+ .withFocus()
+ .addSeparator()
+ .addCustomComponent(MaterialColorPaletteProvider)
+ .addColorPickerListener(pickerListener)
+ .focusWhenDisplay(true)
+ .setFocusCycleRoot(true)
+ .build()
+ dialog.show(colorPicker, null, MouseInfo.getPointerInfo().location)
+ }
+
+ @VisibleForTesting
+ fun getSetColorTask(): ((Color) -> Unit)? {
+ val ktCallExpression = element.sourcePsi as? KtCallExpression ?: return null
+ val constructorType = getConstructorType(element.valueArguments) ?: return null
+ // No matter what the original format is, we make the format become one of:
+ // - (0xAARRGGBB)
+ // - (color = 0xAARRGGBB)
+ // or
+ // - ([0..255], [0..255], [0..255], [0.255])
+ // - (red = [0..255], green = [0..255], blue = [0..255], alpha = [0.255])
+ // or
+ // - ([0x00..0xFF], [0x00..0xFF], [0x00..0xFF], [0x00..0xFF])
+ // - (red = [0x00..0xFF], green =[0x00..0xFF], blue = [0x00..0xFF], alpha = [0x00..0xFF])
+ // or
+ // - ([0.0f..1.0f], [0.0f..1.0f], [0.0f..1.0f], [0.0f..1.0f])
+ // - (red = [0.0f..1.0f], green = [0.0f..1.0f], blue = [0.0f..1.0f], alpha = [0.0f..1.0f])
+ // , depends on the original value type and numeral system.
+ return when (constructorType) {
+ ComposeColorConstructor.INT,
+ ComposeColorConstructor.LONG -> { color: Color ->
+ val valueArgumentList = ktCallExpression.valueArgumentList
+ if (valueArgumentList != null) {
+ val needsArgumentName = valueArgumentList.arguments.any { it.getArgumentName() != null }
+ val hexString = "0x${String.format("%08X", color.rgb)}"
+ val argumentText = if (needsArgumentName) "(color = $hexString)" else "($hexString)"
+ valueArgumentList.replace(
+ KtPsiFactory(ktCallExpression.project).createCallArguments(argumentText)
+ )
+ }
+ }
+ ComposeColorConstructor.INT_X3,
+ ComposeColorConstructor.INT_X4 -> { color: Color ->
+ val valueArgumentList = ktCallExpression.valueArgumentList
+ if (valueArgumentList != null) {
+ val needsArgumentName = valueArgumentList.arguments.any { it.getArgumentName() != null }
+ val hasHexFormat =
+ valueArgumentList.arguments.any {
+ it.getArgumentExpression()?.text?.startsWith("0x") ?: false
+ }
+ val red = if (hasHexFormat) color.red.toHexString() else color.red.toString()
+ val green = if (hasHexFormat) color.green.toHexString() else color.green.toString()
+ val blue = if (hasHexFormat) color.blue.toHexString() else color.blue.toString()
+ val alpha = if (hasHexFormat) color.alpha.toHexString() else color.alpha.toString()
+
+ val argumentText =
+ if (needsArgumentName) "(red = $red, green = $green, blue = $blue, alpha = $alpha)"
+ else "($red, $green, $blue, $alpha)"
+ valueArgumentList.replace(
+ KtPsiFactory(ktCallExpression.project).createCallArguments(argumentText)
+ )
+ }
+ }
+ ComposeColorConstructor.FLOAT_X3,
+ ComposeColorConstructor.FLOAT_X4 -> { color: Color ->
+ val valueArgumentList = ktCallExpression.valueArgumentList
+ if (valueArgumentList != null) {
+ val needsArgumentName = valueArgumentList.arguments.any { it.getArgumentName() != null }
+ val red = (color.red / 255f).toRoundString()
+ val green = (color.green / 255f).toRoundString()
+ val blue = (color.blue / 255f).toRoundString()
+ val alpha = (color.alpha / 255f).toRoundString()
+
+ val argumentText =
+ if (needsArgumentName)
+ "(red = ${red}f, green = ${green}f, blue = ${blue}f, alpha = ${alpha}f)"
+ else "(${red}f, ${green}f, ${blue}f, ${alpha}f)"
+ valueArgumentList.replace(
+ KtPsiFactory(ktCallExpression.project).createCallArguments(argumentText)
+ )
+ }
+ }
+ ComposeColorConstructor.FLOAT_X4_COLORSPACE ->
+ null // TODO: support ComposeColorConstructor.FLOAT_X4_COLORSPACE in the future.
+ }
+ }
+}
+
+private const val COLOR_METHOD = "Color"
+private const val COMPOSE_COLOR_CLASS = "androidx.compose.ui.graphics.ColorKt"
+
+private const val ARG_NAME_RED = "red"
+private const val ARG_NAME_GREEN = "green"
+private const val ARG_NAME_BLUE = "blue"
+private const val ARG_NAME_ALPHA = "alpha"
+
+private val ARGS_RGB = listOf(ARG_NAME_RED, ARG_NAME_GREEN, ARG_NAME_BLUE)
+private val ARGS_RGBA = listOf(ARG_NAME_RED, ARG_NAME_GREEN, ARG_NAME_BLUE, ARG_NAME_ALPHA)
+
+enum class ComposeColorConstructor {
+ INT,
+ LONG,
+ INT_X3,
+ INT_X4,
+ FLOAT_X3,
+ FLOAT_X4,
+ FLOAT_X4_COLORSPACE
+}
+
+private fun getColorInt(arguments: List<KtValueArgument>): Color? {
+ val colorValue =
+ arguments.first().getArgumentExpression()?.evaluateToConstantOrNull<Int>() ?: return null
+ return Color(colorValue, true)
+}
+
+private fun getColorLong(arguments: List<KtValueArgument>): Color? {
+ val colorValue =
+ arguments.first().getArgumentExpression()?.evaluateToConstantOrNull<Long>() ?: return null
+ return Color(colorValue.toInt(), true)
+}
+
+private fun getColorIntX3(arguments: List<KtValueArgument>): Color? {
+ val rgbValues = getNamedValues<Int>(ARGS_RGB, arguments) ?: return null
+ return intColorMapToColor(rgbValues)
+}
+
+private fun getColorIntX4(arguments: List<KtValueArgument>): Color? {
+ val rgbaValues = getNamedValues<Int>(ARGS_RGBA, arguments) ?: return null
+ return intColorMapToColor(rgbaValues)
+}
+
+private fun getColorFloatX3(arguments: List<KtValueArgument>): Color? {
+ val rgbValues = getNamedValues<Float>(ARGS_RGB, arguments) ?: return null
+ return floatColorMapToColor(rgbValues)
+}
+
+private fun getColorFloatX4(arguments: List<KtValueArgument>): Color? {
+ val rgbaValues = getNamedValues<Float>(ARGS_RGBA, arguments) ?: return null
+ return floatColorMapToColor(rgbaValues)
+}
+
+/**
+ * This function return the name-value pair for the request arguments names by extracting the given
+ * ktValueArguments.
+ */
+private inline fun <reified T> getNamedValues(
+ requestArgumentNames: List<String>,
+ ktValueArgument: List<KtValueArgument>
+): Map<String, T>? {
+ val namedValues = mutableMapOf<String, T>()
+
+ val unnamedValue = mutableListOf<T>()
+ for (argument in ktValueArgument) {
+ val (name, value) = getArgumentNameValuePair<T>(argument) ?: return null
+ if (name != null) {
+ namedValues[name] = value
+ } else {
+ unnamedValue.add(value)
+ }
+ }
+
+ val unnamedArgument = requestArgumentNames.filterNot { it in namedValues.keys }.toList()
+ if (unnamedArgument.size != unnamedValue.size) {
+ // The number of argument values doesn't match the given KtValueArgument.
+ return null
+ }
+
+ for (index in unnamedArgument.indices) {
+ // Fill the unnamed argument value from KtValueArgument.
+ namedValues[unnamedArgument[index]] = unnamedValue[index]
+ }
+ if (namedValues.keys != requestArgumentNames.toSet()) {
+ // Has the redundant or missed argument(s).
+ return null
+ }
+ return namedValues
+}
+
+private inline fun <reified T> getArgumentNameValuePair(
+ valueArgument: KtValueArgument
+): Pair<String?, T>? {
+ val name = valueArgument.getArgumentName()?.asName?.asString()
+ val value = valueArgument.getArgumentExpression()?.evaluateToConstantOrNull<T>() ?: return null
+ return name to value
+}
+
+private inline fun <reified T> KtExpression.evaluateToConstantOrNull(): T? {
+ return if (isK2Plugin()) {
+ analyze(this) {
+ evaluate(KtConstantEvaluationMode.CONSTANT_EXPRESSION_EVALUATION)?.value as? T ?: return null
+ }
+ } else {
+ constantValueOrNull()?.value as? T ?: return null
+ }
+}
+
+private fun Int.toHexString(): String =
+ "0x${(Integer.toHexString(this)).uppercase(Locale.getDefault())}"
+
+// Note: toFloat() then toString() is for removing the tail zero(s).
+private fun Float.toRoundString(decimals: Int = 3): String =
+ "%.${decimals}f".format(this).toFloat().toString()
+
+private typealias IntColorMap = Map<String, Int>
+
+private fun intColorMapToColor(intColorMap: IntColorMap): Color? {
+ val red = intColorMap[ARG_NAME_RED] ?: return null
+ val green = intColorMap[ARG_NAME_GREEN] ?: return null
+ val blue = intColorMap[ARG_NAME_BLUE] ?: return null
+ val alpha = intColorMap[ARG_NAME_ALPHA]
+ return if (alpha == null) Color(red, green, blue) else Color(red, green, blue, alpha)
+}
+
+private typealias FloatColorMap = Map<String, Float>
+
+private fun floatColorMapToColor(floatColorMap: FloatColorMap): Color? {
+ val red = floatColorMap[ARG_NAME_RED] ?: return null
+ val green = floatColorMap[ARG_NAME_GREEN] ?: return null
+ val blue = floatColorMap[ARG_NAME_BLUE] ?: return null
+ val alpha = floatColorMap[ARG_NAME_ALPHA]
+ return if (alpha == null) Color(red, green, blue) else Color(red, green, blue, alpha)
+}
+
+private fun getConstructorType(arguments: List<UExpression>): ComposeColorConstructor? {
+ val paramType = arguments.firstOrNull()?.getExpressionType() ?: return null
+ return when (arguments.size) {
+ 1 ->
+ if (PsiTypes.intType() == paramType) ComposeColorConstructor.INT
+ else ComposeColorConstructor.LONG
+ 3 ->
+ if (PsiTypes.intType() == paramType) ComposeColorConstructor.INT_X3
+ else ComposeColorConstructor.FLOAT_X3
+ 4 ->
+ if (PsiTypes.intType() == paramType) ComposeColorConstructor.INT_X4
+ else ComposeColorConstructor.FLOAT_X4
+ 5 -> ComposeColorConstructor.FLOAT_X4_COLORSPACE
+ else -> null
+ }
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposeColorSettingsPage.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposeColorSettingsPage.kt
index 1da9388..97e939f 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposeColorSettingsPage.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposeColorSettingsPage.kt
@@ -23,62 +23,66 @@
import com.intellij.openapi.options.colors.AttributesDescriptor
import com.intellij.openapi.options.colors.ColorDescriptor
import com.intellij.openapi.options.colors.ColorSettingsPage
+import javax.swing.Icon
import org.jetbrains.kotlin.idea.highlighter.KotlinColorSettingsPage
import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors
-import javax.swing.Icon
// This class is used by AndroidStudio to allow the user to change the style of Compose attributes.
class ComposeColorSettingsPage : ColorSettingsPage {
- override fun getHighlighter(): SyntaxHighlighter {
- return KotlinColorSettingsPage().highlighter
- }
+ override fun getHighlighter(): SyntaxHighlighter {
+ return KotlinColorSettingsPage().highlighter
+ }
- override fun getAdditionalHighlightingTagToDescriptorMap(): MutableMap<String,
- TextAttributesKey> {
- val attributes = HashMap<String, TextAttributesKey>()
- attributes[ComposableHighlighterExtension.COMPOSABLE_CALL_TEXT_ATTRIBUTES_NAME] =
- ComposableHighlighterExtension.COMPOSABLE_CALL_TEXT_ATTRIBUTES_KEY
- if (StudioFlags.COMPOSE_STATE_READ_HIGHLIGHTING_ENABLED.get()) {
- attributes[ComposeStateReadAnnotator.COMPOSE_STATE_READ_TEXT_ATTRIBUTES_NAME] =
- ComposeStateReadAnnotator.COMPOSE_STATE_READ_TEXT_ATTRIBUTES_KEY
- }
- attributes["ANNOTATION"] = KotlinHighlightingColors.ANNOTATION
- attributes["KEYWORD"] = KotlinHighlightingColors.KEYWORD
- attributes["FUNCTION_DECLARATION"] = KotlinHighlightingColors.FUNCTION_DECLARATION
- attributes["FUNCTION_PARAMETER"] = KotlinHighlightingColors.PARAMETER
- return attributes
+ override fun getAdditionalHighlightingTagToDescriptorMap():
+ MutableMap<String, TextAttributesKey> {
+ val attributes = HashMap<String, TextAttributesKey>()
+ attributes[ComposableHighlighterExtension.COMPOSABLE_CALL_TEXT_ATTRIBUTES_NAME] =
+ ComposableHighlighterExtension.COMPOSABLE_CALL_TEXT_ATTRIBUTES_KEY
+ if (StudioFlags.COMPOSE_STATE_READ_HIGHLIGHTING_ENABLED.get()) {
+ attributes[ComposeStateReadAnnotator.COMPOSE_STATE_READ_TEXT_ATTRIBUTES_NAME] =
+ ComposeStateReadAnnotator.COMPOSE_STATE_READ_TEXT_ATTRIBUTES_KEY
}
+ attributes["ANNOTATION"] = KotlinHighlightingColors.ANNOTATION
+ attributes["KEYWORD"] = KotlinHighlightingColors.KEYWORD
+ attributes["FUNCTION_DECLARATION"] = KotlinHighlightingColors.FUNCTION_DECLARATION
+ attributes["FUNCTION_PARAMETER"] = KotlinHighlightingColors.PARAMETER
+ return attributes
+ }
- override fun getIcon(): Icon? {
- return null
- }
+ override fun getIcon(): Icon? {
+ return null
+ }
- override fun getAttributeDescriptors(): Array<AttributesDescriptor> {
- // TODO: this needs to be localized.
- return arrayOf(AttributesDescriptor("Calls to @Compose functions",
- ComposableHighlighterExtension.COMPOSABLE_CALL_TEXT_ATTRIBUTES_KEY))
- }
+ override fun getAttributeDescriptors(): Array<AttributesDescriptor> {
+ // TODO: this needs to be localized.
+ return arrayOf(
+ AttributesDescriptor(
+ "Calls to @Compose functions",
+ ComposableHighlighterExtension.COMPOSABLE_CALL_TEXT_ATTRIBUTES_KEY
+ )
+ )
+ }
- override fun getColorDescriptors(): Array<ColorDescriptor> {
- return emptyArray()
- }
+ override fun getColorDescriptors(): Array<ColorDescriptor> {
+ return emptyArray()
+ }
- override fun getDisplayName(): String {
- // TODO: this needs to be localized.
- return "Compose"
- }
+ override fun getDisplayName(): String {
+ // TODO: this needs to be localized.
+ return "Compose"
+ }
- override fun getDemoText(): String {
- return "<ANNOTATION>@Composable</ANNOTATION>\n" +
- "<KEYWORD>fun</KEYWORD> <FUNCTION_DECLARATION>Text</FUNCTION_DECLARATION>(" +
- "<FUNCTION_PARAMETER>text</FUNCTION_PARAMETER>: <FUNCTION_PARAMETER>String" +
- "</FUNCTION_PARAMETER>)\n" +
- "}\n" +
- "\n" +
- "<ANNOTATION>@Composable</ANNOTATION>\n" +
- "<KEYWORD>fun</KEYWORD> <FUNCTION_DECLARATION>Greeting</FUNCTION_DECLARATION>() {\n" +
- " <ComposableCallTextAttributes>Text</ComposableCallTextAttributes>(" +
- "<FUNCTION_PARAMETER>\"Hello\"</FUNCTION_PARAMETER>)\n" +
- "}"
- }
-}
\ No newline at end of file
+ override fun getDemoText(): String {
+ return "<ANNOTATION>@Composable</ANNOTATION>\n" +
+ "<KEYWORD>fun</KEYWORD> <FUNCTION_DECLARATION>Text</FUNCTION_DECLARATION>(" +
+ "<FUNCTION_PARAMETER>text</FUNCTION_PARAMETER>: <FUNCTION_PARAMETER>String" +
+ "</FUNCTION_PARAMETER>)\n" +
+ "}\n" +
+ "\n" +
+ "<ANNOTATION>@Composable</ANNOTATION>\n" +
+ "<KEYWORD>fun</KEYWORD> <FUNCTION_DECLARATION>Greeting</FUNCTION_DECLARATION>() {\n" +
+ " <ComposableCallTextAttributes>Text</ComposableCallTextAttributes>(" +
+ "<FUNCTION_PARAMETER>\"Hello\"</FUNCTION_PARAMETER>)\n" +
+ "}"
+ }
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposeFoldingBuilder.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposeFoldingBuilder.kt
index 9e30289..ebdbb32 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposeFoldingBuilder.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposeFoldingBuilder.kt
@@ -28,36 +28,37 @@
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
-/**
- * Adds a folding region for a Modifier chain longer than two.
- */
+/** Adds a folding region for a Modifier chain longer than two. */
class ComposeFoldingBuilder : CustomFoldingBuilder() {
- override fun buildLanguageFoldRegions(descriptors: MutableList<FoldingDescriptor>, root: PsiElement, document: Document, quick: Boolean) {
+ override fun buildLanguageFoldRegions(
+ descriptors: MutableList<FoldingDescriptor>,
+ root: PsiElement,
+ document: Document,
+ quick: Boolean
+ ) {
if (root !is KtFile || DumbService.isDumb(root.project) || !isComposeEnabled(root)) {
return
}
- val composableFunctions = root.getChildrenOfType<KtNamedFunction>().filter { it.isComposableFunction() }
+ val composableFunctions =
+ root.getChildrenOfType<KtNamedFunction>().filter { it.isComposableFunction() }
for (function in composableFunctions) {
- val modifiersChains = PsiTreeUtil.findChildrenOfType(function, KtDotQualifiedExpression::class.java).filter {
- it.parent !is KtDotQualifiedExpression &&
- isModifierChainLongerThanTwo(it)
- }
+ val modifiersChains =
+ PsiTreeUtil.findChildrenOfType(function, KtDotQualifiedExpression::class.java).filter {
+ it.parent !is KtDotQualifiedExpression && isModifierChainLongerThanTwo(it)
+ }
for (modifierChain in modifiersChains) {
descriptors.add(FoldingDescriptor(modifierChain.node, modifierChain.node.textRange))
}
}
-
}
- /**
- * For Modifier.adjust().adjust() -> Modifier.(...)
- */
+ /** For Modifier.adjust().adjust() -> Modifier.(...) */
override fun getLanguagePlaceholderText(node: ASTNode, range: TextRange): String {
return node.text.substringBefore(".").trim() + ".(...)"
}
override fun isRegionCollapsedByDefault(node: ASTNode): Boolean = false
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposeKDocLinkResolutionService.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposeKDocLinkResolutionService.kt
index 2b5062f..8f1a1d7 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposeKDocLinkResolutionService.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposeKDocLinkResolutionService.kt
@@ -18,23 +18,25 @@
import com.android.tools.idea.flags.StudioFlags
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.idea.base.projectStructure.scope.KotlinSourceFilterScope
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.kdoc.IdeKDocLinkResolutionService
import org.jetbrains.kotlin.idea.kdoc.KDocLinkResolutionService
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.stubindex.KotlinClassShortNameIndex
import org.jetbrains.kotlin.idea.stubindex.KotlinFunctionShortNameIndex
-import org.jetbrains.kotlin.idea.base.projectStructure.scope.KotlinSourceFilterScope
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
/**
- * Resolves links to functions and classes inside KDoc that are not included to the project (as byte code).
+ * Resolves links to functions and classes inside KDoc that are not included to the project (as byte
+ * code).
*
- * It's a copy of [org.jetbrains.kotlin.idea.kdoc.IdeKDocLinkResolutionService], but with a larger search scope:
- * GlobalSearchScope.everythingScope(project) instead of GlobalSearchScope.projectScope(project).
- * Source code is already in the index, it attached in [AndroidModuleDependenciesSetup#setUpLibraryDependency]
+ * It's a copy of [org.jetbrains.kotlin.idea.kdoc.IdeKDocLinkResolutionService], but with a larger
+ * search scope: GlobalSearchScope.everythingScope(project) instead of
+ * GlobalSearchScope.projectScope(project). Source code is already in the index, it attached in
+ * [AndroidModuleDependenciesSetup#setUpLibraryDependency]
*/
class ComposeKDocLinkResolutionService : KDocLinkResolutionService {
override fun resolveKDocLink(
@@ -44,11 +46,14 @@
qualifiedName: List<String>
): Collection<DeclarationDescriptor> {
val project = resolutionFacade.project
- val descriptors = IdeKDocLinkResolutionService(project).resolveKDocLink(context, fromDescriptor, resolutionFacade, qualifiedName)
+ val descriptors =
+ IdeKDocLinkResolutionService(project)
+ .resolveKDocLink(context, fromDescriptor, resolutionFacade, qualifiedName)
if (!StudioFlags.SAMPLES_SUPPORT_ENABLED.get()) return descriptors
- val scope = KotlinSourceFilterScope.librarySources(GlobalSearchScope.everythingScope(project), project)
+ val scope =
+ KotlinSourceFilterScope.librarySources(GlobalSearchScope.everythingScope(project), project)
val shortName = qualifiedName.lastOrNull() ?: return emptyList()
val targetFqName = FqName.fromSegments(qualifiedName)
@@ -56,12 +61,14 @@
val functions = KotlinFunctionShortNameIndex[shortName, project, scope].asSequence()
val classes = KotlinClassShortNameIndex[shortName, project, scope].asSequence()
- val additionalDescriptors = (functions + classes)
- .filter { it.fqName == targetFqName }
- .map { it.unsafeResolveToDescriptor(BodyResolveMode.PARTIAL) } // TODO Filter out not visible due dependencies config descriptors
- .toList()
- if (additionalDescriptors.isNotEmpty())
- return additionalDescriptors + descriptors
+ val additionalDescriptors =
+ (functions + classes)
+ .filter { it.fqName == targetFqName }
+ .map {
+ it.unsafeResolveToDescriptor(BodyResolveMode.PARTIAL)
+ } // TODO Filter out not visible due dependencies config descriptors
+ .toList()
+ if (additionalDescriptors.isNotEmpty()) return additionalDescriptors + descriptors
return descriptors
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposeOverrideImplementsAnnotationsFilter.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposeOverrideImplementsAnnotationsFilter.kt
index 0a386d0..8ed64d0 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposeOverrideImplementsAnnotationsFilter.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposeOverrideImplementsAnnotationsFilter.kt
@@ -19,10 +19,9 @@
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.psi.KtFile
-/**
- * Ensure that any @Composable annotations are retained when implementing an interface.
- */
+/** Ensure that any @Composable annotations are retained when implementing an interface. */
class ComposeOverrideImplementsAnnotationsFilter : OverrideImplementsAnnotationsFilter {
override fun getAnnotations(file: PsiFile): Array<String> =
- if (file is KtFile && isComposeEnabled(file)) arrayOf(COMPOSABLE_ANNOTATION_FQ_NAME) else arrayOf()
+ if (file is KtFile && isComposeEnabled(file)) arrayOf(COMPOSABLE_ANNOTATION_FQ_NAME)
+ else arrayOf()
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposePluginIrGenerationExtension.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposePluginIrGenerationExtension.kt
index 9d65fe5..42660fc 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposePluginIrGenerationExtension.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposePluginIrGenerationExtension.kt
@@ -31,16 +31,19 @@
class ComposePluginIrGenerationExtension : IrGenerationExtension {
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
try {
- ComposeIrGenerationExtension(reportsDestination = null,
- metricsDestination = null,
- generateFunctionKeyMetaClasses = true,
- intrinsicRememberEnabled = false)
- .generate(moduleFragment, pluginContext)
- } catch (e : ProcessCanceledException) {
- // From ProcessCanceledException javadoc: "Usually, this exception should not be caught, swallowed, logged, or handled in any way.
+ ComposeIrGenerationExtension(
+ reportsDestination = null,
+ metricsDestination = null,
+ generateFunctionKeyMetaClasses = true,
+ intrinsicRememberEnabled = false
+ )
+ .generate(moduleFragment, pluginContext)
+ } catch (e: ProcessCanceledException) {
+ // From ProcessCanceledException javadoc: "Usually, this exception should not be caught,
+ // swallowed, logged, or handled in any way.
// Instead, it should be rethrown so that the infrastructure can handle it correctly."
- throw e;
- } catch (versionError : IncompatibleComposeRuntimeVersionException) {
+ throw e
+ } catch (versionError: IncompatibleComposeRuntimeVersionException) {
// We only rethrow version incompatibility when we are trying to CodeGen for Live Edit.
for (s in versionError.stackTrace) {
if (s.className.startsWith(liveEditPackageName)) {
@@ -48,8 +51,8 @@
}
}
versionError.printStackTrace()
- } catch (t : Throwable) {
+ } catch (t: Throwable) {
t.printStackTrace()
}
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposePluginUtils.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposePluginUtils.kt
index e035f0f..c35c802 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposePluginUtils.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposePluginUtils.kt
@@ -68,8 +68,9 @@
return false
}
-internal fun KotlinType.isClassOrExtendsClass(classFqName:String): Boolean {
- return fqName?.asString() == classFqName || supertypes().any { it.fqName?.asString() == classFqName }
+internal fun KotlinType.isClassOrExtendsClass(classFqName: String): Boolean {
+ return fqName?.asString() == classFqName ||
+ supertypes().any { it.fqName?.asString() == classFqName }
}
internal fun KtValueArgument.matchingParamTypeFqName(callee: KtNamedFunction): FqName? {
@@ -77,43 +78,46 @@
val argumentName = getArgumentName()!!.asName.asString()
val matchingParam = callee.valueParameters.find { it.name == argumentName } ?: return null
matchingParam.returnTypeFqName()
- }
- else {
+ } else {
val argumentIndex = (parent as KtValueArgumentList).arguments.indexOf(this)
val paramAtIndex = callee.valueParameters.getOrNull(argumentIndex) ?: return null
paramAtIndex.returnTypeFqName()
}
}
-internal fun KtDeclaration.returnTypeFqName(): FqName? = if (isK2Plugin()) {
- if (this !is KtCallableDeclaration) null
- else analyze(this) { asFqName(this@returnTypeFqName.getReturnKtType()) }
-}
-else {
- this.type()?.fqName
-}
+internal fun KtDeclaration.returnTypeFqName(): FqName? =
+ if (isK2Plugin()) {
+ if (this !is KtCallableDeclaration) null
+ else analyze(this) { asFqName(this@returnTypeFqName.getReturnKtType()) }
+ } else {
+ this.type()?.fqName
+ }
@OptIn(KtAllowAnalysisOnEdt::class)
-internal fun KtElement.callReturnTypeFqName() = if (isK2Plugin()) {
- allowAnalysisOnEdt {
- analyze(this) {
- val callReturnType = this@callReturnTypeFqName.resolveCall()?.singleFunctionCallOrNull()?.symbol?.returnType
- callReturnType?.let { asFqName(it) }
+internal fun KtElement.callReturnTypeFqName() =
+ if (isK2Plugin()) {
+ allowAnalysisOnEdt {
+ analyze(this) {
+ val callReturnType =
+ this@callReturnTypeFqName.resolveCall()?.singleFunctionCallOrNull()?.symbol?.returnType
+ callReturnType?.let { asFqName(it) }
+ }
}
+ } else {
+ resolveToCall(BodyResolveMode.PARTIAL)?.resultingDescriptor?.returnType?.fqName
}
-}
-else {
- resolveToCall(BodyResolveMode.PARTIAL)?.resultingDescriptor?.returnType?.fqName
-}
-// TODO(274630452): When the upstream APIs are available, implement it based on `fullyExpandedType` and `KtTypeRenderer`.
-internal fun KtAnalysisSession.asFqName(type: KtType) = type.expandedClassSymbol?.classIdIfNonLocal?.asSingleFqName()
+// TODO(274630452): When the upstream APIs are available, implement it based on `fullyExpandedType`
+// and `KtTypeRenderer`.
+internal fun KtAnalysisSession.asFqName(type: KtType) =
+ type.expandedClassSymbol?.classIdIfNonLocal?.asSingleFqName()
-internal fun KtFunction.hasComposableAnnotation() = if (isK2Plugin()) {
- hasAnnotation(ComposeClassIds.Composable)
-} else {
- descriptor?.hasComposableAnnotation() == true
-}
+internal fun KtFunction.hasComposableAnnotation() =
+ if (isK2Plugin()) {
+ hasAnnotation(ComposeClassIds.Composable)
+ } else {
+ descriptor?.hasComposableAnnotation() == true
+ }
internal fun KtAnalysisSession.isComposableInvocation(callableSymbol: KtCallableSymbol): Boolean {
fun hasComposableAnnotation(annotated: KtAnnotated?) =
@@ -122,10 +126,11 @@
val type = callableSymbol.returnType
if (hasComposableAnnotation(type)) return true
val functionSymbol = callableSymbol as? KtFunctionSymbol
- if (functionSymbol != null &&
+ if (
+ functionSymbol != null &&
functionSymbol.isOperator &&
functionSymbol.name == OperatorNameConventions.INVOKE
- ) {
+ ) {
functionSymbol.receiverType?.let { receiverType ->
if (hasComposableAnnotation(receiverType)) return true
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposeSettings.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposeSettings.kt
index 539c95d..d818088 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposeSettings.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposeSettings.kt
@@ -24,7 +24,8 @@
@Service
@State(name = "ComposeSettings", storages = [Storage("composeSettings.xml")])
-class ComposeSettings : SimplePersistentStateComponent<ComposeSettingsState>(ComposeSettingsState()) {
+class ComposeSettings :
+ SimplePersistentStateComponent<ComposeSettingsState>(ComposeSettingsState()) {
companion object {
fun getInstance() = ApplicationManager.getApplication().getService(ComposeSettings::class.java)
}
@@ -32,4 +33,4 @@
class ComposeSettingsState : BaseState() {
var isComposeInsertHandlerEnabled by property(true)
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposeSuppressor.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposeSuppressor.kt
index 97274ec..aedb571 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposeSuppressor.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposeSuppressor.kt
@@ -21,19 +21,16 @@
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.lexer.KtTokens
-/**
- * Suppress inspection that require composable function names to start with a lower case letter.
- */
+/** Suppress inspection that require composable function names to start with a lower case letter. */
class ComposeSuppressor : InspectionSuppressor {
override fun isSuppressedFor(element: PsiElement, toolId: String): Boolean {
return toolId == "FunctionName" &&
- element.language == KotlinLanguage.INSTANCE &&
- element.node.elementType == KtTokens.IDENTIFIER &&
- element.parent.isComposableFunction()
+ element.language == KotlinLanguage.INSTANCE &&
+ element.node.elementType == KtTokens.IDENTIFIER &&
+ element.parent.isComposableFunction()
}
override fun getSuppressActions(element: PsiElement?, toolId: String): Array<SuppressQuickFix> {
return SuppressQuickFix.EMPTY_ARRAY
}
}
-
diff --git a/compose-ide-plugin/src/com/android/tools/compose/ComposeUsageGroupingRuleProvider.kt b/compose-ide-plugin/src/com/android/tools/compose/ComposeUsageGroupingRuleProvider.kt
index cb16f26..dab4e43 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/ComposeUsageGroupingRuleProvider.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/ComposeUsageGroupingRuleProvider.kt
@@ -31,10 +31,9 @@
import com.intellij.usages.rules.UsageGroupingRule
import com.intellij.usages.rules.UsageGroupingRuleEx
import com.intellij.usages.rules.UsageGroupingRuleProviderEx
-import org.jetbrains.kotlin.name.ClassId
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.psi.KtFunction
import javax.swing.Icon
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.psi.KtFunction
private val PREVIEW_CLASS_ID = ClassId.fromString("androidx/compose/ui/tooling/preview/Preview")
@@ -51,14 +50,17 @@
private fun KtFunction.hasPreviewAnnotation() = hasAnnotation(PREVIEW_CLASS_ID)
/** Returns whether any of the [UsageTarget]s represent @Composable functions. */
-private fun Array<out UsageTarget>.containsComposable(): Boolean = asSequence()
- .filterIsInstance<PsiElementUsageTarget>()
- .mapNotNull { it.element as? KtFunction }
- .any { it.hasComposableAnnotation() }
+private fun Array<out UsageTarget>.containsComposable(): Boolean =
+ asSequence()
+ .filterIsInstance<PsiElementUsageTarget>()
+ .mapNotNull { it.element as? KtFunction }
+ .any { it.hasComposableAnnotation() }
class ComposeUsageGroupingRuleProvider : UsageGroupingRuleProviderEx {
- override fun getActiveRules(project: Project): Array<UsageGroupingRule> = arrayOf(PreviewUsageGroupingRule)
- override fun getAllRules(project: Project, usageView: UsageView?): Array<UsageGroupingRule> = arrayOf(PreviewUsageGroupingRule)
+ override fun getActiveRules(project: Project): Array<UsageGroupingRule> =
+ arrayOf(PreviewUsageGroupingRule)
+ override fun getAllRules(project: Project, usageView: UsageView?): Array<UsageGroupingRule> =
+ arrayOf(PreviewUsageGroupingRule)
}
private object PreviewUsageGroupingRule : UsageGroupingRuleEx {
@@ -70,11 +72,14 @@
override fun getTitle() = ComposeBundle.message("separate.preview.usages")
override fun getParentGroupsFor(usage: Usage, targets: Array<out UsageTarget>): List<UsageGroup> {
- // This block exists to facilitate end-to-end testing for ShowUsages. When ShowUsages is invoked, irrespective of whether anything
- // related to compose is happening, this code will execute for each Usage seen, logging something to idea.log we can look for in our
+ // This block exists to facilitate end-to-end testing for ShowUsages. When ShowUsages is
+ // invoked, irrespective of whether anything
+ // related to compose is happening, this code will execute for each Usage seen, logging
+ // something to idea.log we can look for in our
// end-to-end test, provided we turn on debugging for this class.
if (java.lang.Boolean.getBoolean("studio.run.under.integration.test")) {
- Logger.getInstance(ComposeUsageGroupingRuleProvider::class.java).debug("Saw usage: ${usage.presentation.plainText.trim()}")
+ Logger.getInstance(ComposeUsageGroupingRuleProvider::class.java)
+ .debug("Saw usage: ${usage.presentation.plainText.trim()}")
}
val element = (usage as? PsiElementUsage)?.element ?: return emptyList()
@@ -96,5 +101,6 @@
internal object ProductionUsageGroup : UsageGroupBase(0) {
override fun getIcon(): Icon? = null
- override fun getPresentableGroupText() = ComposeBundle.message("usage.group.in.nonpreview.function")
+ override fun getPresentableGroupText() =
+ ComposeBundle.message("usage.group.in.nonpreview.function")
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/aa/code/ComposableFunctionRendering.kt b/compose-ide-plugin/src/com/android/tools/compose/aa/code/ComposableFunctionRendering.kt
index 26f7819..6f21244 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/aa/code/ComposableFunctionRendering.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/aa/code/ComposableFunctionRendering.kt
@@ -29,48 +29,70 @@
/**
* Generates [ComposableFunctionRenderParts] for a given Composable function.
*
- * Since Composable functions tend to have numerous optional parameters, those are omitted from the rendered parameters and replaced with an
- * ellipsis ("..."). Additional modifications are made to ensure that a lambda can be added in cases where the Composable function requires
- * another Composable as its final argument.
+ * Since Composable functions tend to have numerous optional parameters, those are omitted from the
+ * rendered parameters and replaced with an ellipsis ("..."). Additional modifications are made to
+ * ensure that a lambda can be added in cases where the Composable function requires another
+ * Composable as its final argument.
*/
-fun KtAnalysisSession.getComposableFunctionRenderParts(functionSymbol: KtFunctionLikeSymbol): ComposableFunctionRenderParts {
+fun KtAnalysisSession.getComposableFunctionRenderParts(
+ functionSymbol: KtFunctionLikeSymbol
+): ComposableFunctionRenderParts {
val allParameters = functionSymbol.valueParameters
val requiredParameters = allParameters.filter { isRequired(it) }
- val lastParamIsComposable = allParameters.lastOrNull()?.let { isComposableFunctionParameter(it) } == true
+ val lastParamIsComposable =
+ allParameters.lastOrNull()?.let { isComposableFunctionParameter(it) } == true
val inParens = if (lastParamIsComposable) requiredParameters.dropLast(1) else requiredParameters
- val tail = if (lastParamIsComposable) LambdaSignatureTemplates.DEFAULT_LAMBDA_PRESENTATION else null
+ val tail =
+ if (lastParamIsComposable) LambdaSignatureTemplates.DEFAULT_LAMBDA_PRESENTATION else null
- val stringAfterValueParameters = when {
- requiredParameters.size < allParameters.size -> if (inParens.isNotEmpty()) ", ...)" else "...)"
- inParens.isEmpty() && lastParamIsComposable -> null // Don't render an empty pair of parentheses if we're rendering a lambda afterwards.
- else -> ")"
- } ?: return ComposableFunctionRenderParts(null, tail)
+ val stringAfterValueParameters =
+ when {
+ requiredParameters.size < allParameters.size ->
+ if (inParens.isNotEmpty()) ", ...)" else "...)"
+ inParens.isEmpty() && lastParamIsComposable ->
+ null // Don't render an empty pair of parentheses if we're rendering a lambda afterwards.
+ else -> ")"
+ }
+ ?: return ComposableFunctionRenderParts(null, tail)
val parameters = renderValueParameters(inParens, stringAfterValueParameters)
return ComposableFunctionRenderParts(parameters, tail)
}
-fun KtAnalysisSession.renderValueParameters(valueParamsInParen: List<KtValueParameterSymbol>, closingString: String) = buildString {
+fun KtAnalysisSession.renderValueParameters(
+ valueParamsInParen: List<KtValueParameterSymbol>,
+ closingString: String
+) = buildString {
append("(")
- valueParamsInParen.joinTo(buffer = this) { it.render(KtDeclarationRendererForSource.WITH_SHORT_NAMES) }
+ valueParamsInParen.joinTo(buffer = this) {
+ it.render(KtDeclarationRendererForSource.WITH_SHORT_NAMES)
+ }
append(closingString)
}
private fun KtAnalysisSession.isRequired(valueParamSymbol: KtValueParameterSymbol): Boolean {
if (valueParamSymbol.hasDefaultValue) return false
- // TODO(274145999): When we check it with a real AS instance, determine if we can drop this hacky solution or not.
- // The KtValueParameterSymbol we get when running this from [ComposableItemPresentationProvider] for some reason says that optional
- // Composable parameters don't declare a default value, which is incorrect. At the moment, the only way I've found to determine that
+ // TODO(274145999): When we check it with a real AS instance, determine if we can drop this hacky
+ // solution or not.
+ // The KtValueParameterSymbol we get when running this from [ComposableItemPresentationProvider]
+ // for some reason says that optional
+ // Composable parameters don't declare a default value, which is incorrect. At the moment, the
+ // only way I've found to determine that
// they're truly optional is by looking at their text.
return valueParamSymbol.psi?.text?.endsWith("/* = compiled code */") != true
}
-fun KtAnalysisSession.isComposableFunctionParameter(valueParamSymbol: KtValueParameterSymbol): Boolean {
- // Since vararg is not a function type parameter, we have to return false for a parameter with a vararg.
- // In FE1.0, it was simple because vararg has an array type and checking that the parameter is a function type returns false.
- // On the other hand, K2's value parameter symbol deliberately unwraps it and returns the element type as a symbol's returnType.
+fun KtAnalysisSession.isComposableFunctionParameter(
+ valueParamSymbol: KtValueParameterSymbol
+): Boolean {
+ // Since vararg is not a function type parameter, we have to return false for a parameter with a
+ // vararg.
+ // In FE1.0, it was simple because vararg has an array type and checking that the parameter is a
+ // function type returns false.
+ // On the other hand, K2's value parameter symbol deliberately unwraps it and returns the element
+ // type as a symbol's returnType.
// We need a separate check for a vararg.
if (valueParamSymbol.isVararg) return false
@@ -78,5 +100,7 @@
// Mimic FE1.0 `KotlinType.isBuiltinFunctionalType`.
val isBuiltinFunctionalType = parameterType.isFunctionType || parameterType.isSuspendFunctionType
return isBuiltinFunctionalType &&
- parameterType.annotationsByClassId(ClassId.topLevel(FqName(COMPOSABLE_ANNOTATION_FQ_NAME))).isNotEmpty()
-}
\ No newline at end of file
+ parameterType
+ .annotationsByClassId(ClassId.topLevel(FqName(COMPOSABLE_ANNOTATION_FQ_NAME)))
+ .isNotEmpty()
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/aa/intentions/ComposeCreateComposableFunctionQuickFix.kt b/compose-ide-plugin/src/com/android/tools/compose/aa/intentions/ComposeCreateComposableFunctionQuickFix.kt
index 898652d..57f2df2 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/aa/intentions/ComposeCreateComposableFunctionQuickFix.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/aa/intentions/ComposeCreateComposableFunctionQuickFix.kt
@@ -49,34 +49,28 @@
*
* Created action creates new function with @Composable annotation.
*
- * Example:
- * For
+ * Example: For
*
- * @Composable
- * fun myComposable() {
- * <caret>newFunction()
- * }
+ * @Composable fun myComposable() { <caret>newFunction() }
*
* creates
*
- * @Composable
- * fun newFunction() {
- * TODO("Not yet implemented")
- * }
+ * @Composable fun newFunction() {
*
- * b/267429486: This quickfix should make use of [CreateCallableFromUsageFix]
- * when that machinery is available on K2. For now, this implementation will
- * e.g. always extract the newFunction as a sibling to the calling compose
- * Function, and have fewer smarts in terms of parameter names and types.
+ * b/267429486: This quickfix should make use of [CreateCallableFromUsageFix] when that machinery is
+ * available on K2. For now, this implementation will e.g. always extract the newFunction as a
+ * sibling to the calling compose Function, and have fewer smarts in terms of parameter names and
+ * types.
+ *
+ * TODO("Not yet implemented") }
*/
class ComposeCreateComposableFunctionQuickFix(
element: KtCallExpression,
private val newFunction: KtNamedFunction,
private val sibling: KtNamedFunction,
-): QuickFixActionBase<KtCallExpression>(element) {
+) : QuickFixActionBase<KtCallExpression>(element) {
- override fun getFamilyName(): String =
- KotlinBundle.message("fix.create.from.usage.family")
+ override fun getFamilyName(): String = KotlinBundle.message("fix.create.from.usage.family")
override fun getText(): String =
ComposeBundle.message("create.composable.function") + " '${newFunction.name}'"
@@ -99,7 +93,9 @@
val parentFunction = unresolvedCall.getStrictParentOfType<KtNamedFunction>() ?: return null
if (!parentFunction.isComposableFunction()) return null
- val unresolvedName = (unresolvedCall.calleeExpression as? KtSimpleNameExpression)?.getReferencedName() ?: return null
+ val unresolvedName =
+ (unresolvedCall.calleeExpression as? KtSimpleNameExpression)?.getReferencedName()
+ ?: return null
if (unresolvedName.isBlank() || !unresolvedName[0].isUpperCase()) return null
val fullCallExpression = unresolvedCall.getQualifiedExpressionForSelectorOrThis()
@@ -113,9 +109,8 @@
}
/**
- * Budget-version of [CreateCallableFromUsageFix]: Constructs a plain
- * function annotated with `@Composable`: infers (type) parameters from
- * [unresolvedCall].
+ * Budget-version of [CreateCallableFromUsageFix]: Constructs a plain function annotated with
+ * `@Composable`: infers (type) parameters from [unresolvedCall].
*
* See b/267429486.
*/
@@ -124,23 +119,31 @@
unresolvedName: String,
container: KtElement,
): KtNamedFunction =
- KtPsiFactory(container).createFunction(
- KtPsiFactory.CallableBuilder(KtPsiFactory.CallableBuilder.Target.FUNCTION).apply {
- modifier("@$COMPOSABLE_ANNOTATION_NAME")
- typeParams(unresolvedCall.typeArguments.mapIndexed { index, _ -> "T$index" })
- name(unresolvedName)
- unresolvedCall.valueArguments.forEachIndexed { index, arg ->
- val type = arg.getArgumentExpression()?.getKtType() ?: builtinTypes.ANY
- val name = arg.getArgumentName()?.referenceExpression?.getReferencedName() ?: "x$index"
- param(name, type.render(KtTypeRendererForSource.WITH_SHORT_NAMES, Variance.INVARIANT))
- }
- noReturnType()
- blockBody("TODO(\"Not yet implemented\")")
- }.asString())
+ KtPsiFactory(container)
+ .createFunction(
+ KtPsiFactory.CallableBuilder(KtPsiFactory.CallableBuilder.Target.FUNCTION)
+ .apply {
+ modifier("@$COMPOSABLE_ANNOTATION_NAME")
+ typeParams(unresolvedCall.typeArguments.mapIndexed { index, _ -> "T$index" })
+ name(unresolvedName)
+ unresolvedCall.valueArguments.forEachIndexed { index, arg ->
+ val type = arg.getArgumentExpression()?.getKtType() ?: builtinTypes.ANY
+ val name =
+ arg.getArgumentName()?.referenceExpression?.getReferencedName() ?: "x$index"
+ param(
+ name,
+ type.render(KtTypeRendererForSource.WITH_SHORT_NAMES, Variance.INVARIANT)
+ )
+ }
+ noReturnType()
+ blockBody("TODO(\"Not yet implemented\")")
+ }
+ .asString()
+ )
/**
- * For the purpose of creating Composable functions, optimistically guesses
- * that [expression] is of type `Unit`.
+ * For the purpose of creating Composable functions, optimistically guesses that [expression] is
+ * of type `Unit`.
*/
private fun KtAnalysisSession.guessReturnType(expression: KtExpression): KtType {
return (expression.getKtType() as? KtFunctionalType)?.returnType ?: builtinTypes.UNIT
diff --git a/compose-ide-plugin/src/com/android/tools/compose/aa/intentions/ComposeIdePluginQuickFixRegistrar.kt b/compose-ide-plugin/src/com/android/tools/compose/aa/intentions/ComposeIdePluginQuickFixRegistrar.kt
index 971461e..f930110 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/aa/intentions/ComposeIdePluginQuickFixRegistrar.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/aa/intentions/ComposeIdePluginQuickFixRegistrar.kt
@@ -15,11 +15,15 @@
*/
package com.android.tools.compose.aa.intentions
+import com.android.tools.compose.intentions.AddComposableToFunctionQuickFix
+import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KotlinQuickFixRegistrar
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KotlinQuickFixesList
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KtQuickFixesListBuilder
-class ComposeIdePluginQuickFixRegistrar : org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KotlinQuickFixRegistrar() {
- override val list: KotlinQuickFixesList = KtQuickFixesListBuilder.registerPsiQuickFix {
- registerApplicator(ComposeCreateComposableFunctionQuickFix.factory)
- }
+class ComposeIdePluginQuickFixRegistrar : KotlinQuickFixRegistrar() {
+ override val list: KotlinQuickFixesList =
+ KtQuickFixesListBuilder.registerPsiQuickFix {
+ registerApplicator(ComposeCreateComposableFunctionQuickFix.factory)
+ registerApplicator(AddComposableToFunctionQuickFix.k2DiagnosticFixFactory)
+ }
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/ComposableFunctionRendering.kt b/compose-ide-plugin/src/com/android/tools/compose/code/ComposableFunctionRendering.kt
index 1c960f9..e80c4d0 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/ComposableFunctionRendering.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/ComposableFunctionRendering.kt
@@ -28,8 +28,8 @@
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtDeclaration
-import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.renderer.DescriptorRenderer.ValueParametersHandler
+import org.jetbrains.kotlin.resolve.source.getPsi
/**
* Represents parts of a Composable function to be used for rendering in various menus or dialogs.
@@ -39,11 +39,11 @@
fun KtDeclaration.getComposableFunctionRenderParts(): ComposableFunctionRenderParts? {
return if (isK2Plugin()) {
analyze(this) {
- val functionLikeSymbol = this@getComposableFunctionRenderParts.getSymbol() as? KtFunctionLikeSymbol ?: return null
+ val functionLikeSymbol =
+ this@getComposableFunctionRenderParts.getSymbol() as? KtFunctionLikeSymbol ?: return null
getComposableFunctionRenderParts(functionLikeSymbol)
}
- }
- else {
+ } else {
val descriptor = this.descriptor as? FunctionDescriptor ?: return null
descriptor.getComposableFunctionRenderParts()
}
@@ -52,9 +52,10 @@
/**
* Generates [ComposableFunctionRenderParts] for a given Composable function.
*
- * Since Composable functions tend to have numerous optional parameters, those are omitted from the rendered parameters and replaced with an
- * ellipsis ("..."). Additional modifications are made to ensure that a lambda can be added in cases where the Composable function requires
- * another Composable as its final argument.
+ * Since Composable functions tend to have numerous optional parameters, those are omitted from the
+ * rendered parameters and replaced with an ellipsis ("..."). Additional modifications are made to
+ * ensure that a lambda can be added in cases where the Composable function requires another
+ * Composable as its final argument.
*/
fun FunctionDescriptor.getComposableFunctionRenderParts(): ComposableFunctionRenderParts {
val allParameters = valueParameters
@@ -62,14 +63,17 @@
val lastParamIsComposable = allParameters.lastOrNull()?.isComposableFunctionParameter() == true
val inParens = if (lastParamIsComposable) requiredParameters.dropLast(1) else requiredParameters
- val descriptorRenderer = when {
- requiredParameters.size < allParameters.size -> SHORT_NAMES_RENDERER_WITH_DOTS
- inParens.isEmpty() && lastParamIsComposable -> null // Don't render an empty pair of parentheses if we're rendering a lambda afterwards.
- else -> SHORT_NAMES_RENDERER
- }
+ val descriptorRenderer =
+ when {
+ requiredParameters.size < allParameters.size -> SHORT_NAMES_RENDERER_WITH_DOTS
+ inParens.isEmpty() && lastParamIsComposable ->
+ null // Don't render an empty pair of parentheses if we're rendering a lambda afterwards.
+ else -> SHORT_NAMES_RENDERER
+ }
val parameters = descriptorRenderer?.renderValueParameters(inParens, false)
- val tail = if (lastParamIsComposable) LambdaSignatureTemplates.DEFAULT_LAMBDA_PRESENTATION else null
+ val tail =
+ if (lastParamIsComposable) LambdaSignatureTemplates.DEFAULT_LAMBDA_PRESENTATION else null
return ComposableFunctionRenderParts(parameters, tail)
}
@@ -77,25 +81,28 @@
private fun ValueParameterDescriptor.isRequired(): Boolean {
if (declaresDefaultValue()) return false
- // The ValueParameterDescriptor we get when running this from [ComposableItemPresentationProvider] for some reason says that optional
- // Composable parameters don't declare a default value, which is incorrect. At the moment, the only way I've found to determine that
+ // The ValueParameterDescriptor we get when running this from [ComposableItemPresentationProvider]
+ // for some reason says that optional
+ // Composable parameters don't declare a default value, which is incorrect. At the moment, the
+ // only way I've found to determine that
// they're truly optional is by looking at their text.
return source.getPsi()?.text?.endsWith("/* = compiled code */") != true
}
fun ValueParameterDescriptor.isComposableFunctionParameter(): Boolean {
val parameterType = type
- return parameterType.isBuiltinFunctionalType && parameterType.annotations.hasAnnotation(FqName(COMPOSABLE_ANNOTATION_FQ_NAME))
+ return parameterType.isBuiltinFunctionalType &&
+ parameterType.annotations.hasAnnotation(FqName(COMPOSABLE_ANNOTATION_FQ_NAME))
}
-/**
- * A version of [SHORT_NAMES_RENDERER] that adds `, ...)` at the end of the parameters list.
- */
-private val SHORT_NAMES_RENDERER_WITH_DOTS = SHORT_NAMES_RENDERER.withOptions {
- valueParametersHandler = object : ValueParametersHandler by ValueParametersHandler.DEFAULT {
- override fun appendAfterValueParameters(parameterCount: Int, builder: StringBuilder) {
- if (parameterCount > 0) builder.append(", ")
- builder.append("...)")
- }
+/** A version of [SHORT_NAMES_RENDERER] that adds `, ...)` at the end of the parameters list. */
+private val SHORT_NAMES_RENDERER_WITH_DOTS =
+ SHORT_NAMES_RENDERER.withOptions {
+ valueParametersHandler =
+ object : ValueParametersHandler by ValueParametersHandler.DEFAULT {
+ override fun appendAfterValueParameters(parameterCount: Int, builder: StringBuilder) {
+ if (parameterCount > 0) builder.append(", ")
+ builder.append("...)")
+ }
+ }
}
-}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/ComposeLineMarkerProviderDescriptor.kt b/compose-ide-plugin/src/com/android/tools/compose/code/ComposeLineMarkerProviderDescriptor.kt
index 352e76b..969971e 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/ComposeLineMarkerProviderDescriptor.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/ComposeLineMarkerProviderDescriptor.kt
@@ -52,22 +52,30 @@
override fun getIcon() = StudioIcons.Compose.Editor.COMPOSABLE_FUNCTION
override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? {
- if (element !is LeafPsiElement || element.elementType != KtTokens.IDENTIFIER || !isComposeEnabled(element)) return null
+ if (
+ element !is LeafPsiElement ||
+ element.elementType != KtTokens.IDENTIFIER ||
+ !isComposeEnabled(element)
+ )
+ return null
val parentFunction = element.parent.parent as? KtCallExpression ?: return null
if (!isComposableInvocation(parentFunction)) return null
- return LineMarkerInfo<PsiElement>(element,
- element.range,
- StudioIcons.Compose.Editor.COMPOSABLE_FUNCTION,
- { ComposeBundle.message("composable.line.marker.tooltip") },
- /* navHandler = */ null,
- GutterIconRenderer.Alignment.RIGHT,
- { ComposeBundle.message("composable.line.marker.tooltip") })
+ return LineMarkerInfo<PsiElement>(
+ element,
+ element.range,
+ StudioIcons.Compose.Editor.COMPOSABLE_FUNCTION,
+ { ComposeBundle.message("composable.line.marker.tooltip") },
+ /* navHandler = */ null,
+ GutterIconRenderer.Alignment.RIGHT,
+ { ComposeBundle.message("composable.line.marker.tooltip") }
+ )
}
companion object {
- private val ANALYSIS_RESULT_KEY = Key<CachedValue<AnalysisResult>>("ComposeLineMarkerProviderDescriptor.AnalysisResult")
+ private val ANALYSIS_RESULT_KEY =
+ Key<CachedValue<AnalysisResult>>("ComposeLineMarkerProviderDescriptor.AnalysisResult")
private fun isComposableInvocation(parentFunction: KtCallExpression): Boolean {
if (isK2Plugin()) {
@@ -82,17 +90,25 @@
val containingFile = parentFunction.containingFile as? KtFile ?: return false
- val analysisResult = CachedValuesManager.getManager(parentFunction.project).getCachedValue(
- containingFile,
- ANALYSIS_RESULT_KEY,
- getCachedValueProvider(containingFile),
- /* trackValue = */ false)
+ val analysisResult =
+ CachedValuesManager.getManager(parentFunction.project)
+ .getCachedValue(
+ containingFile,
+ ANALYSIS_RESULT_KEY,
+ getCachedValueProvider(containingFile),
+ /* trackValue = */ false
+ )
- return parentFunction.getResolvedCall(analysisResult.bindingContext)?.isComposableInvocation() ?: false
+ return parentFunction.getResolvedCall(analysisResult.bindingContext)?.isComposableInvocation()
+ ?: false
}
private fun getCachedValueProvider(ktFile: KtFile) = CachedValueProvider {
- CachedValueProvider.Result.create(ktFile.analyzeWithAllCompilerChecks(), ktFile, PsiModificationTracker.MODIFICATION_COUNT)
+ CachedValueProvider.Result.create(
+ ktFile.analyzeWithAllCompilerChecks(),
+ ktFile,
+ PsiModificationTracker.MODIFICATION_COUNT
+ )
}
}
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/ComposeStateReadAnnotator.kt b/compose-ide-plugin/src/com/android/tools/compose/code/ComposeStateReadAnnotator.kt
index 3ce93fb..41618b9 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/ComposeStateReadAnnotator.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/ComposeStateReadAnnotator.kt
@@ -53,20 +53,25 @@
private val CLASS_ID_OF_STATE = ClassId.topLevel(FqName(FQNAME))
/**
- * Annotator that highlights reads of `androidx.compose.runtime.State` variables inside `@Composable` functions.
+ * Annotator that highlights reads of `androidx.compose.runtime.State` variables inside
+ * `@Composable` functions.
*
- * TODO(b/225218822): Before productionizing this, depending on whether we want a gutter icon, highlighting, or both,
- * we must change this to use `KotlinHighlightingVisitorExtension` (to avoid race conditions), or use a
- * `RelatedItemLineMarkerProvider` for the gutter icon so it can be disabled with a setting. If we do both, we will
- * need to share the logic and store result on the `PsiElement` to avoid computing it twice.
+ * TODO(b/225218822): Before productionizing this, depending on whether we want a gutter icon,
+ * highlighting, or both, we must change this to use `KotlinHighlightingVisitorExtension` (to
+ * avoid race conditions), or use a `RelatedItemLineMarkerProvider` for the gutter icon so it can
+ * be disabled with a setting. If we do both, we will need to share the logic and store result on
+ * the `PsiElement` to avoid computing it twice.
*/
class ComposeStateReadAnnotator : Annotator {
override fun annotate(element: PsiElement, holder: AnnotationHolder) {
if (!StudioFlags.COMPOSE_STATE_READ_HIGHLIGHTING_ENABLED.get()) return
if (element !is KtNameReferenceExpression) return
- val scopeName = element.parentOfType<KtNamedFunction>()?.takeIf { it.hasComposableAnnotation() }?.name ?: return
+ val scopeName =
+ element.parentOfType<KtNamedFunction>()?.takeIf { it.hasComposableAnnotation() }?.name
+ ?: return
element.getStateReadElement()?.let {
- holder.newAnnotation(HighlightSeverity.INFORMATION, createMessage(it.text, scopeName))
+ holder
+ .newAnnotation(HighlightSeverity.INFORMATION, createMessage(it.text, scopeName))
.textAttributes(COMPOSE_STATE_READ_TEXT_ATTRIBUTES_KEY)
.gutterIconRenderer(ComposeStateReadGutterIconRenderer(it.text, scopeName))
.create()
@@ -99,8 +104,8 @@
}
/**
- * Returns whether the expression represents an implicit call to `State#getValue`, i.e. if the expression
- * is for a delegated property where the delegate is of type `State`.
+ * Returns whether the expression represents an implicit call to `State#getValue`, i.e. if the
+ * expression is for a delegated property where the delegate is of type `State`.
*
* E.g. for a name reference expression `foo` if `foo` is defined as:
*
@@ -113,33 +118,33 @@
private fun KotlinType.isStateType() =
(fqName?.asString() == FQNAME || supertypes().any { it.fqName?.asString() == FQNAME })
- private fun KtAnalysisSession.isStateType(type: KtType): Boolean = if (type is KtNonErrorClassType) {
- type.classId == CLASS_ID_OF_STATE || type.getAllSuperTypes().any { it is KtNonErrorClassType && it.classId == CLASS_ID_OF_STATE }
- } else {
- false
- }
+ private fun KtAnalysisSession.isStateType(type: KtType): Boolean =
+ if (type is KtNonErrorClassType) {
+ type.classId == CLASS_ID_OF_STATE ||
+ type.getAllSuperTypes().any { it is KtNonErrorClassType && it.classId == CLASS_ID_OF_STATE }
+ } else {
+ false
+ }
@OptIn(KtAllowAnalysisOnEdt::class)
private fun KtExpression.isStateType(): Boolean =
if (isK2Plugin()) {
- allowAnalysisOnEdt {
- analyze(this) {
- getKtType()?.let { isStateType(it) } ?: false
- }
- }
+ allowAnalysisOnEdt { analyze(this) { getKtType()?.let { isStateType(it) } ?: false } }
} else {
resolveExprType()?.isStateType() ?: false
}
private fun KtNameReferenceExpression.isAssignee(): Boolean {
return parentOfType<KtBinaryExpression>()
- ?.takeIf { it.operationToken.toString() == "EQ" }
- ?.let { it.left == this || it.left?.descendants()?.contains(this) == true }
- ?: false
+ ?.takeIf { it.operationToken.toString() == "EQ" }
+ ?.let { it.left == this || it.left?.descendants()?.contains(this) == true }
+ ?: false
}
- private data class ComposeStateReadGutterIconRenderer(private val stateName: String,
- private val functionName: String) : GutterIconRenderer() {
+ private data class ComposeStateReadGutterIconRenderer(
+ private val stateName: String,
+ private val functionName: String
+ ) : GutterIconRenderer() {
override fun getIcon() = StudioIcons.Common.INFO
override fun getTooltipText() = createMessage(stateName, functionName)
}
@@ -147,7 +152,10 @@
companion object {
const val COMPOSE_STATE_READ_TEXT_ATTRIBUTES_NAME = "ComposeStateReadTextAttributes"
val COMPOSE_STATE_READ_TEXT_ATTRIBUTES_KEY: TextAttributesKey =
- TextAttributesKey.createTextAttributesKey(COMPOSE_STATE_READ_TEXT_ATTRIBUTES_NAME, DefaultLanguageHighlighterColors.FUNCTION_CALL)
+ TextAttributesKey.createTextAttributesKey(
+ COMPOSE_STATE_READ_TEXT_ATTRIBUTES_NAME,
+ DefaultLanguageHighlighterColors.FUNCTION_CALL
+ )
private fun createMessage(stateVariable: String, composable: String) =
"State read: when the value of \"$stateVariable\" changes, \"$composable\" will recompose."
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/actions/ComposeProximityWeigher.kt b/compose-ide-plugin/src/com/android/tools/compose/code/actions/ComposeProximityWeigher.kt
index 11204e6..631ba51 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/actions/ComposeProximityWeigher.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/actions/ComposeProximityWeigher.kt
@@ -25,16 +25,19 @@
import org.jetbrains.kotlin.psi.KtNamedDeclaration
/** This weigher is used in determining the order of items in Kotlin's "Add Imports" list. */
-class ComposeProximityWeigher: ProximityWeigher() {
+class ComposeProximityWeigher : ProximityWeigher() {
override fun weigh(element: PsiElement, location: ProximityLocation): Int? {
if (location.position?.language != KotlinLanguage.INSTANCE) return null
if (location.positionModule?.getModuleSystem()?.usesCompose != true) return null
- // In the "Add Import" case, `location` unfortunately points only to the file we're in -- so we can't do anything smart that would take
+ // In the "Add Import" case, `location` unfortunately points only to the file we're in -- so we
+ // can't do anything smart that would take
// into account the position of the code that needs the import.
// If we've manually weighted this element, use that weight.
- element.manualWeight()?.let { return it }
+ element.manualWeight()?.let {
+ return it
+ }
if (element.isComposableFunction() && !element.isDeprecated()) return COMPOSABLE_METHOD_WEIGHT
@@ -55,7 +58,8 @@
return MANUALLY_WEIGHTED_FQ_NAMES[fqName]
}
-private val MANUALLY_WEIGHTED_FQ_NAMES = mapOf(
- "androidx.compose.ui.Modifier" to PROMOTED_WEIGHT,
- "androidx.compose.ui.graphics.Color" to PROMOTED_WEIGHT,
-)
+private val MANUALLY_WEIGHTED_FQ_NAMES =
+ mapOf(
+ "androidx.compose.ui.Modifier" to PROMOTED_WEIGHT,
+ "androidx.compose.ui.graphics.Color" to PROMOTED_WEIGHT,
+ )
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeCompletionContributor.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeCompletionContributor.kt
index 9b3b983..86f9ff4 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeCompletionContributor.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeCompletionContributor.kt
@@ -45,6 +45,9 @@
import com.intellij.psi.util.parentOfType
import com.intellij.util.asSafely
import icons.StudioIcons
+import java.io.BufferedReader
+import javax.swing.Icon
+import javax.swing.ImageIcon
import org.jetbrains.annotations.VisibleForTesting
import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
@@ -74,9 +77,6 @@
import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue
import org.jetbrains.kotlin.resolve.calls.results.argumentValueType
import org.jetbrains.kotlin.types.typeUtil.isUnit
-import java.io.BufferedReader
-import javax.swing.Icon
-import javax.swing.ImageIcon
private val COMPOSABLE_FUNCTION_ICON = StudioIcons.Compose.Editor.COMPOSABLE_FUNCTION
@@ -91,50 +91,59 @@
// The only type in the list is the return type (can be Unit).
type.isFunctionType && argumentValueType.arguments.size == 1
-private fun KtAnalysisSession.isLambdaWithNoParameters(valueParameterSymbol: KtValueParameterSymbol): Boolean {
+private fun KtAnalysisSession.isLambdaWithNoParameters(
+ valueParameterSymbol: KtValueParameterSymbol
+): Boolean {
// The only type in the list is the return type (can be Unit).
val functionalReturnType = valueParameterSymbol.returnType as? KtFunctionalType ?: return false
return functionalReturnType.ownTypeArguments.size == 1
}
-/**
- * true iff the last parameter is required, and a lambda type with no parameters.
- */
+/** true iff the last parameter is required, and a lambda type with no parameters. */
private fun ValueParameterDescriptor.isRequiredLambdaWithNoParameters() =
!hasDefaultValue() && isLambdaWithNoParameters() && varargElementType == null
-/**
- * true iff the last parameter is required, and a lambda type with no parameters.
- */
-private fun KtAnalysisSession.isRequiredLambdaWithNoParameters(valueParameterSymbol: KtValueParameterSymbol) =
- !valueParameterSymbol.hasDefaultValue && isLambdaWithNoParameters(valueParameterSymbol) && !valueParameterSymbol.isVararg
+/** true iff the last parameter is required, and a lambda type with no parameters. */
+private fun KtAnalysisSession.isRequiredLambdaWithNoParameters(
+ valueParameterSymbol: KtValueParameterSymbol
+) =
+ !valueParameterSymbol.hasDefaultValue &&
+ isLambdaWithNoParameters(valueParameterSymbol) &&
+ !valueParameterSymbol.isVararg
private fun InsertionContext.getParent(): PsiElement? = file.findElementAt(startOffset)?.parent
/**
- * Find the [CallType] from the [InsertionContext]. The [CallType] can be used to detect if the completion is being done in a regular
- * statement, an import or some other expression and decide if we want to apply the [ComposeInsertHandler].
+ * Find the [CallType] from the [InsertionContext]. The [CallType] can be used to detect if the
+ * completion is being done in a regular statement, an import or some other expression and decide if
+ * we want to apply the [ComposeInsertHandler].
*/
private fun PsiElement?.inferCallType(): CallType<*> {
- // Look for an existing KtSimpleNameExpression to pass to CallTypeAndReceiver.detect so we can infer the call type.
- val namedExpression = (this as? KtSimpleNameExpression)?.mainReference?.expression ?: return CallType.DEFAULT
+ // Look for an existing KtSimpleNameExpression to pass to CallTypeAndReceiver.detect so we can
+ // infer the call type.
+ val namedExpression =
+ (this as? KtSimpleNameExpression)?.mainReference?.expression ?: return CallType.DEFAULT
return CallTypeAndReceiver.detect(namedExpression).callType
}
/**
* Return true if element is a KDoc.
*
- * Ideally, we would use [inferCallType] but there doesn't seem to be a [CallType] for a KDoc element.
+ * Ideally, we would use [inferCallType] but there doesn't seem to be a [CallType] for a KDoc
+ * element.
*/
private fun PsiElement?.isKdoc() = this is KDocName
-/**
- * Modifies [LookupElement]s for composable functions, to improve Compose editing UX.
- */
+/** Modifies [LookupElement]s for composable functions, to improve Compose editing UX. */
class ComposeCompletionContributor : CompletionContributor() {
- override fun fillCompletionVariants(parameters: CompletionParameters, resultSet: CompletionResultSet) {
- if (parameters.position.getModuleSystem()?.usesCompose != true ||
- parameters.position.language != KotlinLanguage.INSTANCE) {
+ override fun fillCompletionVariants(
+ parameters: CompletionParameters,
+ resultSet: CompletionResultSet
+ ) {
+ if (
+ parameters.position.getModuleSystem()?.usesCompose != true ||
+ parameters.position.language != KotlinLanguage.INSTANCE
+ ) {
return
}
@@ -153,8 +162,8 @@
psi.isComposableFunction() ->
if (lookupElement.isForSpecialLambdaLookupElement()) null
else completionResult.withLookupElement(ComposableFunctionLookupElement(lookupElement))
-
- ComposeMaterialIconLookupElement.appliesTo(psi) -> completionResult.withLookupElement(ComposeMaterialIconLookupElement(lookupElement))
+ ComposeMaterialIconLookupElement.appliesTo(psi) ->
+ completionResult.withLookupElement(ComposeMaterialIconLookupElement(lookupElement))
// No transformation needed.
else -> completionResult
@@ -162,9 +171,10 @@
}
/**
- * Checks if the [LookupElement] is an additional, "special" lookup element created for functions that can be invoked using the lambda
- * syntax. These are created by [LookupElementFactory.addSpecialFunctionCallElements] and can be confusing for Compose APIs that often
- * use overloaded function names.
+ * Checks if the [LookupElement] is an additional, "special" lookup element created for functions
+ * that can be invoked using the lambda syntax. These are created by
+ * [LookupElementFactory.addSpecialFunctionCallElements] and can be confusing for Compose APIs
+ * that often use overloaded function names.
*/
private fun LookupElement.isForSpecialLambdaLookupElement(): Boolean {
val presentation = LookupElementPresentation()
@@ -173,13 +183,10 @@
}
}
-/**
- * Wraps original Kotlin [LookupElement]s for composable functions to make them stand out more.
- */
-private class ComposableFunctionLookupElement(original: LookupElement) : LookupElementDecorator<LookupElement>(original) {
- /**
- * Set of [CallType]s that should be handled by the [ComposeInsertHandler].
- */
+/** Wraps original Kotlin [LookupElement]s for composable functions to make them stand out more. */
+private class ComposableFunctionLookupElement(original: LookupElement) :
+ LookupElementDecorator<LookupElement>(original) {
+ /** Set of [CallType]s that should be handled by the [ComposeInsertHandler]. */
private val validCallTypes = setOf(CallType.DEFAULT, CallType.DOT)
init {
@@ -196,14 +203,20 @@
analyze(element) {
val functionSymbol = element.getFunctionLikeSymbol()
presentation.icon = COMPOSABLE_FUNCTION_ICON
- presentation.setTypeText(if (functionSymbol.returnType.isUnit) null else presentation.typeText, null)
+ presentation.setTypeText(
+ if (functionSymbol.returnType.isUnit) null else presentation.typeText,
+ null
+ )
val (parameters, tail) = getComposableFunctionRenderParts(functionSymbol)
rewriteSignature(presentation, parameters, tail)
}
} else {
val descriptor = getFunctionDescriptor() ?: return
presentation.icon = COMPOSABLE_FUNCTION_ICON
- presentation.setTypeText(if (descriptor.returnType?.isUnit() == true) null else presentation.typeText, null)
+ presentation.setTypeText(
+ if (descriptor.returnType?.isUnit() == true) null else presentation.typeText,
+ null
+ )
val (parameters, tail) = descriptor.getComposableFunctionRenderParts()
rewriteSignature(presentation, parameters, tail)
}
@@ -231,17 +244,24 @@
}
}
- private fun rewriteSignature(presentation: LookupElementPresentation, parameters: String?, tail: String?) {
+ private fun rewriteSignature(
+ presentation: LookupElementPresentation,
+ parameters: String?,
+ tail: String?
+ ) {
presentation.clearTail()
parameters?.let { presentation.appendTailTextItalic(it, /* grayed = */ false) }
tail?.let { presentation.appendTailText(" $it", /* grayed = */ true) }
}
}
-/** Lookup element that decorates a Compose material icon property with the actual icon it represents. */
+/**
+ * Lookup element that decorates a Compose material icon property with the actual icon it
+ * represents.
+ */
@VisibleForTesting
-internal class ComposeMaterialIconLookupElement(private val original: LookupElement)
- : LookupElementDecorator<LookupElement>(original) {
+internal class ComposeMaterialIconLookupElement(private val original: LookupElement) :
+ LookupElementDecorator<LookupElement>(original) {
init {
// We know we'll want material icons, so start warming up the cache.
@@ -261,36 +281,42 @@
*
* The key is the package name, coming from a fully-qualified icon name.
*
- * The value is a pair that identifies how to construct names that represent these icons. The first string in each pair represents part
- * of a directory name where the theme's icons are stored. The second string value represents the prefix of each image's file name.
+ * The value is a pair that identifies how to construct names that represent these icons. The
+ * first string in each pair represents part of a directory name where the theme's icons are
+ * stored. The second string value represents the prefix of each image's file name.
*/
- private val themeNamingPatterns = mapOf(
- "androidx.compose.material.icons.filled" to Pair("materialicons", "baseline"),
- "androidx.compose.material.icons.rounded" to Pair("materialiconsround", "round"),
- "androidx.compose.material.icons.sharp" to Pair("materialiconssharp", "sharp"),
- "androidx.compose.material.icons.twotone" to Pair("materialiconstwotone", "twotone"),
- "androidx.compose.material.icons.outlined" to Pair("materialiconsoutlined", "outline"),
- )
+ private val themeNamingPatterns =
+ mapOf(
+ "androidx.compose.material.icons.filled" to Pair("materialicons", "baseline"),
+ "androidx.compose.material.icons.rounded" to Pair("materialiconsround", "round"),
+ "androidx.compose.material.icons.sharp" to Pair("materialiconssharp", "sharp"),
+ "androidx.compose.material.icons.twotone" to Pair("materialiconstwotone", "twotone"),
+ "androidx.compose.material.icons.outlined" to Pair("materialiconsoutlined", "outline"),
+ )
/** Whether ComposeMaterialIconLookupElement can apply to the given [LookupElement]. */
fun appliesTo(psiElement: PsiElement): Boolean {
if (psiElement !is KtProperty) return false
val fqName = psiElement.kotlinFqName?.asString() ?: return false
- if (!fqName.startsWith("androidx.compose.material.icons") ||
- psiElement.typeReference?.text?.endsWith("ImageVector") != true) return false
+ if (
+ !fqName.startsWith("androidx.compose.material.icons") ||
+ psiElement.typeReference?.text?.endsWith("ImageVector") != true
+ )
+ return false
return themeNamingPatterns.containsKey(fqName.substringBeforeLast('.'))
}
/**
- * Converts the property name of a Compose material icon to the snake-case equivalent used in file names, with additional underscores
- * separating digits from non-digit characters.
+ * Converts the property name of a Compose material icon to the snake-case equivalent used in
+ * file names, with additional underscores separating digits from non-digit characters.
*
* eg: "AccountBox3" -> "account_box_3"
*
- * If a material icon's name starts with a number, the property name has an underscore prepended to make it a valid identifier, even
- * though the underscore doesn't appear in the file path and name.
+ * If a material icon's name starts with a number, the property name has an underscore prepended
+ * to make it a valid identifier, even though the underscore doesn't appear in the file path and
+ * name.
*
* eg: "_1kPlus42" -> "1k_plus_42"
*/
@@ -322,20 +348,32 @@
private fun getIconFromMaterialIconsProvider(fqName: String): Icon? {
val iconFileName = fqName.iconFileNameFromFqName() ?: return null
- return ComposeMaterialIconService.getInstance(ApplicationManager.getApplication()).getIcon(iconFileName)
+ return ComposeMaterialIconService.getInstance(ApplicationManager.getApplication())
+ .getIcon(iconFileName)
}
private fun getIconFromResources(fqName: String): Icon? {
val resourcePath = fqName.resourcePathFromFqName() ?: return null
- return ComposeMaterialIconLookupElement::class.java.classLoader.getResourceAsStream(resourcePath)?.use { inputStream ->
- val content = inputStream.bufferedReader().use(BufferedReader::readText)
- val errorLog = StringBuilder()
- val bufferedImage = VdPreview.getPreviewFromVectorXml(VdPreview.TargetSize.createFromMaxDimension(16), content, errorLog)
- if (errorLog.isNotEmpty()) Logger.getInstance(ComposeMaterialIconLookupElement::class.java).error(errorLog.toString())
+ return ComposeMaterialIconLookupElement::class
+ .java
+ .classLoader
+ .getResourceAsStream(resourcePath)
+ ?.use { inputStream ->
+ val content = inputStream.bufferedReader().use(BufferedReader::readText)
+ val errorLog = StringBuilder()
+ val bufferedImage =
+ VdPreview.getPreviewFromVectorXml(
+ VdPreview.TargetSize.createFromMaxDimension(16),
+ content,
+ errorLog
+ )
+ if (errorLog.isNotEmpty())
+ Logger.getInstance(ComposeMaterialIconLookupElement::class.java)
+ .error(errorLog.toString())
- ImageIcon(bufferedImage)
- }
+ ImageIcon(bufferedImage)
+ }
}
}
}
@@ -345,93 +383,121 @@
return elementAtCaret.getNextSiblingIgnoringWhitespace(true) ?: return null
}
-private fun InsertionContext.isNextElementOpenCurlyBrace() = getNextElementIgnoringWhitespace()?.text?.startsWith("{") == true
+private fun InsertionContext.isNextElementOpenCurlyBrace() =
+ getNextElementIgnoringWhitespace()?.text?.startsWith("{") == true
-private fun InsertionContext.isNextElementOpenParenthesis() = getNextElementIgnoringWhitespace()?.text?.startsWith("(") == true
+private fun InsertionContext.isNextElementOpenParenthesis() =
+ getNextElementIgnoringWhitespace()?.text?.startsWith("(") == true
-private abstract class ComposeInsertHandler(callType: CallType<*>) : KotlinCallableInsertHandler(callType) {
- override fun handleInsert(context: InsertionContext, item: LookupElement) = with(context) {
- super.handleInsert(context, item)
+private abstract class ComposeInsertHandler(callType: CallType<*>) :
+ KotlinCallableInsertHandler(callType) {
+ override fun handleInsert(context: InsertionContext, item: LookupElement) =
+ with(context) {
+ super.handleInsert(context, item)
- if (isNextElementOpenParenthesis()) return
+ if (isNextElementOpenParenthesis()) return
- // All Kotlin insertion handlers do this, possibly to post-process adding a new import in the call to super above.
- val psiDocumentManager = PsiDocumentManager.getInstance(project)
- psiDocumentManager.commitAllDocuments()
- psiDocumentManager.doPostponedOperationsAndUnblockDocument(document)
+ // All Kotlin insertion handlers do this, possibly to post-process adding a new import in the
+ // call to super above.
+ val psiDocumentManager = PsiDocumentManager.getInstance(project)
+ psiDocumentManager.commitAllDocuments()
+ psiDocumentManager.doPostponedOperationsAndUnblockDocument(document)
- val templateManager = TemplateManager.getInstance(project)
- val template = templateManager.createTemplate("", "").apply { configureFunctionTemplate(context, template = this) }
+ val templateManager = TemplateManager.getInstance(project)
+ val template =
+ templateManager.createTemplate("", "").apply {
+ configureFunctionTemplate(context, template = this)
+ }
- templateManager.startTemplate(editor, template, object : TemplateEditingAdapter() {
- override fun templateFinished(template: Template, brokenOff: Boolean) {
- if (!brokenOff) {
- val callExpression = file.findElementAt(editor.caretModel.offset)?.parentOfType<KtCallExpression>() ?: return
- val valueArgumentList = callExpression.valueArgumentList ?: return
- if (valueArgumentList.arguments.isEmpty() && callExpression.lambdaArguments.isNotEmpty()) {
- runWriteAction { valueArgumentList.delete() }
+ templateManager.startTemplate(
+ editor,
+ template,
+ object : TemplateEditingAdapter() {
+ override fun templateFinished(template: Template, brokenOff: Boolean) {
+ if (!brokenOff) {
+ val callExpression =
+ file.findElementAt(editor.caretModel.offset)?.parentOfType<KtCallExpression>()
+ ?: return
+ val valueArgumentList = callExpression.valueArgumentList ?: return
+ if (
+ valueArgumentList.arguments.isEmpty() && callExpression.lambdaArguments.isNotEmpty()
+ ) {
+ runWriteAction { valueArgumentList.delete() }
+ }
+ }
}
}
- }
- })
- }
+ )
+ }
abstract fun configureFunctionTemplate(context: InsertionContext, template: Template)
class ParameterInfo(val name: String, val isLambdaWithNoParameters: Boolean)
- fun configureFunctionTemplate(template: Template,
- parameterInfoList: List<ParameterInfo>,
- insertLambda: Boolean,
- isNextElementOpenCurlyBrace: Boolean) = template.apply {
- isToReformat = true
- setToIndent(true)
+ fun configureFunctionTemplate(
+ template: Template,
+ parameterInfoList: List<ParameterInfo>,
+ insertLambda: Boolean,
+ isNextElementOpenCurlyBrace: Boolean
+ ) =
+ template.apply {
+ isToReformat = true
+ setToIndent(true)
- when {
- parameterInfoList.isNotEmpty() -> {
- addTextSegment("(")
- parameterInfoList.forEachIndexed { index, paramInfo ->
- if (index > 0) {
- addTextSegment(", ")
+ when {
+ parameterInfoList.isNotEmpty() -> {
+ addTextSegment("(")
+ parameterInfoList.forEachIndexed { index, paramInfo ->
+ if (index > 0) {
+ addTextSegment(", ")
+ }
+ addTextSegment(paramInfo.name + " = ")
+ if (paramInfo.isLambdaWithNoParameters) {
+ addVariable(ConstantNode("{ /*TODO*/ }"), true)
+ } else {
+ addVariable(EmptyExpression(), true)
+ }
}
- addTextSegment(paramInfo.name + " = ")
- if (paramInfo.isLambdaWithNoParameters) {
- addVariable(ConstantNode("{ /*TODO*/ }"), true)
- }
- else {
- addVariable(EmptyExpression(), true)
- }
+ addTextSegment(")")
}
- addTextSegment(")")
+ !insertLambda -> addTextSegment("()")
}
- !insertLambda -> addTextSegment("()")
+ if (insertLambda && !isNextElementOpenCurlyBrace) {
+ addTextSegment(" {\n")
+ addEndVariable()
+ addTextSegment("\n}")
+ }
}
-
- if (insertLambda && !isNextElementOpenCurlyBrace) {
- addTextSegment(" {\n")
- addEndVariable()
- addTextSegment("\n}")
- }
- }
}
-private class ComposeInsertHandlerForK1(val functionDescriptor: FunctionDescriptor, callType: CallType<*>) : ComposeInsertHandler(
- callType) {
+private class ComposeInsertHandlerForK1(
+ val functionDescriptor: FunctionDescriptor,
+ callType: CallType<*>
+) : ComposeInsertHandler(callType) {
override fun configureFunctionTemplate(context: InsertionContext, template: Template) {
val allParameters = functionDescriptor.valueParameters
- val requiredParameters = allParameters.filter { !it.declaresDefaultValue() && it.varargElementType == null }
- val insertLambda = allParameters.lastOrNull()?.let { valueParamDescriptor ->
- valueParamDescriptor.isComposableFunctionParameter() || valueParamDescriptor.isRequiredLambdaWithNoParameters()
- } == true
+ val requiredParameters =
+ allParameters.filter { !it.declaresDefaultValue() && it.varargElementType == null }
+ val insertLambda =
+ allParameters.lastOrNull()?.let { valueParamDescriptor ->
+ valueParamDescriptor.isComposableFunctionParameter() ||
+ valueParamDescriptor.isRequiredLambdaWithNoParameters()
+ } == true
val inParens = if (insertLambda) requiredParameters.dropLast(1) else requiredParameters
- configureFunctionTemplate(template, inParens.map { ParameterInfo(it.name.asString(), it.isLambdaWithNoParameters()) }, insertLambda,
- context.isNextElementOpenCurlyBrace())
+ configureFunctionTemplate(
+ template,
+ inParens.map { ParameterInfo(it.name.asString(), it.isLambdaWithNoParameters()) },
+ insertLambda,
+ context.isNextElementOpenCurlyBrace()
+ )
}
}
-private class ComposeInsertHandlerForK2(val functionElement: KtNamedFunction, callType: CallType<*>) : ComposeInsertHandler(
- callType) {
+private class ComposeInsertHandlerForK2(
+ val functionElement: KtNamedFunction,
+ callType: CallType<*>
+) : ComposeInsertHandler(callType) {
@OptIn(KtAllowAnalysisOnEdt::class)
override fun configureFunctionTemplate(context: InsertionContext, template: Template) {
allowAnalysisOnEdt {
@@ -439,12 +505,18 @@
val functionSymbol = functionElement.getFunctionLikeSymbol()
val allParameters = functionSymbol.valueParameters
val requiredParameters = allParameters.filter { !it.hasDefaultValue && !it.isVararg }
- val insertLambda = allParameters.lastOrNull()?.let { valueParamSymbol ->
- isComposableFunctionParameter(valueParamSymbol) || isRequiredLambdaWithNoParameters(valueParamSymbol)
- } == true
+ val insertLambda =
+ allParameters.lastOrNull()?.let { valueParamSymbol ->
+ isComposableFunctionParameter(valueParamSymbol) ||
+ isRequiredLambdaWithNoParameters(valueParamSymbol)
+ } == true
val inParens = if (insertLambda) requiredParameters.dropLast(1) else requiredParameters
- configureFunctionTemplate(template, inParens.map { ParameterInfo(it.name.asString(), isLambdaWithNoParameters(it)) }, insertLambda,
- context.isNextElementOpenCurlyBrace())
+ configureFunctionTemplate(
+ template,
+ inParens.map { ParameterInfo(it.name.asString(), isLambdaWithNoParameters(it)) },
+ insertLambda,
+ context.isNextElementOpenCurlyBrace()
+ )
}
}
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeCompletionWeigher.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeCompletionWeigher.kt
index 59fbcd5..721a957 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeCompletionWeigher.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeCompletionWeigher.kt
@@ -32,18 +32,20 @@
/**
* Custom [CompletionWeigher] which moves Composable functions up the completion list.
*
- * It doesn't give Composable functions "absolute" priority, some weighers are hardcoded to run first: specifically one that puts prefix
- * matches above [LookupElement]s where the match is in the middle of the name. Overriding this behavior would require an extension point in
+ * It doesn't give Composable functions "absolute" priority, some weighers are hardcoded to run
+ * first: specifically one that puts prefix matches above [LookupElement]s where the match is in the
+ * middle of the name. Overriding this behavior would require an extension point in
* [org.jetbrains.kotlin.idea.completion.CompletionSession.createSorter].
*
- * See [com.intellij.codeInsight.completion.PrioritizedLookupElement] for more information on how ordering of [LookupElement]s works and how
- * to debug it.
+ * See [com.intellij.codeInsight.completion.PrioritizedLookupElement] for more information on how
+ * ordering of [LookupElement]s works and how to debug it.
*/
class ComposeCompletionWeigher : CompletionWeigher() {
override fun weigh(element: LookupElement, location: CompletionLocation): Int {
if (!location.completionParameters.isInComposeEnabledModuleAndFile()) return 0
- // Since Compose uses so many named arguments, promote them to the top. This is for a case where the user has typed something like
+ // Since Compose uses so many named arguments, promote them to the top. This is for a case where
+ // the user has typed something like
// "Button(en<caret>)", and we want to promote the completion "enabled = Boolean".
if (element.isNamedArgumentCompletion()) return 3
@@ -71,22 +73,34 @@
}
}
-/** Set of fully-qualified names of non-Composable functions that should be promoted above standard Composables in a statement. */
-private val PROMOTED_NON_COMPOSABLES_IN_STATEMENTS = setOf(
- "androidx.compose.material.MaterialTheme",
- "androidx.compose.material3.MaterialTheme",
-)
+/**
+ * Set of fully-qualified names of non-Composable functions that should be promoted above standard
+ * Composables in a statement.
+ */
+private val PROMOTED_NON_COMPOSABLES_IN_STATEMENTS =
+ setOf(
+ "androidx.compose.material.MaterialTheme",
+ "androidx.compose.material3.MaterialTheme",
+ )
-/** Set of fully-qualified names of non-Composable functions that should be promoted in a value argument. */
-private val PROMOTED_NON_COMPOSABLES_IN_ARGUMENTS = setOf(
- "androidx.compose.material.MaterialTheme",
- "androidx.compose.material3.MaterialTheme",
-)
+/**
+ * Set of fully-qualified names of non-Composable functions that should be promoted in a value
+ * argument.
+ */
+private val PROMOTED_NON_COMPOSABLES_IN_ARGUMENTS =
+ setOf(
+ "androidx.compose.material.MaterialTheme",
+ "androidx.compose.material3.MaterialTheme",
+ )
-/** Set of fully-qualified name prefixes of non-Composable functions that should be promoted in a value argument. */
-private val PROMOTED_NON_COMPOSABLE_PREFIXES_IN_ARGUMENTS = setOf(
- "androidx.compose.material.icons.",
-)
+/**
+ * Set of fully-qualified name prefixes of non-Composable functions that should be promoted in a
+ * value argument.
+ */
+private val PROMOTED_NON_COMPOSABLE_PREFIXES_IN_ARGUMENTS =
+ setOf(
+ "androidx.compose.material.icons.",
+ )
private fun LookupElement.isPromotedInStatement(): Boolean {
val fqName = (psiElement as? KtNamedDeclaration)?.fqName?.asString()
@@ -96,7 +110,7 @@
private fun LookupElement.isPromotedInArgument(): Boolean {
val fqName = (psiElement as? KtNamedDeclaration)?.fqName?.asString() ?: return false
return PROMOTED_NON_COMPOSABLES_IN_ARGUMENTS.contains(fqName) ||
- PROMOTED_NON_COMPOSABLE_PREFIXES_IN_ARGUMENTS.any { fqName.startsWith(it) }
+ PROMOTED_NON_COMPOSABLE_PREFIXES_IN_ARGUMENTS.any { fqName.startsWith(it) }
}
/** Checks if the proposed completion would insert a composable function. */
@@ -105,13 +119,22 @@
/** Checks if the proposed completion would insert a named argument. */
private fun LookupElement.isNamedArgumentCompletion() = lookupString.endsWith(" =")
-/** Checks if this completion is for a statement (where Compose views are usually called) and not part of another expression. */
+/**
+ * Checks if this completion is for a statement (where Compose views are usually called) and not
+ * part of another expression.
+ */
private fun CompletionParameters.isForStatement() =
- position is LeafPsiElement && position.node.elementType == KtTokens.IDENTIFIER && position.parent?.parent is KtBlockExpression
+ position is LeafPsiElement &&
+ position.node.elementType == KtTokens.IDENTIFIER &&
+ position.parent?.parent is KtBlockExpression
-/** Checks if this completion is for a value argument, where Compose views are usually not called. */
+/**
+ * Checks if this completion is for a value argument, where Compose views are usually not called.
+ */
private fun CompletionParameters.isForValueArgument() =
- position is LeafPsiElement && position.node.elementType == KtTokens.IDENTIFIER && position.parentOfType<KtValueArgument>() != null
+ position is LeafPsiElement &&
+ position.node.elementType == KtTokens.IDENTIFIER &&
+ position.parentOfType<KtValueArgument>() != null
/** Checks if the given completions parameters are in a Kotlin file in a Compose-enabled module. */
private fun CompletionParameters.isInComposeEnabledModuleAndFile() =
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeMaterialIconService.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeMaterialIconService.kt
index 248dca7..115015f 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeMaterialIconService.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeMaterialIconService.kt
@@ -25,22 +25,24 @@
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import java.lang.ref.SoftReference
-import org.jetbrains.annotations.VisibleForTesting
import java.util.concurrent.atomic.AtomicBoolean
import javax.swing.Icon
import javax.swing.ImageIcon
+import org.jetbrains.annotations.VisibleForTesting
-typealias IconLoader = ((MaterialVdIcons, MaterialVdIconsProvider.Status) -> Unit, Disposable) -> Unit
+typealias IconLoader =
+ ((MaterialVdIcons, MaterialVdIconsProvider.Status) -> Unit, Disposable) -> Unit
/**
* Light service providing cached Material icons for usage in the autocomplete dialog.
*
- * Icons are loaded using [MaterialVdIconsProvider], sized to 16x16 to fit autocomplete UI, and are stored using soft references so that
- * they will be discarded if there is memory pressure.
+ * Icons are loaded using [MaterialVdIconsProvider], sized to 16x16 to fit autocomplete UI, and are
+ * stored using soft references so that they will be discarded if there is memory pressure.
*/
@Service
internal class ComposeMaterialIconService
- @VisibleForTesting internal constructor(private val loadIcons: IconLoader) : Disposable {
+@VisibleForTesting
+internal constructor(private val loadIcons: IconLoader) : Disposable {
constructor() : this(ComposeMaterialIconService::callLoadMaterialVdIcons)
private var iconsWrapper: SoftReference<MaterialVdIconsWrapper> = SoftReference(null)
@@ -54,21 +56,26 @@
/**
* Gets an icon given its expected filename.
*
- * The filename should follow the idiomatic file format for Material icons of "<theme>_<iconname>_24.xml". For example, the Attachment
- * icon in the Sharp theme would have the name "sharp_attachment_24.xml".
+ * The filename should follow the idiomatic file format for Material icons of
+ * "<theme>_<iconname>_24.xml". For example, the Attachment icon in the Sharp theme would have the
+ * name "sharp_attachment_24.xml".
*/
fun getIcon(iconFileName: String): Icon? {
// Return an icon if we currently have a reference to the icon wrapper.
- iconsWrapper.get()?.let { return@getIcon it.getIcon(iconFileName) }
+ iconsWrapper.get()?.let {
+ return@getIcon it.getIcon(iconFileName)
+ }
- // Since there's no reference, go ahead and start loading icons but don't wait for it to complete.
+ // Since there's no reference, go ahead and start loading icons but don't wait for it to
+ // complete.
ensureIconsLoaded()
return null
}
/**
- * Icons are loaded using [MaterialVdIconsProvider], which will download icons if they aren't already available on disk. This method kicks
- * off the loading process, and can be used in situations when we know we might be requesting icons shortly.
+ * Icons are loaded using [MaterialVdIconsProvider], which will download icons if they aren't
+ * already available on disk. This method kicks off the loading process, and can be used in
+ * situations when we know we might be requesting icons shortly.
*/
fun ensureIconsLoaded() {
// If we have the icon wrapper, there's no need to start a new loading process.
@@ -79,7 +86,8 @@
return
}
- // Check once more for whether we have icons, on the small chance that loading completed between the above two checks.
+ // Check once more for whether we have icons, on the small chance that loading completed between
+ // the above two checks.
if (iconsWrapper.get() != null) {
iconLoadingInProgress.set(false)
return
@@ -89,8 +97,12 @@
loadIcons(this::materialVdIconsLoadedCallback, this)
}
- private fun materialVdIconsLoadedCallback(icons: MaterialVdIcons, status: MaterialVdIconsProvider.Status) {
- // Store a wrapper using returned icons. When this callback is called multiple times, each call supersedes the last and contains a
+ private fun materialVdIconsLoadedCallback(
+ icons: MaterialVdIcons,
+ status: MaterialVdIconsProvider.Status
+ ) {
+ // Store a wrapper using returned icons. When this callback is called multiple times, each call
+ // supersedes the last and contains a
// superset of its icons.
iconsWrapper = SoftReference(MaterialVdIconsWrapper(icons))
@@ -99,7 +111,8 @@
}
/**
- * Wrapper around [MaterialVdIcons] providing lookup access by icon name and resizing the icons for auto-complete.
+ * Wrapper around [MaterialVdIcons] providing lookup access by icon name and resizing the icons
+ * for auto-complete.
*/
private class MaterialVdIconsWrapper(materialVdIcons: MaterialVdIcons) {
@@ -116,9 +129,14 @@
companion object {
fun getInstance(application: Application): ComposeMaterialIconService = application.service()
- /** Call to [MaterialVdIconsProvider.loadMaterialVdIcons] wrapped in a function to allow overriding for tests. */
- private fun callLoadMaterialVdIcons(refreshUiCallback: (MaterialVdIcons, MaterialVdIconsProvider.Status) -> Unit,
- parentDisposable: Disposable) {
+ /**
+ * Call to [MaterialVdIconsProvider.loadMaterialVdIcons] wrapped in a function to allow
+ * overriding for tests.
+ */
+ private fun callLoadMaterialVdIcons(
+ refreshUiCallback: (MaterialVdIcons, MaterialVdIconsProvider.Status) -> Unit,
+ parentDisposable: Disposable
+ ) {
MaterialVdIconsProvider.loadMaterialVdIcons(refreshUiCallback, parentDisposable)
}
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeModifierCompletionContributor.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeModifierCompletionContributor.kt
index d9f522b..7a9f2c9 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeModifierCompletionContributor.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposeModifierCompletionContributor.kt
@@ -98,10 +98,11 @@
/**
* Enhances code completion for Modifier (androidx.compose.ui.Modifier)
*
- * Adds Modifier extension functions to code completion in places where modifier is expected
- * e.g. parameter of type Modifier, variable of type Modifier as it was called on Modifier.<caret>
+ * Adds Modifier extension functions to code completion in places where modifier is expected e.g.
+ * parameter of type Modifier, variable of type Modifier as it was called on Modifier.<caret>
*
- * Moves extension functions for method called on modifier [isMethodCalledOnModifier] up in the completion list.
+ * Moves extension functions for method called on modifier [isMethodCalledOnModifier] up in the
+ * completion list.
*
* @see COMPOSE_MODIFIER_FQN
*/
@@ -114,43 +115,77 @@
resultSet: CompletionResultSet,
) {
val originalPosition = parameters.position
- val extensionFunctionSymbols = getExtensionFunctionsForModifier(nameExpression, originalPosition, resultSet.prefixMatcher)
+ val extensionFunctionSymbols =
+ getExtensionFunctionsForModifier(nameExpression, originalPosition, resultSet.prefixMatcher)
ProgressManager.checkCanceled()
- val (returnsModifier, others) = extensionFunctionSymbols.partition { asFqName(it.returnType)?.asString() == COMPOSE_MODIFIER_FQN }
+ val (returnsModifier, others) =
+ extensionFunctionSymbols.partition {
+ asFqName(it.returnType)?.asString() == COMPOSE_MODIFIER_FQN
+ }
val lookupElementFactory = KotlinFirLookupElementFactory()
- val importStrategyDetector = ImportStrategyDetector(nameExpression.containingKtFile, nameExpression.project)
+ val importStrategyDetector =
+ ImportStrategyDetector(nameExpression.containingKtFile, nameExpression.project)
- val isNewModifier = !isMethodCalledOnImportedModifier && originalPosition.parentOfType<KtDotQualifiedExpression>() == null
- //Prioritise functions that return Modifier over other extension function.
+ val isNewModifier =
+ !isMethodCalledOnImportedModifier &&
+ originalPosition.parentOfType<KtDotQualifiedExpression>() == null
+ // Prioritise functions that return Modifier over other extension function.
resultSet.addAllElements(
- toLookupElements(returnsModifier, lookupElementFactory, importStrategyDetector, 2.0, insertModifier = isNewModifier))
- //If user didn't type Modifier don't suggest extensions that doesn't return Modifier.
+ toLookupElements(
+ returnsModifier,
+ lookupElementFactory,
+ importStrategyDetector,
+ 2.0,
+ insertModifier = isNewModifier
+ )
+ )
+ // If user didn't type Modifier don't suggest extensions that doesn't return Modifier.
if (isMethodCalledOnImportedModifier) {
- resultSet.addAllElements(toLookupElements(others, lookupElementFactory, importStrategyDetector, 0.0, insertModifier = isNewModifier))
+ resultSet.addAllElements(
+ toLookupElements(
+ others,
+ lookupElementFactory,
+ importStrategyDetector,
+ 0.0,
+ insertModifier = isNewModifier
+ )
+ )
}
ProgressManager.checkCanceled()
- //If method is called on modifier [KotlinCompletionContributor] will add extensions function one more time, we need to filter them out.
+ // If method is called on modifier [KotlinCompletionContributor] will add extensions function
+ // one more time, we need to filter them out.
if (isMethodCalledOnImportedModifier) {
- val extensionFunctionsNames = extensionFunctionSymbols.mapNotNull { (it as? KtNamedSymbol)?.name?.asString() }.toSet()
+ val extensionFunctionsNames =
+ extensionFunctionSymbols.mapNotNull { (it as? KtNamedSymbol)?.name?.asString() }.toSet()
resultSet.runRemainingContributors(parameters) { completionResult ->
- consumerCompletionResultFromRemainingContributor(completionResult, extensionFunctionsNames, originalPosition, resultSet)
+ consumerCompletionResultFromRemainingContributor(
+ completionResult,
+ extensionFunctionsNames,
+ originalPosition,
+ resultSet
+ )
}
}
}
- override fun fillCompletionVariants(parameters: CompletionParameters, resultSet: CompletionResultSet) {
+ override fun fillCompletionVariants(
+ parameters: CompletionParameters,
+ resultSet: CompletionResultSet
+ ) {
val element = parameters.position
if (!isComposeEnabled(element) || parameters.originalFile !is KtFile) {
return
}
- // It says "on imported" because only in that case we are able to resolve that it called on Modifier.
+ // It says "on imported" because only in that case we are able to resolve that it called on
+ // Modifier.
val isMethodCalledOnImportedModifier = element.isMethodCalledOnModifier()
ProgressManager.checkCanceled()
- val isModifierType = isMethodCalledOnImportedModifier || element.isModifierArgument || element.isModifierProperty
+ val isModifierType =
+ isMethodCalledOnImportedModifier || element.isModifierArgument || element.isModifierProperty
if (!isModifierType) return
ProgressManager.checkCanceled()
@@ -159,33 +194,52 @@
if (isK2Plugin()) {
analyze(nameExpression) {
- fillCompletionVariants(parameters, nameExpression, isMethodCalledOnImportedModifier, resultSet)
+ fillCompletionVariants(
+ parameters,
+ nameExpression,
+ isMethodCalledOnImportedModifier,
+ resultSet
+ )
}
return
}
// For K1
- val extensionFunctions = getExtensionFunctionsForModifier(nameExpression, element, resultSet.prefixMatcher)
+ val extensionFunctions =
+ getExtensionFunctionsForModifier(nameExpression, element, resultSet.prefixMatcher)
ProgressManager.checkCanceled()
- val (returnsModifier, others) = extensionFunctions.partition { it.returnType?.fqName?.asString() == COMPOSE_MODIFIER_FQN }
- val lookupElementFactory = createLookupElementFactory(parameters.editor, nameExpression, parameters)
+ val (returnsModifier, others) =
+ extensionFunctions.partition { it.returnType?.fqName?.asString() == COMPOSE_MODIFIER_FQN }
+ val lookupElementFactory =
+ createLookupElementFactory(parameters.editor, nameExpression, parameters)
- val isNewModifier = !isMethodCalledOnImportedModifier && element.parentOfType<KtDotQualifiedExpression>() == null
- //Prioritise functions that return Modifier over other extension function.
- resultSet.addAllElements(returnsModifier.toLookupElements(lookupElementFactory, 2.0, insertModifier = isNewModifier))
- //If user didn't type Modifier don't suggest extensions that doesn't return Modifier.
+ val isNewModifier =
+ !isMethodCalledOnImportedModifier && element.parentOfType<KtDotQualifiedExpression>() == null
+ // Prioritise functions that return Modifier over other extension function.
+ resultSet.addAllElements(
+ returnsModifier.toLookupElements(lookupElementFactory, 2.0, insertModifier = isNewModifier)
+ )
+ // If user didn't type Modifier don't suggest extensions that doesn't return Modifier.
if (isMethodCalledOnImportedModifier) {
- resultSet.addAllElements(others.toLookupElements(lookupElementFactory, 0.0, insertModifier = isNewModifier))
+ resultSet.addAllElements(
+ others.toLookupElements(lookupElementFactory, 0.0, insertModifier = isNewModifier)
+ )
}
ProgressManager.checkCanceled()
- //If method is called on modifier [KotlinCompletionContributor] will add extensions function one more time, we need to filter them out.
+ // If method is called on modifier [KotlinCompletionContributor] will add extensions function
+ // one more time, we need to filter them out.
if (isMethodCalledOnImportedModifier) {
val extensionFunctionsNames = extensionFunctions.map { it.name.asString() }.toSet()
resultSet.runRemainingContributors(parameters) { completionResult ->
- consumerCompletionResultFromRemainingContributor(completionResult, extensionFunctionsNames, element, resultSet)
+ consumerCompletionResultFromRemainingContributor(
+ completionResult,
+ extensionFunctionsNames,
+ element,
+ resultSet
+ )
}
}
}
@@ -198,13 +252,16 @@
resultSet: CompletionResultSet
) {
val suggestedKtFunction = completionResult.lookupElement.psiElement as? KtFunction
- val alreadyAddedResult = suggestedKtFunction?.name?.let { extensionFunctionsNames.contains(it) } == true
+ val alreadyAddedResult =
+ suggestedKtFunction?.name?.let { extensionFunctionsNames.contains(it) } == true
- // Only call [isVisibleFromCompletionPosition] if the function is on an internal object, since that method is heavier.
- // TODO (b/280093734): Remove this workaround once https://youtrack.jetbrains.com/issue/KTIJ-23360 is resolved.
+ // Only call [isVisibleFromCompletionPosition] if the function is on an internal object, since
+ // that method is heavier.
+ // TODO (b/280093734): Remove this workaround once
+ // https://youtrack.jetbrains.com/issue/KTIJ-23360 is resolved.
val isOnInvisibleObject =
- suggestedKtFunction?.containingClassOrObject?.hasModifier(KtTokens.INTERNAL_KEYWORD) == true &&
- !suggestedKtFunction.isVisibleFromCompletionPosition(completionPositionElement)
+ suggestedKtFunction?.containingClassOrObject?.hasModifier(KtTokens.INTERNAL_KEYWORD) ==
+ true && !suggestedKtFunction.isVisibleFromCompletionPosition(completionPositionElement)
if (!alreadyAddedResult && !isOnInvisibleObject) {
resultSet.passResult(completionResult)
@@ -212,25 +269,33 @@
}
/**
- * Checks if the given function is visible from the completion position. Workaround for b/279049842 and b/252977033.
+ * Checks if the given function is visible from the completion position. Workaround for
+ * b/279049842 and b/252977033.
*
- * Some suggestions for Modifier extensions are extension functions that live on internal objects in Compose libraries. These aren't legal
- * to be directly referenced from users' code, but the Kotlin plugin suggests them anyway. This is tracked by
+ * Some suggestions for Modifier extensions are extension functions that live on internal objects
+ * in Compose libraries. These aren't legal to be directly referenced from users' code, but the
+ * Kotlin plugin suggests them anyway. This is tracked by
* https://youtrack.jetbrains.com/issue/KTIJ-23360.
*
- * In the meantime, this method checks whether the containing class/object of the function is visible from the completion position. If
- * not, then it will be filtered out from results.
+ * In the meantime, this method checks whether the containing class/object of the function is
+ * visible from the completion position. If not, then it will be filtered out from results.
*/
private fun KtFunction.isVisibleFromCompletionPosition(completionPosition: PsiElement): Boolean {
- // This is Compose, we should always be completing in a KtFile. If not, let's just assume things are visible so as not to muck with
+ // This is Compose, we should always be completing in a KtFile. If not, let's just assume things
+ // are visible so as not to muck with
// whatever behavior is happening.
val ktFile = completionPosition.containingFile as? KtFile ?: return true
val elementToAnalyze = this.containingClassOrObject ?: this
analyze(elementToAnalyze) {
- val symbolWithVisibility = elementToAnalyze.getSymbol() as? KtSymbolWithVisibility ?: return true
+ val symbolWithVisibility =
+ elementToAnalyze.getSymbol() as? KtSymbolWithVisibility ?: return true
- return isVisible(symbolWithVisibility, useSiteFile = ktFile.getFileSymbol(), position = completionPosition)
+ return isVisible(
+ symbolWithVisibility,
+ useSiteFile = ktFile.getFileSymbol(),
+ position = completionPosition
+ )
}
}
@@ -239,9 +304,11 @@
weight: Double,
insertModifier: Boolean
) = flatMap { descriptor ->
- lookupElementFactory.createStandardLookupElementsForDescriptor(descriptor, useReceiverTypes = true).map {
- PrioritizedLookupElement.withPriority(ModifierLookupElement(it, insertModifier), weight)
- }
+ lookupElementFactory
+ .createStandardLookupElementsForDescriptor(descriptor, useReceiverTypes = true)
+ .map {
+ PrioritizedLookupElement.withPriority(ModifierLookupElement(it, insertModifier), weight)
+ }
}
@Suppress("UnstableApiUsage")
@@ -251,16 +318,20 @@
importStrategyDetector: ImportStrategyDetector,
weight: Double,
insertModifier: Boolean
- ) = functionSymbols.map { symbol ->
- with(lookupElementFactory) {
- val lookupElement = createLookupElement(symbol as KtNamedSymbol, importStrategyDetector)
- PrioritizedLookupElement.withPriority(ModifierLookupElement(lookupElement, insertModifier), weight)
+ ) =
+ functionSymbols.map { symbol ->
+ with(lookupElementFactory) {
+ val lookupElement = createLookupElement(symbol as KtNamedSymbol, importStrategyDetector)
+ PrioritizedLookupElement.withPriority(
+ ModifierLookupElement(lookupElement, insertModifier),
+ weight
+ )
+ }
}
- }
/**
- * Creates LookupElementFactory that is similar to the one kotlin-plugin uses during completion session.
- * Code partially copied from [CompletionSession].
+ * Creates LookupElementFactory that is similar to the one kotlin-plugin uses during completion
+ * session. Code partially copied from [CompletionSession].
*/
private fun createLookupElementFactory(
editor: Editor,
@@ -274,20 +345,30 @@
val moduleDescriptor = resolutionFacade.moduleDescriptor
val callTypeAndReceiver = CallTypeAndReceiver.detect(nameExpression)
- val receiverTypes = callTypeAndReceiver.receiverTypesWithIndex(
- bindingContext, nameExpression, moduleDescriptor, resolutionFacade,
- stableSmartCastsOnly = true, /* we don't include smart cast receiver types for "unstable" receiver value to mark members grayed */
- withImplicitReceiversWhenExplicitPresent = true
- )
+ val receiverTypes =
+ callTypeAndReceiver.receiverTypesWithIndex(
+ bindingContext,
+ nameExpression,
+ moduleDescriptor,
+ resolutionFacade,
+ stableSmartCastsOnly =
+ true, /* we don't include smart cast receiver types for "unstable" receiver value to mark members grayed */
+ withImplicitReceiversWhenExplicitPresent = true
+ )
- val inDescriptor = nameExpression.getResolutionScope(bindingContext, resolutionFacade).ownerDescriptor
+ val inDescriptor =
+ nameExpression.getResolutionScope(bindingContext, resolutionFacade).ownerDescriptor
val insertHandler = InsertHandlerProvider(CallType.DOT, parameters.editor, ::emptyList)
val basicLookupElementFactory = BasicLookupElementFactory(nameExpression.project, insertHandler)
return LookupElementFactory(
- basicLookupElementFactory, editor, receiverTypes,
- callTypeAndReceiver.callType, inDescriptor, CollectRequiredTypesContextVariablesProvider()
+ basicLookupElementFactory,
+ editor,
+ receiverTypes,
+ callTypeAndReceiver.callType,
+ inDescriptor,
+ CollectRequiredTypesContextVariablesProvider()
)
}
@@ -297,8 +378,13 @@
private fun createNameExpression(originalElement: PsiElement): KtSimpleNameExpression {
val originalFile = originalElement.containingFile as KtFile
- val file = KtPsiFactory.contextual(originalFile).createFile("temp.kt", "val x = $COMPOSE_MODIFIER_FQN.call")
- return file.getChildOfType<KtProperty>()!!.getChildOfType<KtDotQualifiedExpression>()!!.lastChild as KtSimpleNameExpression
+ val file =
+ KtPsiFactory.contextual(originalFile)
+ .createFile("temp.kt", "val x = $COMPOSE_MODIFIER_FQN.call")
+ return file
+ .getChildOfType<KtProperty>()!!
+ .getChildOfType<KtDotQualifiedExpression>()!!
+ .lastChild as KtSimpleNameExpression
}
private fun KtAnalysisSession.findReceiverSymbol(element: KtElement): KtClassOrObjectSymbol? {
@@ -306,7 +392,8 @@
when (element) {
is KtNameReferenceExpression -> element
else -> element.childrenOfType<KtNameReferenceExpression>().singleOrNull()
- } ?: return null
+ }
+ ?: return null
val reference = namedReferenceExpression.mainReference as? KtSimpleNameReference ?: return null
return reference.resolveToSymbol() as? KtClassOrObjectSymbol
}
@@ -321,11 +408,20 @@
val searchScope = getResolveScope(file)
val callTypeAndReceiver = CallTypeAndReceiver.detect(nameExpression)
val receiverSymbol = callTypeAndReceiver.receiver?.let { findReceiverSymbol(it) }
- return HLIndexHelper(nameExpression.project, searchScope).getTopLevelExtensions(
- { name -> prefixMatcher.prefixMatches(name.asString()) }, setOfNotNull(
- receiverSymbol?.classIdIfNonLocal?.shortClassName?.identifier)).mapNotNull { it.getSymbol() as? KtCallableSymbol }.filter {
- isVisible(it as KtSymbolWithVisibility, fileSymbol, callTypeAndReceiver.receiver as? KtExpression, originalPosition)
- }
+ return HLIndexHelper(nameExpression.project, searchScope)
+ .getTopLevelExtensions(
+ { name -> prefixMatcher.prefixMatches(name.asString()) },
+ setOfNotNull(receiverSymbol?.classIdIfNonLocal?.shortClassName?.identifier)
+ )
+ .mapNotNull { it.getSymbol() as? KtCallableSymbol }
+ .filter {
+ isVisible(
+ it as KtSymbolWithVisibility,
+ fileSymbol,
+ callTypeAndReceiver.receiver as? KtExpression,
+ originalPosition
+ )
+ }
}
private fun getExtensionFunctionsForModifier(
@@ -341,7 +437,12 @@
val callTypeAndReceiver = CallTypeAndReceiver.detect(nameExpression)
fun isVisible(descriptor: DeclarationDescriptor): Boolean {
if (descriptor is DeclarationDescriptorWithVisibility) {
- return descriptor.isVisible(originalPosition, callTypeAndReceiver.receiver as? KtExpression, bindingContext, resolutionFacade)
+ return descriptor.isVisible(
+ originalPosition,
+ callTypeAndReceiver.receiver as? KtExpression,
+ bindingContext,
+ resolutionFacade
+ )
}
return true
@@ -350,7 +451,13 @@
val indicesHelper = KotlinIndicesHelper(resolutionFacade, searchScope, ::isVisible, file = file)
val nameFilter = { name: String -> prefixMatcher.prefixMatches(name) }
- return indicesHelper.getCallableTopLevelExtensions(callTypeAndReceiver, nameExpression, bindingContext, null, nameFilter)
+ return indicesHelper.getCallableTopLevelExtensions(
+ callTypeAndReceiver,
+ nameExpression,
+ bindingContext,
+ null,
+ nameFilter
+ )
}
private val PsiElement.isModifierProperty: Boolean
@@ -362,10 +469,13 @@
private val PsiElement.isModifierArgument: Boolean
get() {
- val argument = contextOfType<KtValueArgument>().takeIf { it !is KtLambdaArgument } ?: return false
+ val argument =
+ contextOfType<KtValueArgument>().takeIf { it !is KtLambdaArgument } ?: return false
val callExpression = argument.parentOfType<KtCallElement>() ?: return false
- val callee = callExpression.calleeExpression?.mainReference?.resolve() as? KtNamedFunction ?: return false
+ val callee =
+ callExpression.calleeExpression?.mainReference?.resolve() as? KtNamedFunction
+ ?: return false
val argumentTypeFqName = argument.matchingParamTypeFqName(callee)
@@ -378,21 +488,23 @@
* Returns true for Modifier.align().%this%, myModifier.%this%, Modifier.%this%.
*/
private fun PsiElement.isMethodCalledOnModifier(): Boolean {
- val elementOnWhichMethodCalled: KtExpression = (parent as? KtNameReferenceExpression)?.getReceiverExpression() ?: return false
+ val elementOnWhichMethodCalled: KtExpression =
+ (parent as? KtNameReferenceExpression)?.getReceiverExpression() ?: return false
// Case Modifier.align().%this%, modifier.%this%
- val fqName = elementOnWhichMethodCalled.callReturnTypeFqName() ?:
- // Case Modifier.%this%
- ((elementOnWhichMethodCalled as? KtNameReferenceExpression)?.resolve() as? KtClass)?.fqName
+ val fqName =
+ elementOnWhichMethodCalled.callReturnTypeFqName()
+ ?:
+ // Case Modifier.%this%
+ ((elementOnWhichMethodCalled as? KtNameReferenceExpression)?.resolve() as? KtClass)?.fqName
return fqName?.asString() == COMPOSE_MODIFIER_FQN
}
/**
- * Inserts "Modifier." before [delegate] and imports [ComposeModifierCompletionContributor.modifierFqName] if it's not imported.
+ * Inserts "Modifier." before [delegate] and imports
+ * [ComposeModifierCompletionContributor.modifierFqName] if it's not imported.
*/
- private class ModifierLookupElement(
- delegate: LookupElement,
- val insertModifier: Boolean
- ) : LookupElementDecorator<LookupElement>(delegate) {
+ private class ModifierLookupElement(delegate: LookupElement, val insertModifier: Boolean) :
+ LookupElementDecorator<LookupElement>(delegate) {
companion object {
private const val callOnModifierObject = "Modifier."
}
@@ -420,10 +532,14 @@
override fun handleInsert(context: InsertionContext) {
val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
- // Compose plugin inserts Modifier if completion character is '\n', doesn't happened with '\t'. Looks like a bug.
+ // Compose plugin inserts Modifier if completion character is '\n', doesn't happened with
+ // '\t'. Looks like a bug.
if (insertModifier && context.completionChar != '\n') {
context.document.insertString(context.startOffset, callOnModifierObject)
- context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, context.startOffset + callOnModifierObject.length)
+ context.offsetMap.addOffset(
+ CompletionInitializationContext.START_OFFSET,
+ context.startOffset + callOnModifierObject.length
+ )
psiDocumentManager.commitAllDocuments()
psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.document)
}
@@ -431,8 +547,11 @@
if (isK2Plugin()) {
ktFile.addImport(FqName(COMPOSE_MODIFIER_FQN))
} else {
- val modifierDescriptor = ktFile.resolveImportReference(FqName(COMPOSE_MODIFIER_FQN)).singleOrNull()
- modifierDescriptor?.let { ImportInsertHelper.getInstance(context.project).importDescriptor(ktFile, it) }
+ val modifierDescriptor =
+ ktFile.resolveImportReference(FqName(COMPOSE_MODIFIER_FQN)).singleOrNull()
+ modifierDescriptor?.let {
+ ImportInsertHelper.getInstance(context.project).importDescriptor(ktFile, it)
+ }
}
psiDocumentManager.commitAllDocuments()
psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.document)
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposePositioningCompletionContributor.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposePositioningCompletionContributor.kt
index 785c970..1bbd81b 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposePositioningCompletionContributor.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/ComposePositioningCompletionContributor.kt
@@ -58,20 +58,26 @@
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
/**
- * Represents a class in the Compose library containing Alignment or Arrangement properties which might be suggested as completions.
+ * Represents a class in the Compose library containing Alignment or Arrangement properties which
+ * might be suggested as completions.
*/
private data class ClassWithDeclarationsToSuggest(
/** Package on which this class resides. */
private val packageName: String,
- /** Short(er) name of the class containing properties that can be suggested. This may contain multiple dot-separated pieces, and is
- * intended to be concatenated with [packageName] to get the interface's fully-qualified name.
+ /**
+ * Short(er) name of the class containing properties that can be suggested. This may contain
+ * multiple dot-separated pieces, and is intended to be concatenated with [packageName] to get the
+ * interface's fully-qualified name.
*/
private val classShortName: String,
- /** Short name of the class to be imported when a suggestion is made. This differs from [classShortName] in the case of Companions. */
+ /**
+ * Short name of the class to be imported when a suggestion is made. This differs from
+ * [classShortName] in the case of Companions.
+ */
private val classShortNameToImport: String = classShortName,
/**
- * Prefix applied to the property when completing. This is most often the same as [classShortName], but may contain nested classes (e.g.
- * "Arrangement.Absolute").
+ * Prefix applied to the property when completing. This is most often the same as
+ * [classShortName], but may contain nested classes (e.g. "Arrangement.Absolute").
*/
private val propertyCompletionPrefix: String = classShortName,
) {
@@ -82,13 +88,18 @@
/**
* Returns [LookupElement]s for the given [PsiElement].
*
- * @param typeToSuggest Fully-qualified name of the type required for suggested properties.
- * @typeText Display text of the type to be used when rendering the [LookupElement]/
+ * @param typeToSuggest Fully-qualified name of the type required for suggested
+ * properties. @typeText Display text of the type to be used when rendering the [LookupElement]/
*/
- fun getLookupElements(elementToComplete: PsiElement, typeToSuggest: String, typeText: String): List<LookupElement> {
+ fun getLookupElements(
+ elementToComplete: PsiElement,
+ typeToSuggest: String,
+ typeText: String
+ ): List<LookupElement> {
val project = elementToComplete.project
- // It's necessary to ensure these are distinct, because in some circumstances there may be multiple versions of the class returned when
+ // It's necessary to ensure these are distinct, because in some circumstances there may be
+ // multiple versions of the class returned when
// searching with 'allScope'.
return KotlinFullClassNameIndex.get(classFqName, project, project.allScope())
.flatMap { it.getPropertiesByType(project)[typeToSuggest] ?: emptyList() }
@@ -96,44 +107,59 @@
.mapNotNull { createLookupElement(it, elementToComplete, typeText) }
}
- private fun createLookupElement(elementToSuggest: KtDeclaration, elementToComplete: PsiElement, typeText: String): LookupElement? {
+ private fun createLookupElement(
+ elementToSuggest: KtDeclaration,
+ elementToComplete: PsiElement,
+ typeText: String
+ ): LookupElement? {
val lookupStringWithClass = "${propertyCompletionPrefix}.${elementToSuggest.name}"
val presentableTailText = " ($packageName)"
- // If the user's already typed part of a dot expression, then not all the given suggestions from this class will be applicable.
- // For example, if the user has typed "Arrangement.Absolute.L", we want to exclude "Arrangement.Left".
+ // If the user's already typed part of a dot expression, then not all the given suggestions from
+ // this class will be applicable.
+ // For example, if the user has typed "Arrangement.Absolute.L", we want to exclude
+ // "Arrangement.Left".
val alreadyCompletedPrefix =
- elementToComplete.parentOfType<KtDotQualifiedExpression>()?.receiverExpression?.normalizedExpressionText()?.let { "$it." } ?: ""
+ elementToComplete
+ .parentOfType<KtDotQualifiedExpression>()
+ ?.receiverExpression
+ ?.normalizedExpressionText()
+ ?.let { "$it." }
+ ?: ""
if (!lookupStringWithClass.startsWith(alreadyCompletedPrefix)) return null
- // When completing one of these properties with a dot expression, we want the lookup string to exclude any full piece that's already
- // been typed. For example, if the suggestion is "Arrangement.Absolute.Left" and the user has types "Arrangement.Ab", we want the
+ // When completing one of these properties with a dot expression, we want the lookup string to
+ // exclude any full piece that's already
+ // been typed. For example, if the suggestion is "Arrangement.Absolute.Left" and the user has
+ // types "Arrangement.Ab", we want the
// resulting lookup string to be "Absolute.Left".
val mainLookupString = lookupStringWithClass.removePrefix(alreadyCompletedPrefix)
- val builder = LookupElementBuilder
- .create(elementToSuggest, mainLookupString)
- .bold()
- .withTailText(presentableTailText, true)
- .withTypeText(typeText)
- .withInsertHandler lambda@{ context, _ ->
- // Add import in addition to filling in the completion.
- val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
- val ktFile = context.file as KtFile
- if (isK2Plugin()) {
- ktFile.addImport(FqName(classToImport))
- psiDocumentManager.commitAllDocuments()
- psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.document)
- } else {
- val modifierDescriptor = ktFile.resolveImportReference(FqName(classToImport)).singleOrNull()
- if (modifierDescriptor != null) {
- ImportInsertHelper.getInstance(context.project).importDescriptor(ktFile, modifierDescriptor)
+ val builder =
+ LookupElementBuilder.create(elementToSuggest, mainLookupString)
+ .bold()
+ .withTailText(presentableTailText, true)
+ .withTypeText(typeText)
+ .withInsertHandler lambda@{ context, _ ->
+ // Add import in addition to filling in the completion.
+ val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
+ val ktFile = context.file as KtFile
+ if (isK2Plugin()) {
+ ktFile.addImport(FqName(classToImport))
psiDocumentManager.commitAllDocuments()
psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.document)
+ } else {
+ val modifierDescriptor =
+ ktFile.resolveImportReference(FqName(classToImport)).singleOrNull()
+ if (modifierDescriptor != null) {
+ ImportInsertHelper.getInstance(context.project)
+ .importDescriptor(ktFile, modifierDescriptor)
+ psiDocumentManager.commitAllDocuments()
+ psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.document)
+ }
}
}
- }
return object : LookupElementDecorator<LookupElement>(builder) {
override fun renderElement(presentation: LookupElementPresentation) {
@@ -144,25 +170,32 @@
}
companion object {
- /** Gets a list of this class's properties grouped by their fully-qualified type name. This result is cached for fast retrieval. */
- private fun KtClassOrObject.getPropertiesByType(project: Project): Map<String, List<KtProperty>> {
+ /**
+ * Gets a list of this class's properties grouped by their fully-qualified type name. This
+ * result is cached for fast retrieval.
+ */
+ private fun KtClassOrObject.getPropertiesByType(
+ project: Project
+ ): Map<String, List<KtProperty>> {
return CachedValuesManager.getManager(project).getCachedValue(this) {
- val result = declarations
- .filterIsInstance<KtProperty>()
- .mapNotNull { property ->
- property.returnTypeFqName()?.asString()?.let { Pair(it, property) }
- }
- .groupBy({ it.first }, { it.second })
+ val result =
+ declarations
+ .filterIsInstance<KtProperty>()
+ .mapNotNull { property ->
+ property.returnTypeFqName()?.asString()?.let { Pair(it, property) }
+ }
+ .groupBy({ it.first }, { it.second })
CachedValueProvider.Result.create(result, this)
}
}
/**
- * Given a dot-qualified expression, returns a normalized form of the name. This removes inconsistencies that may be introduced by
- * whitespace within the name; so the expression "com . foo .bar" will be reduced to "com.foo.bar".
+ * Given a dot-qualified expression, returns a normalized form of the name. This removes
+ * inconsistencies that may be introduced by whitespace within the name; so the expression "com
+ * . foo .bar" will be reduced to "com.foo.bar".
*/
private fun KtExpression.normalizedExpressionText(): String? {
- return when(this) {
+ return when (this) {
is KtDotQualifiedExpression -> {
val leftSide = receiverExpression.normalizedExpressionText() ?: return null
val rightSide = selectorExpression?.normalizedExpressionText() ?: return null
@@ -180,38 +213,49 @@
/** Package on which this interface resides. */
private val packageName: String,
/**
- * Short(er) name of the interface. This may contain multiple dot-separated pieces, and is intended to be concatenated with [packageName]
- * to get the interface's fully-qualified name.
+ * Short(er) name of the interface. This may contain multiple dot-separated pieces, and is
+ * intended to be concatenated with [packageName] to get the interface's fully-qualified name.
*/
private val interfaceName: String,
- /** A list of classes on which to search for properties implementing this interface, which can be used for suggestions. */
+ /**
+ * A list of classes on which to search for properties implementing this interface, which can be
+ * used for suggestions.
+ */
private val suggestedCompletionPropertyClasses: List<ClassWithDeclarationsToSuggest>,
- /** An additional type that is allowed for suggestions in addition to [interfaceName]. The type must reside on the same [packageName]. */
+ /**
+ * An additional type that is allowed for suggestions in addition to [interfaceName]. The type
+ * must reside on the same [packageName].
+ */
private val additionalTypeToSuggest: String? = null,
/**
- * Collection of weights to be used when ranking suggestions. The key is a short type name residing on [packageName], corresponding to one
- * of the positioning interfaces being handled. The value is a simple priority: larger values result in a higher position in the
- * completion list. This value is added to any weight in [weightsByParentClass].
+ * Collection of weights to be used when ranking suggestions. The key is a short type name
+ * residing on [packageName], corresponding to one of the positioning interfaces being handled.
+ * The value is a simple priority: larger values result in a higher position in the completion
+ * list. This value is added to any weight in [weightsByParentClass].
*/
private val weightsByType: Map<String, Int>,
/**
- * Collection of weights to be used when ranking suggestions. The key is a short type name residing on [packageName], corresponding to the
- * class on which a suggested property is defined. The value is a simple priority: larger values result in a higher position in the
- * completion list. This value is added to any weight in [weightsByType].
+ * Collection of weights to be used when ranking suggestions. The key is a short type name
+ * residing on [packageName], corresponding to the class on which a suggested property is defined.
+ * The value is a simple priority: larger values result in a higher position in the completion
+ * list. This value is added to any weight in [weightsByType].
*/
private val weightsByParentClass: Map<String, Int>,
) {
val interfaceFqName = "$packageName.$interfaceName"
- private val weightsByFullyQualifiedType = weightsByType.mapKeys { (key, _) -> "$packageName.$key" }
- private val weightsByFullyQualifiedParentClass = weightsByParentClass.mapKeys { (key, _) -> "$packageName.$key" }
+ private val weightsByFullyQualifiedType =
+ weightsByType.mapKeys { (key, _) -> "$packageName.$key" }
+ private val weightsByFullyQualifiedParentClass =
+ weightsByParentClass.mapKeys { (key, _) -> "$packageName.$key" }
/**
* A list of types that are allowed for suggestions.
*
- * The first [String] in this [Pair] represents the fully-qualified name of the type. The second [String] in this [Pair] represents a
- * shorter version of the type that can be displayed on the right side of the auto-completion dialog.
+ * The first [String] in this [Pair] represents the fully-qualified name of the type. The second
+ * [String] in this [Pair] represents a shorter version of the type that can be displayed on the
+ * right side of the auto-completion dialog.
*/
private val typesToSuggest: List<Pair<String, String>> = buildList {
add("$packageName.$interfaceName" to interfaceName)
@@ -234,8 +278,10 @@
val lookupElementTypeName = (psiElement as? KtDeclaration)?.returnTypeFqName()?.asString()
val typeWeight = lookupElementTypeName?.let { weightsByFullyQualifiedType[it] } ?: 0
- val lookupElementParentClassName = (psiElement as? KtDeclaration)?.containingClassOrObject?.fqName?.asString()
- val containingClassWeight = lookupElementParentClassName?.let { weightsByFullyQualifiedParentClass[it] } ?: 0
+ val lookupElementParentClassName =
+ (psiElement as? KtDeclaration)?.containingClassOrObject?.fqName?.asString()
+ val containingClassWeight =
+ lookupElementParentClassName?.let { weightsByFullyQualifiedParentClass[it] } ?: 0
return typeWeight + containingClassWeight
}
@@ -243,8 +289,10 @@
/** Returns an applicable [PositioningInterface] for the given [PsiElement] if one exists. */
fun forCompletionElement(psiElement: PsiElement): PositioningInterface? {
- // Arrangement and Alignment completions are handled when completing arguments and properties only.
- val elementToCompleteTypeFqName = psiElement.argumentTypeFqName ?: psiElement.propertyTypeFqName ?: return null
+ // Arrangement and Alignment completions are handled when completing arguments and properties
+ // only.
+ val elementToCompleteTypeFqName =
+ psiElement.argumentTypeFqName ?: psiElement.propertyTypeFqName ?: return null
return VALUES[elementToCompleteTypeFqName]
}
@@ -256,10 +304,13 @@
private val PsiElement.argumentTypeFqName: String?
get() {
- val argument = contextOfType<KtValueArgument>().takeIf { it !is KtLambdaArgument } ?: return null
+ val argument =
+ contextOfType<KtValueArgument>().takeIf { it !is KtLambdaArgument } ?: return null
val callExpression = argument.parentOfType<KtCallElement>() ?: return null
- val callee = callExpression.calleeExpression?.mainReference?.resolve() as? KtNamedFunction ?: return null
+ val callee =
+ callExpression.calleeExpression?.mainReference?.resolve() as? KtNamedFunction
+ ?: return null
val argumentTypeFqName = argument.matchingParamTypeFqName(callee)
@@ -269,144 +320,169 @@
private const val ALIGNMENT_PACKAGE = "androidx.compose.ui"
private const val ARRANGEMENT_PACKAGE = "androidx.compose.foundation.layout"
- private val ALIGNMENT_CLASSES_FOR_SUGGESTIONS = listOf(
- ClassWithDeclarationsToSuggest(
- packageName = ALIGNMENT_PACKAGE,
- classShortName = "Alignment.Companion",
- classShortNameToImport = "Alignment",
- propertyCompletionPrefix = "Alignment",
- ),
- ClassWithDeclarationsToSuggest(
- packageName = ALIGNMENT_PACKAGE,
- classShortName = "AbsoluteAlignment",
- ),
- )
+ private val ALIGNMENT_CLASSES_FOR_SUGGESTIONS =
+ listOf(
+ ClassWithDeclarationsToSuggest(
+ packageName = ALIGNMENT_PACKAGE,
+ classShortName = "Alignment.Companion",
+ classShortNameToImport = "Alignment",
+ propertyCompletionPrefix = "Alignment",
+ ),
+ ClassWithDeclarationsToSuggest(
+ packageName = ALIGNMENT_PACKAGE,
+ classShortName = "AbsoluteAlignment",
+ ),
+ )
- private val ARRANGEMENT_CLASSES_FOR_SUGGESTIONS = listOf(
- ClassWithDeclarationsToSuggest(
- packageName = ARRANGEMENT_PACKAGE,
- classShortName = "Arrangement",
- ),
- ClassWithDeclarationsToSuggest(
- packageName = ARRANGEMENT_PACKAGE,
- classShortName = "Arrangement.Absolute",
- classShortNameToImport = "Arrangement",
- ),
- )
+ private val ARRANGEMENT_CLASSES_FOR_SUGGESTIONS =
+ listOf(
+ ClassWithDeclarationsToSuggest(
+ packageName = ARRANGEMENT_PACKAGE,
+ classShortName = "Arrangement",
+ ),
+ ClassWithDeclarationsToSuggest(
+ packageName = ARRANGEMENT_PACKAGE,
+ classShortName = "Arrangement.Absolute",
+ classShortNameToImport = "Arrangement",
+ ),
+ )
- private val VALUES = listOf(
- PositioningInterface(
- packageName = ALIGNMENT_PACKAGE,
- interfaceName = "Alignment",
- ALIGNMENT_CLASSES_FOR_SUGGESTIONS,
- weightsByType = mapOf(
- "Alignment" to 10,
- "Alignment.Horizontal" to -10,
- "Alignment.Vertical" to -10,
- ),
- weightsByParentClass = mapOf(
- "Alignment.Companion" to 2,
- "Alignment" to 2,
- "AbsoluteAlignment" to 1,
- ),
- ),
- PositioningInterface(
- packageName = ALIGNMENT_PACKAGE,
- interfaceName = "Alignment.Horizontal",
- ALIGNMENT_CLASSES_FOR_SUGGESTIONS,
- weightsByType = mapOf(
- "Alignment.Horizontal" to 10,
- "Alignment" to -10,
- "Alignment.Vertical" to -10,
- ),
- weightsByParentClass = mapOf(
- "Alignment.Companion" to 2,
- "Alignment" to 2,
- "AbsoluteAlignment" to 1,
- ),
- ),
- PositioningInterface(
- packageName = ALIGNMENT_PACKAGE,
- interfaceName = "Alignment.Vertical",
- ALIGNMENT_CLASSES_FOR_SUGGESTIONS,
- weightsByType = mapOf(
- "Alignment.Vertical" to 10,
- "Alignment" to -10,
- "Alignment.Horizontal" to -10,
- ),
- weightsByParentClass = mapOf(
- "Alignment.Companion" to 2,
- "Alignment" to 2,
- "AbsoluteAlignment" to 1,
- ),
- ),
-
- PositioningInterface(
- packageName = ARRANGEMENT_PACKAGE,
- interfaceName = "Arrangement.Horizontal",
- ARRANGEMENT_CLASSES_FOR_SUGGESTIONS,
- additionalTypeToSuggest = "Arrangement.HorizontalOrVertical",
- weightsByType = mapOf(
- "Arrangement.Horizontal" to 10,
- "Arrangement.HorizontalOrVertical" to 10,
- "Arrangement.Vertical" to -10,
- ),
- weightsByParentClass = mapOf(
- "Arrangement" to 2,
- "Arrangement.Absolute" to 1,
- ),
- ),
- PositioningInterface(
- packageName = ARRANGEMENT_PACKAGE,
- interfaceName = "Arrangement.Vertical",
- ARRANGEMENT_CLASSES_FOR_SUGGESTIONS,
- additionalTypeToSuggest = "Arrangement.HorizontalOrVertical",
- weightsByType = mapOf(
- "Arrangement.Vertical" to 10,
- "Arrangement.HorizontalOrVertical" to 10,
- "Arrangement.Horizontal" to -10,
- ),
- weightsByParentClass = mapOf(
- "Arrangement" to 2,
- "Arrangement.Absolute" to 1,
- ),
- ),
- PositioningInterface(
- packageName = ARRANGEMENT_PACKAGE,
- interfaceName = "Arrangement.HorizontalOrVertical",
- ARRANGEMENT_CLASSES_FOR_SUGGESTIONS,
- weightsByType = mapOf(
- "Arrangement.HorizontalOrVertical" to 10,
- "Arrangement.Vertical" to -10,
- "Arrangement.Horizontal" to -10,
- ),
- weightsByParentClass = mapOf(
- "Arrangement" to 2,
- "Arrangement.Absolute" to 1,
- ),
- ),
- ).associateBy { it.interfaceFqName }
+ private val VALUES =
+ listOf(
+ PositioningInterface(
+ packageName = ALIGNMENT_PACKAGE,
+ interfaceName = "Alignment",
+ ALIGNMENT_CLASSES_FOR_SUGGESTIONS,
+ weightsByType =
+ mapOf(
+ "Alignment" to 10,
+ "Alignment.Horizontal" to -10,
+ "Alignment.Vertical" to -10,
+ ),
+ weightsByParentClass =
+ mapOf(
+ "Alignment.Companion" to 2,
+ "Alignment" to 2,
+ "AbsoluteAlignment" to 1,
+ ),
+ ),
+ PositioningInterface(
+ packageName = ALIGNMENT_PACKAGE,
+ interfaceName = "Alignment.Horizontal",
+ ALIGNMENT_CLASSES_FOR_SUGGESTIONS,
+ weightsByType =
+ mapOf(
+ "Alignment.Horizontal" to 10,
+ "Alignment" to -10,
+ "Alignment.Vertical" to -10,
+ ),
+ weightsByParentClass =
+ mapOf(
+ "Alignment.Companion" to 2,
+ "Alignment" to 2,
+ "AbsoluteAlignment" to 1,
+ ),
+ ),
+ PositioningInterface(
+ packageName = ALIGNMENT_PACKAGE,
+ interfaceName = "Alignment.Vertical",
+ ALIGNMENT_CLASSES_FOR_SUGGESTIONS,
+ weightsByType =
+ mapOf(
+ "Alignment.Vertical" to 10,
+ "Alignment" to -10,
+ "Alignment.Horizontal" to -10,
+ ),
+ weightsByParentClass =
+ mapOf(
+ "Alignment.Companion" to 2,
+ "Alignment" to 2,
+ "AbsoluteAlignment" to 1,
+ ),
+ ),
+ PositioningInterface(
+ packageName = ARRANGEMENT_PACKAGE,
+ interfaceName = "Arrangement.Horizontal",
+ ARRANGEMENT_CLASSES_FOR_SUGGESTIONS,
+ additionalTypeToSuggest = "Arrangement.HorizontalOrVertical",
+ weightsByType =
+ mapOf(
+ "Arrangement.Horizontal" to 10,
+ "Arrangement.HorizontalOrVertical" to 10,
+ "Arrangement.Vertical" to -10,
+ ),
+ weightsByParentClass =
+ mapOf(
+ "Arrangement" to 2,
+ "Arrangement.Absolute" to 1,
+ ),
+ ),
+ PositioningInterface(
+ packageName = ARRANGEMENT_PACKAGE,
+ interfaceName = "Arrangement.Vertical",
+ ARRANGEMENT_CLASSES_FOR_SUGGESTIONS,
+ additionalTypeToSuggest = "Arrangement.HorizontalOrVertical",
+ weightsByType =
+ mapOf(
+ "Arrangement.Vertical" to 10,
+ "Arrangement.HorizontalOrVertical" to 10,
+ "Arrangement.Horizontal" to -10,
+ ),
+ weightsByParentClass =
+ mapOf(
+ "Arrangement" to 2,
+ "Arrangement.Absolute" to 1,
+ ),
+ ),
+ PositioningInterface(
+ packageName = ARRANGEMENT_PACKAGE,
+ interfaceName = "Arrangement.HorizontalOrVertical",
+ ARRANGEMENT_CLASSES_FOR_SUGGESTIONS,
+ weightsByType =
+ mapOf(
+ "Arrangement.HorizontalOrVertical" to 10,
+ "Arrangement.Vertical" to -10,
+ "Arrangement.Horizontal" to -10,
+ ),
+ weightsByParentClass =
+ mapOf(
+ "Arrangement" to 2,
+ "Arrangement.Absolute" to 1,
+ ),
+ ),
+ )
+ .associateBy { it.interfaceFqName }
}
}
/**
- * Suggests completion for the Alignment and Arrangement interfaces. Both interfaces have Horizontal and Vertical variants which the default
- * auto-completion intermixes, even though only one subset is generally applicable in any given completion.
+ * Suggests completion for the Alignment and Arrangement interfaces. Both interfaces have Horizontal
+ * and Vertical variants which the default auto-completion intermixes, even though only one subset
+ * is generally applicable in any given completion.
*/
class ComposePositioningCompletionContributor : CompletionContributor() {
- override fun fillCompletionVariants(parameters: CompletionParameters, result: CompletionResultSet) {
+ override fun fillCompletionVariants(
+ parameters: CompletionParameters,
+ result: CompletionResultSet
+ ) {
val elementToComplete = parameters.position
if (!isComposeEnabled(elementToComplete) || parameters.originalFile !is KtFile) return
// Add any suggested elements needed for this element.
- val lookupElements = PositioningInterface.forCompletionElement(elementToComplete)?.getSuggestedCompletions(elementToComplete) ?: return
+ val lookupElements =
+ PositioningInterface.forCompletionElement(elementToComplete)
+ ?.getSuggestedCompletions(elementToComplete)
+ ?: return
result.addAllElements(lookupElements)
- // Run the remaining contributors, removing any duplicates of the items that have already been suggested.
+ // Run the remaining contributors, removing any duplicates of the items that have already been
+ // suggested.
val addedElements = lookupElements.mapNotNull { it.psiElement?.kotlinFqName }.toSet()
result.runRemainingContributors(parameters) { completionResult ->
- val alreadyAddedElement = completionResult.lookupElement.psiElement?.kotlinFqName?.let { addedElements.contains(it) } ?: false
+ val alreadyAddedElement =
+ completionResult.lookupElement.psiElement?.kotlinFqName?.let { addedElements.contains(it) }
+ ?: false
if (!alreadyAddedElement) {
result.passResult(completionResult)
}
@@ -420,12 +496,15 @@
val parameters = location.completionParameters
val elementToComplete = parameters.position
if (!isComposeEnabled(elementToComplete) || parameters.originalFile !is KtFile) {
- // Return null when this isn't a completion we care about to avoid any further comparisons or object allocations.
+ // Return null when this isn't a completion we care about to avoid any further comparisons or
+ // object allocations.
return null
}
- // Since this is a completion involving one of the types handled here, we want to rank everything. If it's not an element being
+ // Since this is a completion involving one of the types handled here, we want to rank
+ // everything. If it's not an element being
// adjusted, then the weight of '0' will effectively let the item pass through unmodified.
- return PositioningInterface.forCompletionElement(elementToComplete)?.getWeight(lookupElement) ?: 0
+ return PositioningInterface.forCompletionElement(elementToComplete)?.getWeight(lookupElement)
+ ?: 0
}
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/Constants.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/Constants.kt
index c44a15f..dfa03f9 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/Constants.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/Constants.kt
@@ -16,30 +16,22 @@
package com.android.tools.compose.code.completion.constraintlayout
internal object KeyWords {
- /**
- * Name of the property within a MotionScene that contains several ConstraintSet declarations.
- */
+ /** Name of the property within a MotionScene that contains several ConstraintSet declarations. */
const val ConstraintSets = "ConstraintSets"
- /**
- * Name of the property within a MotionScene that contains several Transition declarations.
- */
+ /** Name of the property within a MotionScene that contains several Transition declarations. */
const val Transitions = "Transitions"
/**
- * Name of the property used to indicate that the containing ConstraintSet inherits its constraints from the ConstraintSet given by the
- * `Extends` property value.
+ * Name of the property used to indicate that the containing ConstraintSet inherits its
+ * constraints from the ConstraintSet given by the `Extends` property value.
*/
const val Extends = "Extends"
- /**
- * Reserved ID for the containing layout. Typically referenced in constraint blocks.
- */
+ /** Reserved ID for the containing layout. Typically referenced in constraint blocks. */
const val ParentId = "parent"
- /**
- * Name of the Visibility property in a constraint block.
- */
+ /** Name of the Visibility property in a constraint block. */
const val Visibility = "visibility"
/**
@@ -61,10 +53,8 @@
val keyWord: String
}
-//region Constrain KeyWords
-/**
- * The classic anchors used to constrain a widget.
- */
+// region Constrain KeyWords
+/** The classic anchors used to constrain a widget. */
internal enum class StandardAnchor(override val keyWord: String) : ConstraintLayoutKeyWord {
Start("start"),
Left("left"),
@@ -96,17 +86,13 @@
CenterV("centerVertically")
}
-/**
- * Supported keywords to define the dimension of a widget.
- */
+/** Supported keywords to define the dimension of a widget. */
internal enum class Dimension(override val keyWord: String) : ConstraintLayoutKeyWord {
Width("width"),
Height("height")
}
-/**
- * Keywords to apply rendering time transformations to a widget.
- */
+/** Keywords to apply rendering time transformations to a widget. */
internal enum class RenderTransform(override val keyWord: String) : ConstraintLayoutKeyWord {
Alpha("alpha"),
ScaleX("scaleX"),
@@ -118,7 +104,7 @@
TranslationY("translationY"),
TranslationZ("translationZ"),
}
-//endregion
+// endregion
internal enum class DimBehavior(override val keyWord: String) : ConstraintLayoutKeyWord {
Spread("spread"),
@@ -127,19 +113,19 @@
MatchParent("parent")
}
-internal enum class VisibilityMode(override val keyWord: String): ConstraintLayoutKeyWord {
+internal enum class VisibilityMode(override val keyWord: String) : ConstraintLayoutKeyWord {
Visible("visible"),
Invisible("invisible"),
Gone("gone")
}
-internal enum class ClearOption(override val keyWord: String): ConstraintLayoutKeyWord {
+internal enum class ClearOption(override val keyWord: String) : ConstraintLayoutKeyWord {
Constraints("constraints"),
Dimensions("dimensions"),
Transforms("transforms")
}
-internal enum class TransitionField(override val keyWord: String): ConstraintLayoutKeyWord {
+internal enum class TransitionField(override val keyWord: String) : ConstraintLayoutKeyWord {
From("from"),
To("to"),
PathArc("pathMotionArc"),
@@ -147,14 +133,14 @@
OnSwipe("onSwipe")
}
-internal enum class OnSwipeField(override val keyWord: String): ConstraintLayoutKeyWord {
+internal enum class OnSwipeField(override val keyWord: String) : ConstraintLayoutKeyWord {
AnchorId("anchor"),
Direction("direction"),
Side("side"),
Mode("mode")
}
-internal enum class OnSwipeSide(override val keyWord: String): ConstraintLayoutKeyWord {
+internal enum class OnSwipeSide(override val keyWord: String) : ConstraintLayoutKeyWord {
Top("top"),
Left("left"),
Right("right"),
@@ -164,7 +150,7 @@
End("end")
}
-internal enum class OnSwipeDirection(override val keyWord: String): ConstraintLayoutKeyWord {
+internal enum class OnSwipeDirection(override val keyWord: String) : ConstraintLayoutKeyWord {
Up("up"),
Down("down"),
Left("left"),
@@ -175,28 +161,27 @@
AntiClockwise("anticlockwise")
}
-internal enum class OnSwipeMode(override val keyWord: String): ConstraintLayoutKeyWord {
+internal enum class OnSwipeMode(override val keyWord: String) : ConstraintLayoutKeyWord {
Velocity("velocity"),
Spring("spring")
}
-internal enum class KeyFrameField(override val keyWord: String): ConstraintLayoutKeyWord {
+internal enum class KeyFrameField(override val keyWord: String) : ConstraintLayoutKeyWord {
Positions("KeyPositions"),
Attributes("KeyAttributes"),
Cycles("KeyCycles")
}
-/**
- * Common fields used by any of [KeyFrameField].
- */
-internal enum class KeyFrameChildCommonField(override val keyWord: String): ConstraintLayoutKeyWord {
+/** Common fields used by any of [KeyFrameField]. */
+internal enum class KeyFrameChildCommonField(override val keyWord: String) :
+ ConstraintLayoutKeyWord {
TargetId("target"),
Frames("frames"),
Easing("transitionEasing"),
Fit("curveFit"),
}
-internal enum class KeyPositionField(override val keyWord: String): ConstraintLayoutKeyWord {
+internal enum class KeyPositionField(override val keyWord: String) : ConstraintLayoutKeyWord {
PercentX("percentX"),
PercentY("percentY"),
PercentWidth("percentWidth"),
@@ -205,8 +190,8 @@
Type("type")
}
-internal enum class KeyCycleField(override val keyWord: String): ConstraintLayoutKeyWord {
+internal enum class KeyCycleField(override val keyWord: String) : ConstraintLayoutKeyWord {
Period("period"),
Offset("offset"),
Phase("phase")
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/ConstraintLayoutJsonCompletionContributor.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/ConstraintLayoutJsonCompletionContributor.kt
index 0348aa8..c7bf2d6 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/ConstraintLayoutJsonCompletionContributor.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/ConstraintLayoutJsonCompletionContributor.kt
@@ -36,31 +36,55 @@
internal const val BASE_DEPTH_FOR_LITERAL_IN_PROPERTY = 2
-internal const val BASE_DEPTH_FOR_NAME_IN_PROPERTY_OBJECT = BASE_DEPTH_FOR_LITERAL_IN_PROPERTY + BASE_DEPTH_FOR_LITERAL_IN_PROPERTY
+internal const val BASE_DEPTH_FOR_NAME_IN_PROPERTY_OBJECT =
+ BASE_DEPTH_FOR_LITERAL_IN_PROPERTY + BASE_DEPTH_FOR_LITERAL_IN_PROPERTY
-/** Depth for a literal of a property of the list of ConstraintSets. With respect to the ConstraintSets root element. */
-private const val CONSTRAINT_SET_LIST_PROPERTY_DEPTH = BASE_DEPTH_FOR_LITERAL_IN_PROPERTY + BASE_DEPTH_FOR_LITERAL_IN_PROPERTY
+/**
+ * Depth for a literal of a property of the list of ConstraintSets. With respect to the
+ * ConstraintSets root element.
+ */
+private const val CONSTRAINT_SET_LIST_PROPERTY_DEPTH =
+ BASE_DEPTH_FOR_LITERAL_IN_PROPERTY + BASE_DEPTH_FOR_LITERAL_IN_PROPERTY
-/** Depth for a literal of a property of a ConstraintSet. With respect to the ConstraintSets root element. */
-private const val CONSTRAINT_SET_PROPERTY_DEPTH = CONSTRAINT_SET_LIST_PROPERTY_DEPTH + BASE_DEPTH_FOR_LITERAL_IN_PROPERTY
+/**
+ * Depth for a literal of a property of a ConstraintSet. With respect to the ConstraintSets root
+ * element.
+ */
+private const val CONSTRAINT_SET_PROPERTY_DEPTH =
+ CONSTRAINT_SET_LIST_PROPERTY_DEPTH + BASE_DEPTH_FOR_LITERAL_IN_PROPERTY
-/** Depth for a literal of a property of a Transition. With respect to the Transitions root element. */
+/**
+ * Depth for a literal of a property of a Transition. With respect to the Transitions root element.
+ */
private const val TRANSITION_PROPERTY_DEPTH = CONSTRAINT_SET_PROPERTY_DEPTH
-/** Depth for a literal of a property of a Constraints block. With respect to the ConstraintSets root element. */
-internal const val CONSTRAINT_BLOCK_PROPERTY_DEPTH = CONSTRAINT_SET_PROPERTY_DEPTH + BASE_DEPTH_FOR_LITERAL_IN_PROPERTY
+/**
+ * Depth for a literal of a property of a Constraints block. With respect to the ConstraintSets root
+ * element.
+ */
+internal const val CONSTRAINT_BLOCK_PROPERTY_DEPTH =
+ CONSTRAINT_SET_PROPERTY_DEPTH + BASE_DEPTH_FOR_LITERAL_IN_PROPERTY
-/** Depth for a literal of a property of an OnSwipe block. With respect to the Transitions root element. */
-internal const val ONSWIPE_PROPERTY_DEPTH = TRANSITION_PROPERTY_DEPTH + BASE_DEPTH_FOR_LITERAL_IN_PROPERTY
+/**
+ * Depth for a literal of a property of an OnSwipe block. With respect to the Transitions root
+ * element.
+ */
+internal const val ONSWIPE_PROPERTY_DEPTH =
+ TRANSITION_PROPERTY_DEPTH + BASE_DEPTH_FOR_LITERAL_IN_PROPERTY
-/** Depth for a literal of a property of a KeyFrames block. With respect to the Transitions root element. */
+/**
+ * Depth for a literal of a property of a KeyFrames block. With respect to the Transitions root
+ * element.
+ */
internal const val KEYFRAMES_PROPERTY_DEPTH = ONSWIPE_PROPERTY_DEPTH
/**
- * [CompletionContributor] for the JSON5 format supported in ConstraintLayout-Compose (and MotionLayout).
+ * [CompletionContributor] for the JSON5 format supported in ConstraintLayout-Compose (and
+ * MotionLayout).
*
- * See the official wiki in [GitHub](https://github.com/androidx/constraintlayout/wiki/ConstraintSet-JSON5-syntax) to learn more about the
- * supported JSON5 syntax.
+ * See the official wiki in
+ * [GitHub](https://github.com/androidx/constraintlayout/wiki/ConstraintSet-JSON5-syntax) to learn
+ * more about the supported JSON5 syntax.
*/
class ConstraintLayoutJsonCompletionContributor : CompletionContributor() {
init {
@@ -88,14 +112,18 @@
CompletionType.BASIC,
// Complete IDs on special anchors, they take a single string value
jsonStringValue()
- .withPropertyParentAtLevel(BASE_DEPTH_FOR_LITERAL_IN_PROPERTY, SpecialAnchor.values().map { it.keyWord }),
+ .withPropertyParentAtLevel(
+ BASE_DEPTH_FOR_LITERAL_IN_PROPERTY,
+ SpecialAnchor.values().map { it.keyWord }
+ ),
ConstraintIdsProvider
)
extend(
CompletionType.BASIC,
// Complete IDs in the constraint array (first position)
jsonStringValue()
- // First element in the array, ie: there is no PsiElement preceding the desired one at this level
+ // First element in the array, ie: there is no PsiElement preceding the desired one at this
+ // level
.withParent(psiElement<JsonStringLiteral>().atIndexOfJsonArray(0))
.insideConstraintArray(),
ConstraintIdsProvider
@@ -104,7 +132,8 @@
CompletionType.BASIC,
// Complete anchors in the constraint array (second position)
jsonStringValue()
- // Second element in the array, ie: there is one PsiElement preceding the desired one at this level
+ // Second element in the array, ie: there is one PsiElement preceding the desired one at
+ // this level
.withParent(psiElement<JsonStringLiteral>().atIndexOfJsonArray(1))
.insideConstraintArray(),
AnchorablesProvider
@@ -112,15 +141,17 @@
extend(
CompletionType.BASIC,
// Complete a clear option within the 'clear' array
- jsonStringValue()
- .insideClearArray(),
+ jsonStringValue().insideClearArray(),
ClearOptionsProvider
)
extend(
CompletionType.BASIC,
// Complete non-numeric dimension values for width & height
jsonStringValue()
- .withPropertyParentAtLevel(BASE_DEPTH_FOR_LITERAL_IN_PROPERTY, Dimension.values().map { it.keyWord }),
+ .withPropertyParentAtLevel(
+ BASE_DEPTH_FOR_LITERAL_IN_PROPERTY,
+ Dimension.values().map { it.keyWord }
+ ),
EnumValuesCompletionProvider(DimBehavior::class)
)
extend(
@@ -130,23 +161,26 @@
.withPropertyParentAtLevel(BASE_DEPTH_FOR_LITERAL_IN_PROPERTY, KeyWords.Visibility),
EnumValuesCompletionProvider(VisibilityMode::class)
)
- //endregion
+ // endregion
- //region Transitions
+ // region Transitions
extend(
CompletionType.BASIC,
// Complete fields of a Transition block
- jsonPropertyName()
- .withTransitionsParentAtLevel(TRANSITION_PROPERTY_DEPTH),
+ jsonPropertyName().withTransitionsParentAtLevel(TRANSITION_PROPERTY_DEPTH),
TransitionFieldsProvider
)
extend(
CompletionType.BASIC,
// Complete existing ConstraintSet names for `from` and `to` Transition properties
jsonStringValue()
- .withPropertyParentAtLevel(BASE_DEPTH_FOR_LITERAL_IN_PROPERTY, listOf(TransitionField.From.keyWord, TransitionField.To.keyWord))
+ .withPropertyParentAtLevel(
+ BASE_DEPTH_FOR_LITERAL_IN_PROPERTY,
+ listOf(TransitionField.From.keyWord, TransitionField.To.keyWord)
+ )
.withTransitionsParentAtLevel(TRANSITION_PROPERTY_DEPTH),
- // TODO(b/207030860): Guarantee that provided names for 'from' or 'to' are distinct from each other,
+ // TODO(b/207030860): Guarantee that provided names for 'from' or 'to' are distinct from each
+ // other,
// ie: both shouldn't reference the same ConstraintSet
ConstraintSetNamesProvider
)
@@ -154,7 +188,10 @@
CompletionType.BASIC,
// Complete fields of a KeyFrames block
jsonPropertyName()
- .withPropertyParentAtLevel(BASE_DEPTH_FOR_NAME_IN_PROPERTY_OBJECT, TransitionField.KeyFrames.keyWord)
+ .withPropertyParentAtLevel(
+ BASE_DEPTH_FOR_NAME_IN_PROPERTY_OBJECT,
+ TransitionField.KeyFrames.keyWord
+ )
.withTransitionsParentAtLevel(KEYFRAMES_PROPERTY_DEPTH),
KeyFramesFieldsProvider
)
@@ -162,7 +199,10 @@
CompletionType.BASIC,
// Complete fields of an OnSwipe block
jsonPropertyName()
- .withPropertyParentAtLevel(BASE_DEPTH_FOR_NAME_IN_PROPERTY_OBJECT, TransitionField.OnSwipe.keyWord)
+ .withPropertyParentAtLevel(
+ BASE_DEPTH_FOR_NAME_IN_PROPERTY_OBJECT,
+ TransitionField.OnSwipe.keyWord
+ )
.withTransitionsParentAtLevel(ONSWIPE_PROPERTY_DEPTH),
OnSwipeFieldsProvider
)
@@ -170,7 +210,10 @@
CompletionType.BASIC,
// Complete the possible IDs for the OnSwipe `anchor` property
jsonStringValue()
- .withPropertyParentAtLevel(BASE_DEPTH_FOR_LITERAL_IN_PROPERTY, OnSwipeField.AnchorId.keyWord),
+ .withPropertyParentAtLevel(
+ BASE_DEPTH_FOR_LITERAL_IN_PROPERTY,
+ OnSwipeField.AnchorId.keyWord
+ ),
ConstraintIdsProvider
)
extend(
@@ -184,7 +227,10 @@
CompletionType.BASIC,
// Complete the known values for the OnSwipe `direction` property
jsonStringValue()
- .withPropertyParentAtLevel(BASE_DEPTH_FOR_LITERAL_IN_PROPERTY, OnSwipeField.Direction.keyWord),
+ .withPropertyParentAtLevel(
+ BASE_DEPTH_FOR_LITERAL_IN_PROPERTY,
+ OnSwipeField.Direction.keyWord
+ ),
EnumValuesCompletionProvider(OnSwipeDirection::class)
)
extend(
@@ -199,17 +245,26 @@
// Complete the fields for any of the possible KeyFrames children
jsonPropertyName()
// A level deeper considering the array surrounding the object
- .withPropertyParentAtLevel(BASE_DEPTH_FOR_NAME_IN_PROPERTY_OBJECT + 1, KeyFrameField.values().map { it.keyWord }),
+ .withPropertyParentAtLevel(
+ BASE_DEPTH_FOR_NAME_IN_PROPERTY_OBJECT + 1,
+ KeyFrameField.values().map { it.keyWord }
+ ),
KeyFrameChildFieldsCompletionProvider
)
- //endregion
+ // endregion
}
- override fun fillCompletionVariants(parameters: CompletionParameters, result: CompletionResultSet) {
- if (parameters.position.getModuleSystem()?.usesCompose != true || parameters.position.language != JsonLanguage.INSTANCE) {
+ override fun fillCompletionVariants(
+ parameters: CompletionParameters,
+ result: CompletionResultSet
+ ) {
+ if (
+ parameters.position.getModuleSystem()?.usesCompose != true ||
+ parameters.position.language != JsonLanguage.INSTANCE
+ ) {
// TODO(b/207030860): Allow in other contexts once the syntax is supported outside Compose
return
}
super.fillCompletionVariants(parameters, result)
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/InsertionFormat.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/InsertionFormat.kt
index 0ba83a9..cf5462a 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/InsertionFormat.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/InsertionFormat.kt
@@ -31,17 +31,21 @@
internal val ConstrainAnchorTemplate = LiveTemplateFormat(": ['<>', '<>', <0>],")
-internal val ClearAllTemplate = LiteralWithCaretFormat(
- literalFormat = ": ['${ClearOption.Constraints}', '${ClearOption.Dimensions}', '${ClearOption.Transforms}'],"
-)
+internal val ClearAllTemplate =
+ LiteralWithCaretFormat(
+ literalFormat =
+ ": ['${ClearOption.Constraints}', '${ClearOption.Dimensions}', '${ClearOption.Transforms}'],"
+ )
/**
- * Returns a [LiveTemplateFormat] that contains a template for a Json array with numeric type, where the size of the array is given by
- * [count] and the user may edit each of the values in the array using Live Templates.
+ * Returns a [LiveTemplateFormat] that contains a template for a Json array with numeric type, where
+ * the size of the array is given by [count] and the user may edit each of the values in the array
+ * using Live Templates.
*
- * E.g.: For [count] = 3, returns the template: `": [0, 0, 0],"`, where every value may be changed by the user.
+ * E.g.: For [count] = 3, returns the template: `": [0, 0, 0],"`, where every value may be changed
+ * by the user.
*/
internal fun buildJsonNumberArrayTemplate(count: Int): LiveTemplateFormat {
val times = count.coerceAtLeast(1)
return LiveTemplateFormat(": [" + "<0>, ".repeat(times).removeSuffix(", ") + "],")
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/JsonPsiUtil.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/JsonPsiUtil.kt
index 29a5b45..50c1418 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/JsonPsiUtil.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/JsonPsiUtil.kt
@@ -20,8 +20,10 @@
import com.intellij.psi.util.parentOfType
/**
- * From the element being invoked, returns the [JsonProperty] parent that also includes the [JsonProperty] from which completion is
- * triggered.
+ * From the element being invoked, returns the [JsonProperty] parent that also includes the
+ * [JsonProperty] from which completion is triggered.
*/
internal fun getJsonPropertyParent(parameters: CompletionParameters): JsonProperty? =
- parameters.position.parentOfType<JsonProperty>(withSelf = true)?.parentOfType<JsonProperty>(withSelf = false)
\ No newline at end of file
+ parameters.position
+ .parentOfType<JsonProperty>(withSelf = true)
+ ?.parentOfType<JsonProperty>(withSelf = false)
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/PatternUtils.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/PatternUtils.kt
index 2937e7a..e2c9f53 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/PatternUtils.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/PatternUtils.kt
@@ -35,67 +35,90 @@
internal fun jsonStringValue() =
PlatformPatterns.psiElement(JsonElementTypes.SINGLE_QUOTED_STRING).withParent<JsonStringLiteral>()
-internal fun PsiElementPattern<*, *>.withConstraintSetsParentAtLevel(level: Int) = withPropertyParentAtLevel(level, KeyWords.ConstraintSets)
-internal fun PsiElementPattern<*, *>.withTransitionsParentAtLevel(level: Int) = withPropertyParentAtLevel(level, KeyWords.Transitions)
+internal fun PsiElementPattern<*, *>.withConstraintSetsParentAtLevel(level: Int) =
+ withPropertyParentAtLevel(level, KeyWords.ConstraintSets)
+
+internal fun PsiElementPattern<*, *>.withTransitionsParentAtLevel(level: Int) =
+ withPropertyParentAtLevel(level, KeyWords.Transitions)
internal fun PsiElementPattern<*, *>.insideClearArray() = inArrayWithinConstraintBlockProperty {
// For the 'clear' constraint block property
matches(KeyWords.Clear)
}
-internal fun PsiElementPattern<*, *>.insideConstraintArray() = inArrayWithinConstraintBlockProperty {
- // The parent property name may only be a StandardAnchor
- oneOf(StandardAnchor.values().map { it.keyWord })
-}
+internal fun PsiElementPattern<*, *>.insideConstraintArray() =
+ inArrayWithinConstraintBlockProperty {
+ // The parent property name may only be a StandardAnchor
+ oneOf(StandardAnchor.values().map { it.keyWord })
+ }
/**
- * [PsiElementPattern] that matches an element in a [JsonArray] within a Constraint block. Where the property the array is assigned to, has
- * a name that is matched by [matchPropertyName].
+ * [PsiElementPattern] that matches an element in a [JsonArray] within a Constraint block. Where the
+ * property the array is assigned to, has a name that is matched by [matchPropertyName].
*/
-internal fun PsiElementPattern<*, *>.inArrayWithinConstraintBlockProperty(matchPropertyName: StringPattern.() -> StringPattern) =
+internal fun PsiElementPattern<*, *>.inArrayWithinConstraintBlockProperty(
+ matchPropertyName: StringPattern.() -> StringPattern
+) =
withSuperParent(2, psiElement<JsonArray>())
.withSuperParent(
BASE_DEPTH_FOR_LITERAL_IN_PROPERTY + 1, // JsonArray adds one level
- psiElement<JsonProperty>().withChild(
- // The first expression in a JsonProperty corresponds to the name of the property
- psiElement<JsonReferenceExpression>().withText(StandardPatterns.string().matchPropertyName())
- )
+ psiElement<JsonProperty>()
+ .withChild(
+ // The first expression in a JsonProperty corresponds to the name of the property
+ psiElement<JsonReferenceExpression>()
+ .withText(StandardPatterns.string().matchPropertyName())
+ )
)
- .withConstraintSetsParentAtLevel(CONSTRAINT_BLOCK_PROPERTY_DEPTH + 1) // JsonArray adds one level
+ .withConstraintSetsParentAtLevel(
+ CONSTRAINT_BLOCK_PROPERTY_DEPTH + 1
+ ) // JsonArray adds one level
// endregion
// region Kotlin Syntax Helpers
-internal inline fun <reified T : PsiElement> psiElement(): PsiElementPattern<T, PsiElementPattern.Capture<T>> =
- PlatformPatterns.psiElement(T::class.java)
+internal inline fun <reified T : PsiElement> psiElement():
+ PsiElementPattern<T, PsiElementPattern.Capture<T>> = PlatformPatterns.psiElement(T::class.java)
-internal inline fun <reified T : PsiElement> PsiElementPattern<*, *>.withParent() = this.withParent(T::class.java)
+internal inline fun <reified T : PsiElement> PsiElementPattern<*, *>.withParent() =
+ this.withParent(T::class.java)
/**
- * Pattern such that when traversing up the tree from the current element, the element at [level] is a [JsonProperty]. And its name matches
- * the given [name].
+ * Pattern such that when traversing up the tree from the current element, the element at [level] is
+ * a [JsonProperty]. And its name matches the given [name].
*/
internal fun PsiElementPattern<*, *>.withPropertyParentAtLevel(level: Int, name: String) =
withPropertyParentAtLevel(level, listOf(name))
/**
- * Pattern such that when traversing up the tree from the current element, the element at [level] is a [JsonProperty]. Which name matches
- * one of the given [names].
+ * Pattern such that when traversing up the tree from the current element, the element at [level] is
+ * a [JsonProperty]. Which name matches one of the given [names].
*/
-internal fun PsiElementPattern<*, *>.withPropertyParentAtLevel(level: Int, names: Collection<String>) =
- this.withSuperParent(level, psiElement<JsonProperty>().withChild(
- psiElement<JsonReferenceExpression>().withText(StandardPatterns.string().oneOf(names)))
+internal fun PsiElementPattern<*, *>.withPropertyParentAtLevel(
+ level: Int,
+ names: Collection<String>
+) =
+ this.withSuperParent(
+ level,
+ psiElement<JsonProperty>()
+ .withChild(
+ psiElement<JsonReferenceExpression>().withText(StandardPatterns.string().oneOf(names))
+ )
)
/**
- * Verifies that the current element is at the given [index] of the elements contained by its [JsonArray] parent.
+ * Verifies that the current element is at the given [index] of the elements contained by its
+ * [JsonArray] parent.
*/
-internal fun <T : JsonValue> PsiElementPattern<T, PsiElementPattern.Capture<T>>.atIndexOfJsonArray(index: Int) =
- with(object : PatternCondition<T>("atIndexOfJsonArray") {
- override fun accepts(element: T, context: ProcessingContext?): Boolean {
- val parent = element.context as? JsonArray ?: return false
- val children = parent.valueList
- val indexOfSelf = children.indexOf(element)
- return index == indexOfSelf
+internal fun <T : JsonValue> PsiElementPattern<T, PsiElementPattern.Capture<T>>.atIndexOfJsonArray(
+ index: Int
+) =
+ with(
+ object : PatternCondition<T>("atIndexOfJsonArray") {
+ override fun accepts(element: T, context: ProcessingContext?): Boolean {
+ val parent = element.context as? JsonArray ?: return false
+ val children = parent.valueList
+ val indexOfSelf = children.indexOf(element)
+ return index == indexOfSelf
+ }
}
- })
-// endregion
\ No newline at end of file
+ )
+// endregion
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/CompletionProviders.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/CompletionProviders.kt
index 8f6ac28..e8dff54 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/CompletionProviders.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/CompletionProviders.kt
@@ -57,11 +57,17 @@
import kotlin.reflect.KClass
/**
- * Completion provider that looks for the 'ConstraintSets' declaration and passes a model that provides useful functions for inheritors that
- * want to provide completions based on the contents of the 'ConstraintSets' [JsonProperty].
+ * Completion provider that looks for the 'ConstraintSets' declaration and passes a model that
+ * provides useful functions for inheritors that want to provide completions based on the contents
+ * of the 'ConstraintSets' [JsonProperty].
*/
-internal abstract class BaseConstraintSetsCompletionProvider : CompletionProvider<CompletionParameters>() {
- final override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
+internal abstract class BaseConstraintSetsCompletionProvider :
+ CompletionProvider<CompletionParameters>() {
+ final override fun addCompletions(
+ parameters: CompletionParameters,
+ context: ProcessingContext,
+ result: CompletionResultSet
+ ) {
val constraintSetsModel = createConstraintSetsModel(initialElement = parameters.position)
if (constraintSetsModel != null) {
ProgressManager.checkCanceled()
@@ -70,7 +76,8 @@
}
/**
- * Inheritors should implement this function that may pass a reference to the ConstraintSets property.
+ * Inheritors should implement this function that may pass a reference to the ConstraintSets
+ * property.
*/
abstract fun addCompletions(
constraintSetsPropertyModel: ConstraintSetsPropertyModel,
@@ -85,7 +92,8 @@
*/
private fun createConstraintSetsModel(initialElement: PsiElement): ConstraintSetsPropertyModel? {
// Start with the closest JsonObject towards the root
- var currentJsonObject: JsonObject? = initialElement.parentOfType<JsonObject>(withSelf = true) ?: return null
+ var currentJsonObject: JsonObject? =
+ initialElement.parentOfType<JsonObject>(withSelf = true) ?: return null
lateinit var topLevelJsonObject: JsonObject
// Then find the top most JsonObject while checking for cancellation
@@ -96,18 +104,22 @@
ProgressManager.checkCanceled()
}
- // The last non-null JsonObject is the topmost, the ConstraintSets property is expected within this element
- val constraintSetsProperty = topLevelJsonObject.findProperty(KeyWords.ConstraintSets) ?: return null
- // TODO(b/207030860): Consider creating the model even if there's no property that is explicitly called 'ConstraintSets'
- // ie: imply that the root JsonObject is the ConstraintSets object, with the downside that figuring out the correct context would
+ // The last non-null JsonObject is the topmost, the ConstraintSets property is expected within
+ // this element
+ val constraintSetsProperty =
+ topLevelJsonObject.findProperty(KeyWords.ConstraintSets) ?: return null
+ // TODO(b/207030860): Consider creating the model even if there's no property that is explicitly
+ // called 'ConstraintSets'
+ // ie: imply that the root JsonObject is the ConstraintSets object, with the downside that
+ // figuring out the correct context would
// be much more difficult
return ConstraintSetsPropertyModel(constraintSetsProperty)
}
}
/**
- * Provides options to autocomplete constraint IDs for constraint set declarations, based on the IDs already defined by the user in other
- * constraint sets.
+ * Provides options to autocomplete constraint IDs for constraint set declarations, based on the IDs
+ * already defined by the user in other constraint sets.
*/
internal object ConstraintSetFieldsProvider : BaseConstraintSetsCompletionProvider() {
override fun addCompletions(
@@ -115,17 +127,21 @@
parameters: CompletionParameters,
result: CompletionResultSet
) {
- val currentConstraintSet = ConstraintSetModel.getModelForCompletionOnConstraintSetProperty(parameters) ?: return
+ val currentConstraintSet =
+ ConstraintSetModel.getModelForCompletionOnConstraintSetProperty(parameters) ?: return
val currentSetName = currentConstraintSet.name ?: return
- constraintSetsPropertyModel.getRemainingFieldsForConstraintSet(currentSetName).forEach { fieldName ->
- val template = if (fieldName == KeyWords.Extends) JsonStringValueTemplate else JsonNewObjectTemplate
+ constraintSetsPropertyModel.getRemainingFieldsForConstraintSet(currentSetName).forEach {
+ fieldName ->
+ val template =
+ if (fieldName == KeyWords.Extends) JsonStringValueTemplate else JsonNewObjectTemplate
result.addLookupElement(lookupString = fieldName, tailText = null, template)
}
}
}
/**
- * Autocomplete options with the names of all available ConstraintSets, except from the one the autocomplete was invoked from.
+ * Autocomplete options with the names of all available ConstraintSets, except from the one the
+ * autocomplete was invoked from.
*/
internal object ConstraintSetNamesProvider : BaseConstraintSetsCompletionProvider() {
override fun addCompletions(
@@ -133,7 +149,8 @@
parameters: CompletionParameters,
result: CompletionResultSet
) {
- val currentConstraintSet = ConstraintSetModel.getModelForCompletionOnConstraintSetProperty(parameters)
+ val currentConstraintSet =
+ ConstraintSetModel.getModelForCompletionOnConstraintSetProperty(parameters)
val currentSetName = currentConstraintSet?.name
val names = constraintSetsPropertyModel.getConstraintSetNames().toMutableSet()
if (currentSetName != null) {
@@ -144,7 +161,8 @@
}
/**
- * Autocomplete options used to define the constraints of a widget (defined by the ID) within a ConstraintSet
+ * Autocomplete options used to define the constraints of a widget (defined by the ID) within a
+ * ConstraintSet
*/
internal object ConstraintsProvider : BaseConstraintSetsCompletionProvider() {
override fun addCompletions(
@@ -152,11 +170,16 @@
parameters: CompletionParameters,
result: CompletionResultSet
) {
- val parentPropertyModel = JsonPropertyModel.getModelForCompletionOnInnerJsonProperty(parameters) ?: return
+ val parentPropertyModel =
+ JsonPropertyModel.getModelForCompletionOnInnerJsonProperty(parameters) ?: return
val existingFieldsSet = parentPropertyModel.declaredFieldNamesSet
StandardAnchor.values().forEach {
if (!existingFieldsSet.contains(it.keyWord)) {
- result.addLookupElement(lookupString = it.keyWord, tailText = " [...]", format = ConstrainAnchorTemplate)
+ result.addLookupElement(
+ lookupString = it.keyWord,
+ tailText = " [...]",
+ format = ConstrainAnchorTemplate
+ )
}
}
if (!existingFieldsSet.contains(KeyWords.Visibility)) {
@@ -167,13 +190,16 @@
result.addEnumKeyWordsWithNumericValueTemplate<RenderTransform>(existingFieldsSet)
// Complete 'clear' if the containing ConstraintSet has `extendsFrom`
- val containingConstraintSetModel = parentPropertyModel.getParentProperty()?.let {
- ConstraintSetModel(it)
- }
+ val containingConstraintSetModel =
+ parentPropertyModel.getParentProperty()?.let { ConstraintSetModel(it) }
if (containingConstraintSetModel?.extendsFrom != null) {
// Add an option with an empty string array and another one with all clear options
result.addLookupElement(lookupString = KeyWords.Clear, format = JsonStringArrayTemplate)
- result.addLookupElement(lookupString = KeyWords.Clear, format = ClearAllTemplate, tailText = " [<all>]")
+ result.addLookupElement(
+ lookupString = KeyWords.Clear,
+ format = ClearAllTemplate,
+ tailText = " [<all>]"
+ )
}
}
}
@@ -181,8 +207,8 @@
/**
* Provides IDs when autocompleting a constraint array.
*
- * The ID may be either 'parent' or any of the declared IDs in all ConstraintSets, except the ID of the constraints block from which this
- * provider was invoked.
+ * The ID may be either 'parent' or any of the declared IDs in all ConstraintSets, except the ID of
+ * the constraints block from which this provider was invoked.
*/
internal object ConstraintIdsProvider : BaseConstraintSetsCompletionProvider() {
override fun addCompletions(
@@ -190,22 +216,22 @@
parameters: CompletionParameters,
result: CompletionResultSet
) {
- val possibleIds = constraintSetsPropertyModel.constraintSets.flatMap { it.declaredIds }.toCollection(HashSet())
+ val possibleIds =
+ constraintSetsPropertyModel.constraintSets.flatMap { it.declaredIds }.toCollection(HashSet())
// Parent ID should always be present
possibleIds.add(KeyWords.ParentId)
// Remove the current ID
getJsonPropertyParent(parameters)?.name?.let(possibleIds::remove)
- possibleIds.forEach { id ->
- result.addLookupElement(lookupString = id)
- }
+ possibleIds.forEach { id -> result.addLookupElement(lookupString = id) }
}
}
/**
* Provides the appropriate anchors when completing a constraint array.
*
- * [StandardAnchor.verticalAnchors] can only be constrained to other vertical anchors. Same logic for [StandardAnchor.horizontalAnchors].
+ * [StandardAnchor.verticalAnchors] can only be constrained to other vertical anchors. Same logic
+ * for [StandardAnchor.horizontalAnchors].
*/
internal object AnchorablesProvider : BaseConstraintSetsCompletionProvider() {
override fun addCompletions(
@@ -213,13 +239,15 @@
parameters: CompletionParameters,
result: CompletionResultSet
) {
- val currentAnchorKeyWord = parameters.position.parentOfType<JsonProperty>(withSelf = true)?.name ?: return
+ val currentAnchorKeyWord =
+ parameters.position.parentOfType<JsonProperty>(withSelf = true)?.name ?: return
- val possibleAnchors = when {
- StandardAnchor.isVertical(currentAnchorKeyWord) -> StandardAnchor.verticalAnchors
- StandardAnchor.isHorizontal(currentAnchorKeyWord) -> StandardAnchor.horizontalAnchors
- else -> emptyList()
- }
+ val possibleAnchors =
+ when {
+ StandardAnchor.isVertical(currentAnchorKeyWord) -> StandardAnchor.verticalAnchors
+ StandardAnchor.isHorizontal(currentAnchorKeyWord) -> StandardAnchor.horizontalAnchors
+ else -> emptyList()
+ }
possibleAnchors.forEach { result.addLookupElement(lookupString = it.keyWord) }
}
}
@@ -235,10 +263,14 @@
parameters: CompletionParameters,
result: CompletionResultSet
) {
- val existing = parameters.position.parentOfType<JsonArray>(withSelf = false)?.valueList
- ?.filterIsInstance<JsonStringLiteral>()
- ?.map { it.value }
- ?.toSet() ?: emptySet()
+ val existing =
+ parameters.position
+ .parentOfType<JsonArray>(withSelf = false)
+ ?.valueList
+ ?.filterIsInstance<JsonStringLiteral>()
+ ?.map { it.value }
+ ?.toSet()
+ ?: emptySet()
addEnumKeywords<ClearOption>(result, existing)
}
}
@@ -249,8 +281,13 @@
* @see TransitionField
*/
internal object TransitionFieldsProvider : CompletionProvider<CompletionParameters>() {
- override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
- val parentPropertyModel = JsonPropertyModel.getModelForCompletionOnInnerJsonProperty(parameters) ?: return
+ override fun addCompletions(
+ parameters: CompletionParameters,
+ context: ProcessingContext,
+ result: CompletionResultSet
+ ) {
+ val parentPropertyModel =
+ JsonPropertyModel.getModelForCompletionOnInnerJsonProperty(parameters) ?: return
TransitionField.values().forEach {
if (parentPropertyModel.containsPropertyOfName(it.keyWord)) {
// skip
@@ -275,9 +312,16 @@
* @see OnSwipeField
*/
internal object OnSwipeFieldsProvider : CompletionProvider<CompletionParameters>() {
- override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
- val parentPropertyModel = JsonPropertyModel.getModelForCompletionOnInnerJsonProperty(parameters) ?: return
- result.addEnumKeyWordsWithStringValueTemplate<OnSwipeField>(parentPropertyModel.declaredFieldNamesSet)
+ override fun addCompletions(
+ parameters: CompletionParameters,
+ context: ProcessingContext,
+ result: CompletionResultSet
+ ) {
+ val parentPropertyModel =
+ JsonPropertyModel.getModelForCompletionOnInnerJsonProperty(parameters) ?: return
+ result.addEnumKeyWordsWithStringValueTemplate<OnSwipeField>(
+ parentPropertyModel.declaredFieldNamesSet
+ )
}
}
@@ -287,8 +331,13 @@
* @see KeyFrameField
*/
internal object KeyFramesFieldsProvider : CompletionProvider<CompletionParameters>() {
- override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
- val parentPropertyModel = JsonPropertyModel.getModelForCompletionOnInnerJsonProperty(parameters) ?: return
+ override fun addCompletions(
+ parameters: CompletionParameters,
+ context: ProcessingContext,
+ result: CompletionResultSet
+ ) {
+ val parentPropertyModel =
+ JsonPropertyModel.getModelForCompletionOnInnerJsonProperty(parameters) ?: return
addEnumKeywords<KeyFrameField>(
result = result,
format = JsonObjectArrayTemplate,
@@ -298,21 +347,29 @@
}
/**
- * Provides completion for the fields of KeyFrame children. A KeyFrame child can be any of [KeyFrameField].
+ * Provides completion for the fields of KeyFrame children. A KeyFrame child can be any of
+ * [KeyFrameField].
*/
internal object KeyFrameChildFieldsCompletionProvider : CompletionProvider<CompletionParameters>() {
- override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
- // TODO(b/207030860): For consistency, make it so that JsonPropertyModel may be used here. It currently won't work because the model
+ override fun addCompletions(
+ parameters: CompletionParameters,
+ context: ProcessingContext,
+ result: CompletionResultSet
+ ) {
+ // TODO(b/207030860): For consistency, make it so that JsonPropertyModel may be used here. It
+ // currently won't work because the model
// doesn't consider a property defined by an array of objects.
// Obtain existing list of existing properties
val parentObject = parameters.position.parentOfType<JsonObject>(withSelf = false) ?: return
val existingFieldsSet = parentObject.propertyList.map { it.name }.toSet()
- // We have to know the type of KeyFrame we are autocompleting for (KeyPositions, KeyAttributes, etc)
+ // We have to know the type of KeyFrame we are autocompleting for (KeyPositions, KeyAttributes,
+ // etc)
val keyFrameTypeName = parentObject.parentOfType<JsonProperty>(withSelf = false)?.name ?: return
- // Look for the `frames` property, we want to know the size of its array (if present), since all other numeric properties should have an
+ // Look for the `frames` property, we want to know the size of its array (if present), since all
+ // other numeric properties should have an
// array of the same size
val framesProperty = parentObject.findProperty(KeyFrameChildCommonField.Frames.keyWord)
val arrayCountInFramesProperty = (framesProperty?.value as? JsonArray)?.valueList?.size ?: 1
@@ -329,12 +386,14 @@
return@forEach
}
when (it) {
- KeyFrameChildCommonField.Frames -> result.addLookupElement(lookupString = it.keyWord, format = jsonNumberArrayTemplate)
+ KeyFrameChildCommonField.Frames ->
+ result.addLookupElement(lookupString = it.keyWord, format = jsonNumberArrayTemplate)
else -> result.addLookupElement(lookupString = it.keyWord, format = JsonStringValueTemplate)
}
}
- // Figure out which type of KeyFrame the completion is being called on, and offer completion for their respective fields
+ // Figure out which type of KeyFrame the completion is being called on, and offer completion for
+ // their respective fields
when (keyFrameTypeName) {
KeyFrameField.Positions.keyWord -> {
addKeyPositionFields(result, existingFieldsSet) {
@@ -344,12 +403,24 @@
}
KeyFrameField.Attributes.keyWord -> {
// KeyAttributes properties are the same as the RenderTransform fields
- addEnumKeywords<RenderTransform>(result = result, format = jsonNumberArrayTemplate, existing = existingFieldsSet)
+ addEnumKeywords<RenderTransform>(
+ result = result,
+ format = jsonNumberArrayTemplate,
+ existing = existingFieldsSet
+ )
}
KeyFrameField.Cycles.keyWord -> {
// KeyCycles properties are a mix of RenderTransform fields and KeyCycles specific fields
- addEnumKeywords<RenderTransform>(result = result, format = jsonNumberArrayTemplate, existing = existingFieldsSet)
- addEnumKeywords<KeyCycleField>(result = result, format = jsonNumberArrayTemplate, existing = existingFieldsSet)
+ addEnumKeywords<RenderTransform>(
+ result = result,
+ format = jsonNumberArrayTemplate,
+ existing = existingFieldsSet
+ )
+ addEnumKeywords<KeyCycleField>(
+ result = result,
+ format = jsonNumberArrayTemplate,
+ existing = existingFieldsSet
+ )
}
else -> {
thisLogger().warn("Completion on unknown KeyFrame type: $keyFrameTypeName")
@@ -358,7 +429,8 @@
}
/**
- * Add LookupElements to the [result] for each non-repeated [KeyPositionField] using the [InsertionFormat] returned by [templateProvider].
+ * Add LookupElements to the [result] for each non-repeated [KeyPositionField] using the
+ * [InsertionFormat] returned by [templateProvider].
*/
private fun addKeyPositionFields(
result: CompletionResultSet,
@@ -370,7 +442,10 @@
// Skip repeated fields
return@forEach
}
- result.addLookupElement(lookupString = keyPositionField.keyWord, format = templateProvider(keyPositionField))
+ result.addLookupElement(
+ lookupString = keyPositionField.keyWord,
+ format = templateProvider(keyPositionField)
+ )
}
}
@@ -390,17 +465,20 @@
*
* The provided values come from [ConstraintLayoutKeyWord.keyWord].
*/
-internal class EnumValuesCompletionProvider<E>(private val enumClass: KClass<E>)
- : CompletionProvider<CompletionParameters>() where E : Enum<E>, E : ConstraintLayoutKeyWord {
- override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
- enumClass.java.enumConstants.forEach {
- result.addLookupElement(lookupString = it.keyWord)
- }
+internal class EnumValuesCompletionProvider<E>(private val enumClass: KClass<E>) :
+ CompletionProvider<CompletionParameters>() where E : Enum<E>, E : ConstraintLayoutKeyWord {
+ override fun addCompletions(
+ parameters: CompletionParameters,
+ context: ProcessingContext,
+ result: CompletionResultSet
+ ) {
+ enumClass.java.enumConstants.forEach { result.addLookupElement(lookupString = it.keyWord) }
}
}
/**
- * Add the [ConstraintLayoutKeyWord.keyWord] of the enum constants as a completion result that takes a string for its value.
+ * Add the [ConstraintLayoutKeyWord.keyWord] of the enum constants as a completion result that takes
+ * a string for its value.
*/
private inline fun <reified E> CompletionResultSet.addEnumKeyWordsWithStringValueTemplate(
existing: Set<String>
@@ -409,7 +487,8 @@
}
/**
- * Add the [ConstraintLayoutKeyWord.keyWord] of the enum constants as a completion result that takes a number for its value.
+ * Add the [ConstraintLayoutKeyWord.keyWord] of the enum constants as a completion result that takes
+ * a number for its value.
*/
private inline fun <reified E> CompletionResultSet.addEnumKeyWordsWithNumericValueTemplate(
existing: Set<String>
@@ -417,9 +496,7 @@
addEnumKeywords<E>(result = this, existing = existing, format = JsonNumericValueTemplate)
}
-/**
- * Helper function to simplify adding enum constant members to the completion result.
- */
+/** Helper function to simplify adding enum constant members to the completion result. */
private inline fun <reified E> addEnumKeywords(
result: CompletionResultSet,
existing: Set<String> = emptySet(),
@@ -430,4 +507,4 @@
result.addLookupElement(lookupString = constant.keyWord, format = format)
}
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/BaseJsonElementModel.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/BaseJsonElementModel.kt
index 16e74dd..a17aa03 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/BaseJsonElementModel.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/BaseJsonElementModel.kt
@@ -25,53 +25,45 @@
import com.intellij.psi.util.parentOfType
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
-/**
- * Base model for [JsonElement], sets a pointer to avoid holding to the element itself.
- */
-internal abstract class BaseJsonElementModel<E: JsonElement>(element: E) {
+/** Base model for [JsonElement], sets a pointer to avoid holding to the element itself. */
+internal abstract class BaseJsonElementModel<E : JsonElement>(element: E) {
protected val elementPointer = SmartPointerManager.createPointer(element)
}
/**
* Base model for a [JsonProperty].
*
- * Populates some common fields and provides useful function while avoiding holding to PsiElement instances.
+ * Populates some common fields and provides useful function while avoiding holding to PsiElement
+ * instances.
*/
-internal open class JsonPropertyModel(element: JsonProperty): BaseJsonElementModel<JsonProperty>(element) {
- /**
- * The [JsonObject] that describes this [JsonProperty].
- */
+internal open class JsonPropertyModel(element: JsonProperty) :
+ BaseJsonElementModel<JsonProperty>(element) {
+ /** The [JsonObject] that describes this [JsonProperty]. */
private val innerJsonObject: JsonObject? = elementPointer.element?.getChildOfType<JsonObject>()
- /**
- * A mapping of the containing [JsonProperty]s by their declare name.
- */
+ /** A mapping of the containing [JsonProperty]s by their declare name. */
private val propertiesByName: Map<String, JsonProperty> =
innerJsonObject?.propertyList?.associateBy { it.name } ?: emptyMap()
- /**
- * [List] of all the children of this element that are [JsonProperty].
- */
+ /** [List] of all the children of this element that are [JsonProperty]. */
protected val innerProperties: Collection<JsonProperty> = propertiesByName.values
- /**
- * Name of the [JsonProperty].
- */
+ /** Name of the [JsonProperty]. */
val name: String?
get() = elementPointer.element?.name
- /**
- * A set of names for all declared properties in this [JsonProperty].
- */
+ /** A set of names for all declared properties in this [JsonProperty]. */
val declaredFieldNamesSet: Set<String> = propertiesByName.keys
/**
- * For the children of the current element, returns the [JsonProperty] which name matches the given [name]. Null if none of them does.
+ * For the children of the current element, returns the [JsonProperty] which name matches the
+ * given [name]. Null if none of them does.
*/
protected fun findProperty(name: String): JsonProperty? = propertiesByName[name]
/**
- * Returns true if this [JsonProperty] contains another [JsonProperty] declared by the given [name].
+ * Returns true if this [JsonProperty] contains another [JsonProperty] declared by the given
+ * [name].
*/
fun containsPropertyOfName(name: String): Boolean = propertiesByName.containsKey(name)
@@ -80,22 +72,26 @@
*
* May return null if this model is for a top level [JsonProperty].
*/
- fun getParentProperty(): JsonProperty? = elementPointer.element?.parentOfType<JsonProperty>(withSelf = false)
+ fun getParentProperty(): JsonProperty? =
+ elementPointer.element?.parentOfType<JsonProperty>(withSelf = false)
companion object {
/**
- * Returns the [JsonPropertyModel] where the completion is performed on an inner [JsonProperty], including if the completion is on the
- * value side of the inner [JsonProperty].
+ * Returns the [JsonPropertyModel] where the completion is performed on an inner [JsonProperty],
+ * including if the completion is on the value side of the inner [JsonProperty].
*
- * In other words, the model of the second [JsonProperty] parent if the element on [CompletionParameters.getPosition] is NOT a
- * [JsonProperty].
+ * In other words, the model of the second [JsonProperty] parent if the element on
+ * [CompletionParameters.getPosition] is NOT a [JsonProperty].
*
- * Or the model of the first [JsonProperty] parent if the element on [CompletionParameters.getPosition] is a [JsonProperty].
+ * Or the model of the first [JsonProperty] parent if the element on
+ * [CompletionParameters.getPosition] is a [JsonProperty].
*/
- fun getModelForCompletionOnInnerJsonProperty(parameters: CompletionParameters): JsonPropertyModel? {
+ fun getModelForCompletionOnInnerJsonProperty(
+ parameters: CompletionParameters
+ ): JsonPropertyModel? {
val parentJsonProperty = getJsonPropertyParent(parameters) ?: return null
ProgressManager.checkCanceled()
return JsonPropertyModel(parentJsonProperty)
}
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetModel.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetModel.kt
index 95fd5db..fc22454 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetModel.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetModel.kt
@@ -28,19 +28,13 @@
* A ConstraintSet is a state that defines a specific layout of the contents in a ConstraintLayout.
*/
internal class ConstraintSetModel(jsonProperty: JsonProperty) : JsonPropertyModel(jsonProperty) {
- /**
- * List of properties that have a constraint block assigned to it.
- */
+ /** List of properties that have a constraint block assigned to it. */
private val propertiesWithConstraints = innerProperties.filter { it.name != KeyWords.Extends }
- /**
- * Name of the ConstraintSet this is extending constraints from.
- */
+ /** Name of the ConstraintSet this is extending constraints from. */
val extendsFrom: String? = (findProperty(KeyWords.Extends)?.value as? JsonStringLiteral)?.value
- /**
- * List of IDs declared in this ConstraintSet.
- */
+ /** List of IDs declared in this ConstraintSet. */
val declaredIds = propertiesWithConstraints.map { it.name }
/**
@@ -49,22 +43,23 @@
* Note that it does not resolve constraints inherited from [extendsFrom].
*/
val constraintsById: Map<String, ConstraintsModel> =
- propertiesWithConstraints.associate { property ->
- property.name to ConstraintsModel(property)
- }
+ propertiesWithConstraints.associate { property -> property.name to ConstraintsModel(property) }
- // TODO(b/207030860): Add a method that can pull all resolved constraints for each widget ID, it could be useful to make sure we are not
+ // TODO(b/207030860): Add a method that can pull all resolved constraints for each widget ID, it
+ // could be useful to make sure we are not
// offering options that are implicitly present from the 'Extends' ConstraintSet
companion object {
/**
- * Returns a [ConstraintSetModel], for when the completion is performed on a property or the value of a property within a ConstraintSet
- * declaration.
+ * Returns a [ConstraintSetModel], for when the completion is performed on a property or the
+ * value of a property within a ConstraintSet declaration.
*/
- fun getModelForCompletionOnConstraintSetProperty(parameters: CompletionParameters): ConstraintSetModel? {
+ fun getModelForCompletionOnConstraintSetProperty(
+ parameters: CompletionParameters
+ ): ConstraintSetModel? {
val parentJsonProperty = getJsonPropertyParent(parameters) ?: return null
ProgressManager.checkCanceled()
return ConstraintSetModel(parentJsonProperty)
}
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetsPropertyModel.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetsPropertyModel.kt
index 0d16fa7..40ca127 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetsPropertyModel.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetsPropertyModel.kt
@@ -21,31 +21,29 @@
/**
* Model for the `ConstraintSets` Json block.
*
- * The `ConstraintSets` Json block, is a collection of different ConstraintSets, each of which describes a state of the layout by defining
- * properties of each of its widgets such as width, height or their layout constraints.
+ * The `ConstraintSets` Json block, is a collection of different ConstraintSets, each of which
+ * describes a state of the layout by defining properties of each of its widgets such as width,
+ * height or their layout constraints.
*
* @param constraintSetsElement The PSI element of the `ConstraintSets` Json property
*/
-internal class ConstraintSetsPropertyModel(
- constraintSetsElement: JsonProperty
-) : JsonPropertyModel(constraintSetsElement) {
- // TODO(b/209839226): Explore how we could use these models to validate the syntax or structure of the JSON as well as to check logic
+internal class ConstraintSetsPropertyModel(constraintSetsElement: JsonProperty) :
+ JsonPropertyModel(constraintSetsElement) {
+ // TODO(b/209839226): Explore how we could use these models to validate the syntax or structure of
+ // the JSON as well as to check logic
// correctness through Inspections/Lint
- /**
- * List of all ConstraintSet elements in the Json block.
- */
+ /** List of all ConstraintSet elements in the Json block. */
val constraintSets: List<ConstraintSetModel> = innerProperties.map { ConstraintSetModel(it) }
- /**
- * The names of all ConstraintSets in this block.
- */
+ /** The names of all ConstraintSets in this block. */
fun getConstraintSetNames(): Collection<String> {
return declaredFieldNamesSet
}
/**
- * Returns the remaining possible fields for the given [constraintSetName], this is done by reading all fields in all ConstraintSets and
- * subtracting the fields already present in [constraintSetName]. Most of these should be the IDs that represent constrained widgets.
+ * Returns the remaining possible fields for the given [constraintSetName], this is done by
+ * reading all fields in all ConstraintSets and subtracting the fields already present in
+ * [constraintSetName]. Most of these should be the IDs that represent constrained widgets.
*/
fun getRemainingFieldsForConstraintSet(constraintSetName: String): List<String> {
val availableNames = mutableSetOf(KeyWords.Extends)
@@ -54,8 +52,7 @@
constraintSet.declaredFieldNamesSet.forEach { propertyName ->
if (constraintSet.name == constraintSetName) {
usedNames.add(propertyName)
- }
- else {
+ } else {
availableNames.add(propertyName)
}
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintsModel.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintsModel.kt
index 4370246..b8ea00e 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintsModel.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintsModel.kt
@@ -18,12 +18,14 @@
import com.intellij.json.psi.JsonProperty
/**
- * Model for the JSON block that corresponds to the constraints applied on a widget (defined by an ID).
+ * Model for the JSON block that corresponds to the constraints applied on a widget (defined by an
+ * ID).
*
- * Constraints are a set of instructions that define the widget's dimensions, position with respect to other widgets and render-time
- * transforms.
+ * Constraints are a set of instructions that define the widget's dimensions, position with respect
+ * to other widgets and render-time transforms.
*/
-internal class ConstraintsModel(jsonProperty: JsonProperty): JsonPropertyModel(jsonProperty) {
- // TODO(b/207030860): Fill the contents of this model as is necessary, keeping in mind that it would be useful to have fields like
+internal class ConstraintsModel(jsonProperty: JsonProperty) : JsonPropertyModel(jsonProperty) {
+ // TODO(b/207030860): Fill the contents of this model as is necessary, keeping in mind that it
+ // would be useful to have fields like
// 'verticalConstraints', 'hasBaseline', 'dimensionBehavior', etc...
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeDebuggerClassesFilterProvider.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeDebuggerClassesFilterProvider.kt
index d1a1e7f..2ca5c53 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeDebuggerClassesFilterProvider.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeDebuggerClassesFilterProvider.kt
@@ -24,6 +24,7 @@
}
override fun getFilters(): List<ClassFilter> {
- return if (ComposeDebuggerSettings.getInstance().filterComposeRuntimeClasses) FILTERS else listOf()
+ return if (ComposeDebuggerSettings.getInstance().filterComposeRuntimeClasses) FILTERS
+ else listOf()
}
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeDebuggerSettings.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeDebuggerSettings.kt
index 5780f64..1109a69 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeDebuggerSettings.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeDebuggerSettings.kt
@@ -26,11 +26,9 @@
import com.intellij.xdebugger.settings.DebuggerSettingsCategory
import com.intellij.xdebugger.settings.XDebuggerSettings
-@State(
- name = "ComposeDebuggerSettings",
- storages = [Storage("compose.debug.xml")]
-)
-class ComposeDebuggerSettings : XDebuggerSettings<ComposeDebuggerSettings>("compose_debugger"), Getter<ComposeDebuggerSettings> {
+@State(name = "ComposeDebuggerSettings", storages = [Storage("compose.debug.xml")])
+class ComposeDebuggerSettings :
+ XDebuggerSettings<ComposeDebuggerSettings>("compose_debugger"), Getter<ComposeDebuggerSettings> {
var filterComposeRuntimeClasses: Boolean = true
companion object {
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeFunctionBreakpoint.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeFunctionBreakpoint.kt
index 30509b6..ba94610 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeFunctionBreakpoint.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeFunctionBreakpoint.kt
@@ -23,14 +23,14 @@
import org.jetbrains.kotlin.idea.debugger.core.breakpoints.SourcePositionRefiner
/**
- * A [com.intellij.debugger.ui.breakpoints.MethodBreakpoint] that supports `@Composable` function breakpoints
+ * A [com.intellij.debugger.ui.breakpoints.MethodBreakpoint] that supports `@Composable` function
+ * breakpoints
*/
-internal class ComposeFunctionBreakpoint(
- project: Project,
- breakpoint: XBreakpoint<*>
-) : KotlinFunctionBreakpoint(project, breakpoint), SourcePositionRefiner {
+internal class ComposeFunctionBreakpoint(project: Project, breakpoint: XBreakpoint<*>) :
+ KotlinFunctionBreakpoint(project, breakpoint), SourcePositionRefiner {
override fun isMethodMatch(method: Method, debugProcess: DebugProcessImpl) =
- method.name() == methodName && method.signature().withoutComposeArgs() == mySignature?.getName(debugProcess)
+ method.name() == methodName &&
+ method.signature().withoutComposeArgs() == mySignature?.getName(debugProcess)
}
private fun String.withoutComposeArgs(): String {
@@ -40,4 +40,4 @@
return this
}
return substring(0, start) + substring(end)
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeFunctionBreakpointHandlerFactory.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeFunctionBreakpointHandlerFactory.kt
index dd5d38b..bd28add 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeFunctionBreakpointHandlerFactory.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeFunctionBreakpointHandlerFactory.kt
@@ -19,11 +19,10 @@
import com.intellij.debugger.engine.JavaBreakpointHandler
import com.intellij.debugger.engine.JavaBreakpointHandlerFactory
-/**
- * A [JavaBreakpointHandlerFactory] for [ComposeFunctionBreakpoint]
- */
+/** A [JavaBreakpointHandlerFactory] for [ComposeFunctionBreakpoint] */
internal class ComposeFunctionBreakpointHandlerFactory : JavaBreakpointHandlerFactory {
- override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler = ComposeFunctionBreakpointHandler(process)
+ override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler =
+ ComposeFunctionBreakpointHandler(process)
private class ComposeFunctionBreakpointHandler(process: DebugProcessImpl) :
JavaBreakpointHandler(ComposeFunctionBreakpointType::class.java, process)
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeFunctionBreakpointType.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeFunctionBreakpointType.kt
index c0b0e80..a593f89 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeFunctionBreakpointType.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/ComposeFunctionBreakpointType.kt
@@ -29,23 +29,29 @@
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtPsiUtil
-/**
- * A [com.intellij.debugger.ui.breakpoints.JavaMethodBreakpointType] for `@Composable` functions
- */
+/** A [com.intellij.debugger.ui.breakpoints.JavaMethodBreakpointType] for `@Composable` functions */
internal class ComposeFunctionBreakpointType :
- KotlinFunctionBreakpointType("compose-function", ComposeBundle.message("compose.breakpoint.description")) {
+ KotlinFunctionBreakpointType(
+ "compose-function",
+ ComposeBundle.message("compose.breakpoint.description")
+ ) {
- override fun createJavaBreakpoint(project: Project, breakpoint: XBreakpoint<JavaMethodBreakpointProperties>): KotlinFunctionBreakpoint =
- ComposeFunctionBreakpoint(project, breakpoint)
+ override fun createJavaBreakpoint(
+ project: Project,
+ breakpoint: XBreakpoint<JavaMethodBreakpointProperties>
+ ): KotlinFunctionBreakpoint = ComposeFunctionBreakpoint(project, breakpoint)
- override fun isFunctionBreakpointApplicable(file: VirtualFile, line: Int, project: Project): Boolean =
+ override fun isFunctionBreakpointApplicable(
+ file: VirtualFile,
+ line: Int,
+ project: Project
+ ): Boolean =
isBreakpointApplicable(file, line, project) { element ->
when (element) {
is KtFunction ->
ApplicabilityResult.maybe(
- !KtPsiUtil.isLocal(element) &&
- !element.isInlineOnly() &&
- element.isComposable())
+ !KtPsiUtil.isLocal(element) && !element.isInlineOnly() && element.isComposable()
+ )
else -> ApplicabilityResult.UNKNOWN
}
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/ComposePositionManager.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/ComposePositionManager.kt
index f0f23a0..5c5969f 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/ComposePositionManager.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/ComposePositionManager.kt
@@ -41,33 +41,33 @@
* A PositionManager capable of setting breakpoints inside of ComposableSingleton lambdas.
*
* This class essentially resolves breakpoints for lambdas generated by the compose compiler
- * optimization that was introduced in I8c967b14c5d9bf67e5646e60f630f2e29e006366
- * The default [KotlinPositionManager] only locates source positions in enclosing and nested
- * classes, while composable singleton lambdas are cached in a separate top-level class.
+ * optimization that was introduced in I8c967b14c5d9bf67e5646e60f630f2e29e006366 The default
+ * [KotlinPositionManager] only locates source positions in enclosing and nested classes, while
+ * composable singleton lambdas are cached in a separate top-level class.
*
* See https://issuetracker.google.com/190373291 for more information.
*/
class ComposePositionManager(
private val debugProcess: DebugProcess,
private val kotlinPositionManager: KotlinPositionManager
-) : MultiRequestPositionManager by kotlinPositionManager, PositionManagerWithMultipleStackFrames {
+) : MultiRequestPositionManager by kotlinPositionManager, PositionManagerWithMultipleStackFrames {
override fun getAcceptedFileTypes(): Set<FileType> = KOTLIN_FILE_TYPES
override fun createStackFrames(descriptor: StackFrameDescriptorImpl): List<XStackFrame> =
kotlinPositionManager.createStackFrames(descriptor)
- override fun evaluateCondition(context: EvaluationContext,
- frame: StackFrameProxyImpl,
- location: Location,
- expression: String): ThreeState =
- kotlinPositionManager.evaluateCondition(context, frame, location, expression)
+ override fun evaluateCondition(
+ context: EvaluationContext,
+ frame: StackFrameProxyImpl,
+ location: Location,
+ expression: String
+ ): ThreeState = kotlinPositionManager.evaluateCondition(context, frame, location, expression)
/**
* Returns all prepared classes which could contain the given classPosition.
*
- * This handles the case where a user sets a breakpoint in a ComposableSingleton
- * lambda after the debug process has already initialized the corresponding
- * `lambda-n` class.
+ * This handles the case where a user sets a breakpoint in a ComposableSingleton lambda after the
+ * debug process has already initialized the corresponding `lambda-n` class.
*/
override fun getAllClasses(classPosition: SourcePosition): List<ReferenceType> {
val file = classPosition.file
@@ -79,9 +79,10 @@
}
val vm = debugProcess.virtualMachineProxy
- val singletonClasses = vm.classesByName(computeComposableSingletonsClassName(file)).flatMap { referenceType ->
- if (referenceType.isPrepared) allRecursivelyNestedTypesOf(referenceType) else listOf()
- }
+ val singletonClasses =
+ vm.classesByName(computeComposableSingletonsClassName(file)).flatMap { referenceType ->
+ if (referenceType.isPrepared) allRecursivelyNestedTypesOf(referenceType) else listOf()
+ }
if (singletonClasses.isEmpty()) {
throw NoDataException.INSTANCE
@@ -112,7 +113,10 @@
* the given `position`, but may not be loaded yet. The `requestor` will be called for any newly
* prepared class which matches any of the created search patterns.
*/
- override fun createPrepareRequests(requestor: ClassPrepareRequestor, position: SourcePosition): List<ClassPrepareRequest> {
+ override fun createPrepareRequests(
+ requestor: ClassPrepareRequestor,
+ position: SourcePosition
+ ): List<ClassPrepareRequest> {
val file = position.file
if (file !is KtFile) {
throw NoDataException.INSTANCE
@@ -123,32 +127,37 @@
// in order to locate breakpoints in ordinary Kotlin code.
val kotlinRequests = kotlinPositionManager.createPrepareRequests(requestor, position)
- val singletonRequest = debugProcess.requestsManager.createClassPrepareRequest(
- requestor,
- "${computeComposableSingletonsClassName(file)}\$*"
- )
+ val singletonRequest =
+ debugProcess.requestsManager.createClassPrepareRequest(
+ requestor,
+ "${computeComposableSingletonsClassName(file)}\$*"
+ )
return if (singletonRequest == null) kotlinRequests else kotlinRequests + singletonRequest
}
/**
- * A method from [PositionManager] which was superseded by [createPrepareRequests] in [MultiRequestPositionManager].
- * Intellij code should never call this method for subclasses of [MultiRequestPositionManager].
+ * A method from [PositionManager] which was superseded by [createPrepareRequests] in
+ * [MultiRequestPositionManager]. Intellij code should never call this method for subclasses of
+ * [MultiRequestPositionManager].
*/
- override fun createPrepareRequest(requestor: ClassPrepareRequestor, position: SourcePosition): ClassPrepareRequest? {
+ override fun createPrepareRequest(
+ requestor: ClassPrepareRequestor,
+ position: SourcePosition
+ ): ClassPrepareRequest? {
return createPrepareRequests(requestor, position).firstOrNull()
}
/**
* Compute the name of the ComposableSingletons class for the given file.
*
- * The Compose compiler plugin creates per-file ComposableSingletons classes to cache
- * composable lambdas without captured variables. We need to locate these classes in order
- * to search them for breakpoint locations.
+ * The Compose compiler plugin creates per-file ComposableSingletons classes to cache composable
+ * lambdas without captured variables. We need to locate these classes in order to search them for
+ * breakpoint locations.
*
- * NOTE: The pattern for ComposableSingletons classes needs to be kept in sync with the
- * code in `ComposerLambdaMemoization.getOrCreateComposableSingletonsClass`.
- * The optimization was introduced in I8c967b14c5d9bf67e5646e60f630f2e29e006366
+ * NOTE: The pattern for ComposableSingletons classes needs to be kept in sync with the code in
+ * `ComposerLambdaMemoization.getOrCreateComposableSingletonsClass`. The optimization was
+ * introduced in I8c967b14c5d9bf67e5646e60f630f2e29e006366
*/
private fun computeComposableSingletonsClassName(file: KtFile): String {
// The code in `ComposerLambdaMemoization` always uses the file short name and
@@ -157,7 +166,8 @@
val filePath = file.virtualFile?.path ?: file.name
val fileName = filePath.split('/').last()
val shortName = PackagePartClassUtils.getFilePartShortName(fileName)
- val fileClassFqName = runReadAction { JvmFileClassUtil.getFileClassInfoNoResolve(file) }.facadeClassFqName
+ val fileClassFqName =
+ runReadAction { JvmFileClassUtil.getFileClassInfoNoResolve(file) }.facadeClassFqName
return "${fileClassFqName.parent().asString()}.ComposableSingletons\$$shortName"
}
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ComposeStateNode.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ComposeStateNode.kt
index 05d8c00..df3001b 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ComposeStateNode.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ComposeStateNode.kt
@@ -26,9 +26,9 @@
import kotlin.collections.Map.Entry
/**
- * A [XNamedValue] representing the recomposition state of the enclosing Composable function/lambda
+ * A [XNamedValue] representing the recomposition state of the enclosing Composable function/lambda
*
- * The node looks something like this:
+ * The node looks something like this:
*
* ```
* + Recomposition State = Composable fun FunctionName(): Arguments: Different: ["arg1"] Same: ["arg2", "this"]
@@ -39,8 +39,9 @@
* + this = Same
* + value = <value of this>
* ```
- * Which means, for this recomposition, the value `arg1` has changed from the last time the Composable was composed. `arg2` & `this` have
- * not changed.
+ *
+ * Which means, for this recomposition, the value `arg1` has changed from the last time the
+ * Composable was composed. `arg2` & `this` have not changed.
*/
internal class ComposeStateNode(
private val evaluationContext: EvaluationContextImpl,
@@ -70,7 +71,8 @@
}
private fun getStateSummary(): String {
- fun Entry<ParamState, List<StateObject>>.toSummary() = "${key.name}: [${value.joinToString { """"${it.name}"""" }}]"
+ fun Entry<ParamState, List<StateObject>>.toSummary() =
+ "${key.name}: [${value.joinToString { """"${it.name}"""" }}]"
val summary = stateObjects.groupBy { it.state }.entries.joinToString { it.toSummary() }
return ComposeBundle.message("recomposition.state.summary", summary)
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ComposeValueContributor.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ComposeValueContributor.kt
index c5a53f3..c1bf664 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ComposeValueContributor.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ComposeValueContributor.kt
@@ -36,6 +36,7 @@
import com.intellij.xdebugger.frame.XNamedValue
import com.sun.jdi.IntegerValue
import com.sun.jdi.Location
+import java.util.concurrent.CancellationException
import org.jetbrains.kotlin.idea.codeinsight.utils.findExistingEditor
import org.jetbrains.kotlin.idea.debugger.core.stackFrame.KotlinStackFrame
import org.jetbrains.kotlin.idea.debugger.core.stackFrame.KotlinStackFrameValueContributor
@@ -43,15 +44,12 @@
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtFunctionLiteral
import org.jetbrains.kotlin.psi.KtNamedFunction
-import java.util.concurrent.CancellationException
private const val COMPOSER_VAR = "\$composer"
private const val CHANGED_VAR = "\$changed"
private const val DIRTY_VAR = "\$dirty"
-/**
- * Contributes Compose specific information to the debugger variable view.
- */
+/** Contributes Compose specific information to the debugger variable view. */
internal class ComposeValueContributor : KotlinStackFrameValueContributor {
override fun contributeValues(
frame: KotlinStackFrame,
@@ -87,7 +85,15 @@
if (nodeManager == null) {
thisLogger().warn("Unable to add $COMPOSER_VAR. nodeManager is null")
} else {
- values.add(JavaValue.create(null, LocalVariableDescriptorImpl(context.project, composer), context, nodeManager, false))
+ values.add(
+ JavaValue.create(
+ null,
+ LocalVariableDescriptorImpl(context.project, composer),
+ context,
+ nodeManager,
+ false
+ )
+ )
}
}
@@ -99,7 +105,8 @@
val stateObjects = mutableListOf<StateObject>()
- // ComposeValueContributor.contributeValues() is called with "variables == thisVariables + otherVariables" so if there is a "this"
+ // ComposeValueContributor.contributeValues() is called with "variables == thisVariables +
+ // otherVariables" so if there is a "this"
// variable, it's going to be the first one.
val firstParameter = variables.first()
if (firstParameter.name() == "this") {
@@ -108,7 +115,8 @@
}
try {
- val functionInfo = getFunctionInfo(context.debugProcess.positionManager, frame.stackFrameProxy.location())
+ val functionInfo =
+ getFunctionInfo(context.debugProcess.positionManager, frame.stackFrameProxy.location())
// Named parameters
functionInfo.parameters.zip(states.drop(stateObjects.size)).forEach { (param, state) ->
stateObjects.add(Parameter(state, param, variableMap[param]))
@@ -119,8 +127,7 @@
stateObjects.add(ThisObject(states[stateObjects.size]))
}
values.add(ComposeStateNode(context, forced, functionInfo.description, stateObjects))
- }
- catch (e: IllegalStateException) {
+ } catch (e: IllegalStateException) {
thisLogger().error("Error fetching parameters for $frame", e)
values.add(ErrorNode(ComposeBundle.message("recomposition.state.missing.parameters")))
}
@@ -130,12 +137,20 @@
/**
* Finds the parameter names of a function.
*
- * Inspired by [org.jetbrains.kotlin.idea.debugger.coroutine.KotlinVariableNameFinder.findVariableNames].
+ * Inspired by
+ * [org.jetbrains.kotlin.idea.debugger.coroutine.KotlinVariableNameFinder.findVariableNames].
*/
- private fun getFunctionInfo(positionManager: CompoundPositionManager, location: Location): FunctionInfo {
+ private fun getFunctionInfo(
+ positionManager: CompoundPositionManager,
+ location: Location
+ ): FunctionInfo {
return runReadAction {
- val element = positionManager.getSourcePosition(location)?.elementAt ?: throw IllegalStateException("Unable to get source position")
- val function = element.parentOfType<KtFunction>(withSelf = true) ?: throw IllegalStateException("Unable to find KtFunction element")
+ val element =
+ positionManager.getSourcePosition(location)?.elementAt
+ ?: throw IllegalStateException("Unable to get source position")
+ val function =
+ element.parentOfType<KtFunction>(withSelf = true)
+ ?: throw IllegalStateException("Unable to find KtFunction element")
val parameters = function.valueParameters.mapNotNull { it.name }
FunctionInfo(getDescription(function), parameters)
}
@@ -146,18 +161,28 @@
private fun getDescription(function: KtFunction): String {
return when (function) {
- is KtFunctionLiteral -> ComposeBundle.message("recomposition.state.function.description.lambda", getLambdaName(function))
- else -> ComposeBundle.message("recomposition.state.function.description.function", function.nameAsSafeName.asString())
+ is KtFunctionLiteral ->
+ ComposeBundle.message(
+ "recomposition.state.function.description.lambda",
+ getLambdaName(function)
+ )
+ else ->
+ ComposeBundle.message(
+ "recomposition.state.function.description.function",
+ function.nameAsSafeName.asString()
+ )
}
}
/**
- * Search parent hierarchy for either a declaration of a composable function or a call to a composable function.
+ * Search parent hierarchy for either a declaration of a composable function or a call to a
+ * composable function.
*/
private fun getLambdaName(lambda: KtFunctionLiteral): String {
var element: PsiElement = lambda
while (true) {
- element = element.parentOfTypes(KtNamedFunction::class, KtCallExpression::class) ?: return "lambda"
+ element =
+ element.parentOfTypes(KtNamedFunction::class, KtCallExpression::class) ?: return "lambda"
when {
element is KtNamedFunction && element.isComposableFunction() -> return element.getLambdaName()
element is KtCallExpression && element.isTargetComposable() -> return element.getLambdaName()
@@ -167,21 +192,33 @@
private fun KtNamedFunction.getLambdaName() = "lambda@${nameAsSafeName.asString()}"
-private fun KtCallExpression.getLambdaName() = calleeExpression?.let { "lambda@${it.text}" } ?: "lambda"
+private fun KtCallExpression.getLambdaName() =
+ calleeExpression?.let { "lambda@${it.text}" } ?: "lambda"
private fun KtCallExpression.isTargetComposable(): Boolean {
val editor = findExistingEditor() ?: return false
- val target = TargetElementUtil.getInstance().findTargetElement(editor, REFERENCED_ELEMENT_ACCEPTED, startOffset) ?: return false
+ val target =
+ TargetElementUtil.getInstance()
+ .findTargetElement(editor, REFERENCED_ELEMENT_ACCEPTED, startOffset)
+ ?: return false
return target.isComposableFunction()
}
-private fun getParamStates(frame: KotlinStackFrame, variables: List<LocalVariableProxyImpl>): List<ParamState> {
- val vars = (variables.filterByPrefix(DIRTY_VAR).takeIf { it.isNotEmpty() } ?: variables.filterByPrefix(CHANGED_VAR))
+private fun getParamStates(
+ frame: KotlinStackFrame,
+ variables: List<LocalVariableProxyImpl>
+): List<ParamState> {
+ val vars =
+ (variables.filterByPrefix(DIRTY_VAR).takeIf { it.isNotEmpty() }
+ ?: variables.filterByPrefix(CHANGED_VAR))
return ParamState.decode(vars.map { it.intValue(frame) })
}
-private fun LocalVariableProxyImpl.intValue(frame: KotlinStackFrame) = (frame.stackFrameProxy.getValue(this) as IntegerValue).value()
+private fun LocalVariableProxyImpl.intValue(frame: KotlinStackFrame) =
+ (frame.stackFrameProxy.getValue(this) as IntegerValue).value()
-private fun List<LocalVariableProxyImpl>.filterByPrefix(prefix: String) = filter { it.name().startsWith(prefix) }.sortedBy { it.name() }
+private fun List<LocalVariableProxyImpl>.filterByPrefix(prefix: String) =
+ filter { it.name().startsWith(prefix) }.sortedBy { it.name() }
-private fun KotlinStackFrame.findComposer() = stackFrameProxy.visibleVariables().find { it.name() == COMPOSER_VAR }
+private fun KotlinStackFrame.findComposer() =
+ stackFrameProxy.visibleVariables().find { it.name() == COMPOSER_VAR }
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ErrorNode.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ErrorNode.kt
index ee9dd23..f368566 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ErrorNode.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ErrorNode.kt
@@ -21,10 +21,9 @@
import com.intellij.xdebugger.frame.XValueNode
import com.intellij.xdebugger.frame.XValuePlace
-/**
- * A [XNamedValue] shown instead of a [ComposeStateNode] when some error has occurred
- */
-internal class ErrorNode(val errorText: String) : XNamedValue(ComposeBundle.message("recomposition.state.label")) {
+/** A [XNamedValue] shown instead of a [ComposeStateNode] when some error has occurred */
+internal class ErrorNode(val errorText: String) :
+ XNamedValue(ComposeBundle.message("recomposition.state.label")) {
override fun computePresentation(node: XValueNode, place: XValuePlace) {
node.setPresentation(AllIcons.General.Error, null, errorText, false)
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ParamState.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ParamState.kt
index 62cb5fc..9059757 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ParamState.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ParamState.kt
@@ -19,32 +19,29 @@
private const val SLOTS_PER_INT = 10
-/**
- * Based on [androidx.compose.compiler.plugins.kotlin.lower.ParamState]
- */
+/** Based on [androidx.compose.compiler.plugins.kotlin.lower.ParamState] */
internal enum class ParamState(private val nameResource: String, private val bits: Int) {
/**
* Indicates that nothing is certain about the current state of the parameter. It could be
- * different from it was during the last execution, or it could be the same, but it is not
- * known so the current function looking at it must call equals on it in order to find out.
- * This is the only state that can cause the function to spend slot table space in order to
- * look at it.
+ * different from it was during the last execution, or it could be the same, but it is not known
+ * so the current function looking at it must call equals on it in order to find out. This is the
+ * only state that can cause the function to spend slot table space in order to look at it.
*/
Uncertain("recomposition.state.uncertain", 0b000),
/**
* This indicates that the value is known to be the same since the last time the function was
* executed. There is no need to store the value in the slot table in this case because the
- * calling function will *always* know whether the value was the same or different as it was
- * in the previous execution.
+ * calling function will *always* know whether the value was the same or different as it was in
+ * the previous execution.
*/
Same("recomposition.state.same", 0b001),
/**
- * This indicates that the value is known to be different since the last time the function
- * was executed. There is no need to store the value in the slot table in this case because
- * the calling function will *always* know whether the value was the same or different as it
- * was in the previous execution.
+ * This indicates that the value is known to be different since the last time the function was
+ * executed. There is no need to store the value in the slot table in this case because the
+ * calling function will *always* know whether the value was the same or different as it was in
+ * the previous execution.
*/
Different("recomposition.state.different", 0b010),
@@ -54,9 +51,7 @@
*/
Static("recomposition.state.static", 0b011),
- /**
- * If the msb is set, it is unstable. Lower bits are ignored.
- */
+ /** If the msb is set, it is unstable. Lower bits are ignored. */
Unstable100("recomposition.state.unstable", 0b100),
Unstable101("recomposition.state.unstable", 0b101),
Unstable110("recomposition.state.unstable", 0b110),
@@ -68,9 +63,7 @@
companion object {
private val STATES = ParamState.values().sortedBy { it.bits }
- /**
- * Returns the decoded param states of [values].
- */
+ /** Returns the decoded param states of [values]. */
fun decode(values: List<Int>): List<ParamState> {
val states = mutableListOf<ParamState>()
values.forEach {
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ParameterNode.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ParameterNode.kt
index 2633980..8203b6e 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ParameterNode.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ParameterNode.kt
@@ -36,7 +36,8 @@
private val state: ParamState,
) : XNamedValue(name) {
// Create the child node early because we are known to be on the correct thread.
- private val valueNode = if (param != null) createValueNode(param) else createOptimizedOutNode(name)
+ private val valueNode =
+ if (param != null) createValueNode(param) else createOptimizedOutNode(name)
override fun computePresentation(node: XValueNode, place: XValuePlace) {
node.setPresentation(AllIcons.Nodes.Parameter, null, state.getDisplayName(), true)
@@ -47,15 +48,19 @@
}
private fun createValueNode(param: LocalVariableProxyImpl): JavaValue {
- val nodeManager = context.debugProcess.xdebugProcess?.nodeManager ?: throw IllegalStateException("Missing node manager")
- val descriptor = object : LocalVariableDescriptorImpl(context.project, param) {
- override fun getName() = ComposeBundle.message("recomposition.state.value")
- }
+ val nodeManager =
+ context.debugProcess.xdebugProcess?.nodeManager
+ ?: throw IllegalStateException("Missing node manager")
+ val descriptor =
+ object : LocalVariableDescriptorImpl(context.project, param) {
+ override fun getName() = ComposeBundle.message("recomposition.state.value")
+ }
return JavaValue.create(null, descriptor, context, nodeManager, false)
}
}
-private fun createOptimizedOutNode(name: String): XNamedValue = JavaStackFrame.createMessageNode(
- ComposeBundle.message("recomposition.optimised.variable.message", "\'$name\'"),
- AllIcons.General.Information
-)
+private fun createOptimizedOutNode(name: String): XNamedValue =
+ JavaStackFrame.createMessageNode(
+ ComposeBundle.message("recomposition.optimised.variable.message", "\'$name\'"),
+ AllIcons.General.Information
+ )
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/StateObject.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/StateObject.kt
index 82f0bad..b8e51e9 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/StateObject.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/StateObject.kt
@@ -22,17 +22,21 @@
/**
* Represents an object whose state is reported in the `$changed/$dirty` state vector.
*
- * There are 2 types of objects, parameters (including an extension receiver) and a `this` pointer. A [StateObject] can be converted into an
- * [XNamedValue] which can then be added to the [ComposeStateNode].
+ * There are 2 types of objects, parameters (including an extension receiver) and a `this` pointer.
+ * A [StateObject] can be converted into an [XNamedValue] which can then be added to the
+ * [ComposeStateNode].
*/
internal sealed class StateObject(val state: ParamState, val name: String) {
abstract fun toXValue(context: EvaluationContextImpl): XNamedValue
- class Parameter(state: ParamState, name: String, val param: LocalVariableProxyImpl?) : StateObject(state, name) {
- override fun toXValue(context: EvaluationContextImpl): XNamedValue = ParameterNode(context, name, param, state)
+ class Parameter(state: ParamState, name: String, val param: LocalVariableProxyImpl?) :
+ StateObject(state, name) {
+ override fun toXValue(context: EvaluationContextImpl): XNamedValue =
+ ParameterNode(context, name, param, state)
}
class ThisObject(state: ParamState) : StateObject(state, "this") {
- override fun toXValue(context: EvaluationContextImpl): XNamedValue = ThisObjectNode(context, state)
+ override fun toXValue(context: EvaluationContextImpl): XNamedValue =
+ ThisObjectNode(context, state)
}
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ThisObjectNode.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ThisObjectNode.kt
index 2a2e2c1..ee02595 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ThisObjectNode.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/recomposition/ThisObjectNode.kt
@@ -42,10 +42,13 @@
}
private fun createValueNode(): JavaValue {
- val nodeManager = context.debugProcess.xdebugProcess?.nodeManager ?: throw IllegalStateException("Missing node manager")
- val descriptor = object : ThisDescriptorImpl(context.project) {
- override fun getName() = ComposeBundle.message("recomposition.state.value")
- }
+ val nodeManager =
+ context.debugProcess.xdebugProcess?.nodeManager
+ ?: throw IllegalStateException("Missing node manager")
+ val descriptor =
+ object : ThisDescriptorImpl(context.project) {
+ override fun getName() = ComposeBundle.message("recomposition.state.value")
+ }
return JavaValue.create(null, descriptor, context, nodeManager, false)
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/render/ComposeStateObjectClassRenderer.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/render/ComposeStateObjectClassRenderer.kt
index c8e4285..01ee360 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/render/ComposeStateObjectClassRenderer.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/render/ComposeStateObjectClassRenderer.kt
@@ -46,34 +46,35 @@
import com.sun.jdi.ClassType
import com.sun.jdi.Type
import com.sun.jdi.Value
+import java.util.concurrent.CompletableFuture
import org.jetbrains.kotlin.idea.debugger.KotlinClassRenderer
import org.jetbrains.kotlin.idea.debugger.isInKotlinSources
-import java.util.concurrent.CompletableFuture
/**
* Renderer for a given compose `StateObject` type object.
*
- * Basically, for a given compose state object, its underlying value (by invoking [DEBUGGER_DISPLAY_VALUE_METHOD_NAME])
- * determines how it's rendered in the "Variables" pane. This is to provide an auto-unboxing experience while debugging,
- * that users can identify the data by a glance at this more readable data view.
+ * Basically, for a given compose state object, its underlying value (by invoking
+ * [DEBUGGER_DISPLAY_VALUE_METHOD_NAME]) determines how it's rendered in the "Variables" pane. This
+ * is to provide an auto-unboxing experience while debugging, that users can identify the data by a
+ * glance at this more readable data view.
*
* E.g.
* 1) if the underlying value is an integer `1`, the label is rendered `1`.
- * 2) if the underlying value is a list, then the given object is rendered by a `List` renderer instead of the
- * original `Kotlin class` renderer. That is, "size = xx" is the label, and the `ArrayRenderer` is the children renderer
- * in this case.
- * 3) if the underlying value is a map, then the given object is rendered by a `Map` renderer instead of the original
- * `Kotlin class` renderer. That is, "size = xx" is the label, and the `ArrayRenderer` is the children renderer in
- * this case. When expanding, each of the entry is rendered by the `Map.Entry` renderer.
+ * 2) if the underlying value is a list, then the given object is rendered by a `List` renderer
+ * instead of the original `Kotlin class` renderer. That is, "size = xx" is the label, and the
+ * `ArrayRenderer` is the children renderer in this case.
+ * 3) if the underlying value is a map, then the given object is rendered by a `Map` renderer
+ * instead of the original `Kotlin class` renderer. That is, "size = xx" is the label, and the
+ * `ArrayRenderer` is the children renderer in this case. When expanding, each of the entry is
+ * rendered by the `Map.Entry` renderer.
*
- * @param fqcn the fully qualified class name of the Compose State Object to apply this custom renderer to.
+ * @param fqcn the fully qualified class name of the Compose State Object to apply this custom
+ * renderer to.
*/
class ComposeStateObjectClassRenderer(private val fqcn: String) : ClassRenderer() {
// We fallback to [KotlinClassRenderer] when the following exception is thrown:
// Unable to evaluate the expression No such instance method: 'getDebuggerDisplayValue',
- private val fallbackRenderer by lazy {
- KotlinClassRenderer()
- }
+ private val fallbackRenderer by lazy { KotlinClassRenderer() }
private val prioritizedCollectionRenderers by lazy {
NodeRendererSettings.getInstance()
@@ -87,7 +88,8 @@
init {
setIsApplicableChecker { type: Type? ->
- if (type !is ClassType || !type.isInKotlinSources()) return@setIsApplicableChecker CompletableFuture.completedFuture(false)
+ if (type !is ClassType || !type.isInKotlinSources())
+ return@setIsApplicableChecker CompletableFuture.completedFuture(false)
DebuggerUtilsAsync.instanceOf(type, fqcn)
}
@@ -96,22 +98,27 @@
companion object {
private val NODE_RENDERER_KEY = Key.create<NodeRenderer>(this::class.java.simpleName)
- // The name of the method we expect the Compose State Object to implement. We invoke it to retrieve the underlying
+ // The name of the method we expect the Compose State Object to implement. We invoke it to
+ // retrieve the underlying
// Compose State Object value.
private const val DEBUGGER_DISPLAY_VALUE_METHOD_NAME = "getDebuggerDisplayValue"
}
- override fun buildChildren(value: Value, builder: ChildrenBuilder, evaluationContext: EvaluationContext) {
- val debuggerDisplayValueDescriptor = try {
- getDebuggerDisplayValueDescriptor(value, evaluationContext, null)
- }
- catch (evaluateException: EvaluateException) {
- if (evaluateException.localizedMessage.startsWith("No such instance method:")) {
- return fallbackRenderer.buildChildren(value, builder, evaluationContext)
- }
+ override fun buildChildren(
+ value: Value,
+ builder: ChildrenBuilder,
+ evaluationContext: EvaluationContext
+ ) {
+ val debuggerDisplayValueDescriptor =
+ try {
+ getDebuggerDisplayValueDescriptor(value, evaluationContext, null)
+ } catch (evaluateException: EvaluateException) {
+ if (evaluateException.localizedMessage.startsWith("No such instance method:")) {
+ return fallbackRenderer.buildChildren(value, builder, evaluationContext)
+ }
- throw evaluateException
- }
+ throw evaluateException
+ }
getDelegatedRendererAsync(evaluationContext.debugProcess, debuggerDisplayValueDescriptor)
.thenAccept { renderer ->
@@ -120,8 +127,13 @@
}
}
- override fun getChildValueExpression(node: DebuggerTreeNode, context: DebuggerContext): PsiElement? {
- return node.parent.descriptor.getUserData(NODE_RENDERER_KEY)?.getChildValueExpression(node, context)
+ override fun getChildValueExpression(
+ node: DebuggerTreeNode,
+ context: DebuggerContext
+ ): PsiElement? {
+ return node.parent.descriptor
+ .getUserData(NODE_RENDERER_KEY)
+ ?.getChildValueExpression(node, context)
}
override fun isExpandableAsync(
@@ -129,26 +141,31 @@
evaluationContext: EvaluationContext,
parentDescriptor: NodeDescriptor
): CompletableFuture<Boolean> {
- val debuggerDisplayValueDescriptor = try {
- getDebuggerDisplayValueDescriptor(value, evaluationContext, null)
- }
- catch (evaluateException: EvaluateException) {
- if (evaluateException.localizedMessage.startsWith("No such instance method:")) {
- return fallbackRenderer.isExpandableAsync(value, evaluationContext, parentDescriptor)
- }
+ val debuggerDisplayValueDescriptor =
+ try {
+ getDebuggerDisplayValueDescriptor(value, evaluationContext, null)
+ } catch (evaluateException: EvaluateException) {
+ if (evaluateException.localizedMessage.startsWith("No such instance method:")) {
+ return fallbackRenderer.isExpandableAsync(value, evaluationContext, parentDescriptor)
+ }
- return CompletableFuture.failedFuture(evaluateException)
- }
+ return CompletableFuture.failedFuture(evaluateException)
+ }
return getDelegatedRendererAsync(evaluationContext.debugProcess, debuggerDisplayValueDescriptor)
.thenCompose { renderer ->
- renderer.isExpandableAsync(debuggerDisplayValueDescriptor.value, evaluationContext, debuggerDisplayValueDescriptor)
+ renderer.isExpandableAsync(
+ debuggerDisplayValueDescriptor.value,
+ evaluationContext,
+ debuggerDisplayValueDescriptor
+ )
}
}
/**
- * Returns a [ValueDescriptor] for the underlying "debugger display value", which is evaluated by invoking the
- * [DEBUGGER_DISPLAY_VALUE_METHOD_NAME] method of the Compose `StateObject` type object: [value].
+ * Returns a [ValueDescriptor] for the underlying "debugger display value", which is evaluated by
+ * invoking the [DEBUGGER_DISPLAY_VALUE_METHOD_NAME] method of the Compose `StateObject` type
+ * object: [value].
*/
private fun getDebuggerDisplayValueDescriptor(
value: Value,
@@ -160,11 +177,13 @@
if (!debugProcess.isAttached) throw EvaluateExceptionUtil.PROCESS_EXITED
val thisEvaluationContext = evaluationContext.createEvaluationContext(value)
- val debuggerDisplayValue = debuggerDisplayValueEvaluator.evaluate(debugProcess.project, thisEvaluationContext)
+ val debuggerDisplayValue =
+ debuggerDisplayValueEvaluator.evaluate(debugProcess.project, thisEvaluationContext)
return object : ValueDescriptorImpl(evaluationContext.project, debuggerDisplayValue) {
override fun getDescriptorEvaluation(context: DebuggerContext): PsiExpression? = null
- override fun calcValue(evaluationContext: EvaluationContextImpl?): Value = debuggerDisplayValue
+ override fun calcValue(evaluationContext: EvaluationContextImpl?): Value =
+ debuggerDisplayValue
override fun calcValueName(): String = "value"
override fun setValueLabel(label: String) {
@@ -174,26 +193,31 @@
}
/**
- * Return an ID of this renderer class, used by the IntelliJ platform to identify our renderer among all active
- * renderers in the system.
+ * Return an ID of this renderer class, used by the IntelliJ platform to identify our renderer
+ * among all active renderers in the system.
*/
override fun getUniqueId(): String {
return fqcn
}
- override fun calcLabel(descriptor: ValueDescriptor, evaluationContext: EvaluationContext, listener: DescriptorLabelListener): String {
- val debuggerDisplayValueDescriptor: ValueDescriptor = try {
- getDebuggerDisplayValueDescriptor(descriptor.value, evaluationContext, descriptor)
- }
- catch (evaluateException: EvaluateException) {
- if (evaluateException.localizedMessage.startsWith("No such instance method:")) {
- return fallbackRenderer.calcLabel(descriptor, evaluationContext, listener)
+ override fun calcLabel(
+ descriptor: ValueDescriptor,
+ evaluationContext: EvaluationContext,
+ listener: DescriptorLabelListener
+ ): String {
+ val debuggerDisplayValueDescriptor: ValueDescriptor =
+ try {
+ getDebuggerDisplayValueDescriptor(descriptor.value, evaluationContext, descriptor)
+ } catch (evaluateException: EvaluateException) {
+ if (evaluateException.localizedMessage.startsWith("No such instance method:")) {
+ return fallbackRenderer.calcLabel(descriptor, evaluationContext, listener)
+ }
+
+ throw evaluateException
}
- throw evaluateException
- }
-
- val renderer = getDelegatedRendererAsync(evaluationContext.debugProcess, debuggerDisplayValueDescriptor)
+ val renderer =
+ getDelegatedRendererAsync(evaluationContext.debugProcess, debuggerDisplayValueDescriptor)
return calcLabelAsync(renderer, debuggerDisplayValueDescriptor, evaluationContext, listener)
.getNow(XDebuggerUIConstants.getCollectingDataMessage())
}
@@ -210,8 +234,7 @@
descriptor.setValueLabel(label)
listener.labelChanged()
return@thenApply label
- }
- catch (evaluateException: EvaluateException) {
+ } catch (evaluateException: EvaluateException) {
descriptor.setValueLabelFailed(evaluateException)
listener.labelChanged()
return@thenApply ""
@@ -222,29 +245,34 @@
/**
* Returns a [CompletableFuture] of the first applicable renderer for the given [valueDescriptor].
*/
- private fun getDelegatedRendererAsync(debugProcess: DebugProcess, valueDescriptor: ValueDescriptor): CompletableFuture<NodeRenderer> {
+ private fun getDelegatedRendererAsync(
+ debugProcess: DebugProcess,
+ valueDescriptor: ValueDescriptor
+ ): CompletableFuture<NodeRenderer> {
val type = valueDescriptor.type
return DebuggerUtilsImpl.getApplicableRenderers(prioritizedCollectionRenderers, type)
.thenCompose { renderers ->
- // Return any applicable renderer of [prioritizedCollectionRenderers]. This is to de-prioritize `Kotlin class` renderer.
+ // Return any applicable renderer of [prioritizedCollectionRenderers]. This is to
+ // de-prioritize `Kotlin class` renderer.
// Or fallback to the default renderer.
- val found = renderers.firstOrNull() ?: return@thenCompose (debugProcess as DebugProcessImpl).getAutoRendererAsync(type)
+ val found =
+ renderers.firstOrNull()
+ ?: return@thenCompose (debugProcess as DebugProcessImpl).getAutoRendererAsync(type)
CompletableFuture.completedFuture(found)
}
}
- /**
- * [CachedEvaluator] used to invoke the [DEBUGGER_DISPLAY_VALUE_METHOD_NAME] method.
- */
+ /** [CachedEvaluator] used to invoke the [DEBUGGER_DISPLAY_VALUE_METHOD_NAME] method. */
private class DebuggerDisplayValueEvaluator(private val fqcn: String) : CachedEvaluator() {
init {
- referenceExpression = TextWithImportsImpl(
- CodeFragmentKind.EXPRESSION,
- "this.$DEBUGGER_DISPLAY_VALUE_METHOD_NAME()",
- "",
- JavaFileType.INSTANCE
- )
+ referenceExpression =
+ TextWithImportsImpl(
+ CodeFragmentKind.EXPRESSION,
+ "this.$DEBUGGER_DISPLAY_VALUE_METHOD_NAME()",
+ "",
+ JavaFileType.INSTANCE
+ )
}
override fun getClassName(): String {
@@ -255,4 +283,4 @@
return getEvaluator(project).evaluate(context)
}
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/render/ComposeStateObjectRendererProviderBase.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/render/ComposeStateObjectRendererProviderBase.kt
index af2ff5d..975e3c7 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/render/ComposeStateObjectRendererProviderBase.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/render/ComposeStateObjectRendererProviderBase.kt
@@ -25,16 +25,16 @@
/**
* Base custom renderer provider for rendering a given compose `StateObject` type object.
*
- * [stateObjectClassRenderer] is the actual underlying renderer for the label and the children nodes. Users can select
- * the provided renderer by [rendererName] if applicable.
+ * [stateObjectClassRenderer] is the actual underlying renderer for the label and the children
+ * nodes. Users can select the provided renderer by [rendererName] if applicable.
*
- * @param fqcn the fully qualified class name of the Compose State Object to apply the underlying custom renderer to.
+ * @param fqcn the fully qualified class name of the Compose State Object to apply the underlying
+ * custom renderer to.
*/
-sealed class ComposeStateObjectRendererProviderBase(private val fqcn: String) : CompoundRendererProvider() {
+sealed class ComposeStateObjectRendererProviderBase(private val fqcn: String) :
+ CompoundRendererProvider() {
private val rendererName = "Compose State Object"
- private val stateObjectClassRenderer by lazy {
- ComposeStateObjectClassRenderer(fqcn)
- }
+ private val stateObjectClassRenderer by lazy { ComposeStateObjectClassRenderer(fqcn) }
override fun getName(): String {
return rendererName
@@ -43,9 +43,7 @@
override fun isEnabled() = true
override fun getIsApplicableChecker(): Function<Type?, CompletableFuture<Boolean>> {
- return Function { type: Type? ->
- stateObjectClassRenderer.isApplicableAsync(type)
- }
+ return Function { type: Type? -> stateObjectClassRenderer.isApplicableAsync(type) }
}
override fun getValueLabelRenderer(): ValueLabelRenderer {
@@ -57,18 +55,14 @@
}
}
-class SnapshotMutableStateImplRendererProvider : ComposeStateObjectRendererProviderBase(
- "androidx.compose.runtime.SnapshotMutableStateImpl"
-)
+class SnapshotMutableStateImplRendererProvider :
+ ComposeStateObjectRendererProviderBase("androidx.compose.runtime.SnapshotMutableStateImpl")
-class DerivedSnapshotStateRendererProvider : ComposeStateObjectRendererProviderBase(
- "androidx.compose.runtime.DerivedSnapshotState"
-)
+class DerivedSnapshotStateRendererProvider :
+ ComposeStateObjectRendererProviderBase("androidx.compose.runtime.DerivedSnapshotState")
-class ComposeStateObjectListRendererProvider : ComposeStateObjectRendererProviderBase(
- "androidx.compose.runtime.snapshots.SnapshotStateList"
-)
+class ComposeStateObjectListRendererProvider :
+ ComposeStateObjectRendererProviderBase("androidx.compose.runtime.snapshots.SnapshotStateList")
-class ComposeStateObjectMapRendererProvider : ComposeStateObjectRendererProviderBase(
- "androidx.compose.runtime.snapshots.SnapshotStateMap"
-)
\ No newline at end of file
+class ComposeStateObjectMapRendererProvider :
+ ComposeStateObjectRendererProviderBase("androidx.compose.runtime.snapshots.SnapshotStateMap")
diff --git a/compose-ide-plugin/src/com/android/tools/compose/debug/render/KotlinMapEntryRenderer.kt b/compose-ide-plugin/src/com/android/tools/compose/debug/render/KotlinMapEntryRenderer.kt
index 18347e3..b790007 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/debug/render/KotlinMapEntryRenderer.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/debug/render/KotlinMapEntryRenderer.kt
@@ -23,22 +23,21 @@
import com.intellij.debugger.ui.tree.render.ValueLabelRenderer
import com.sun.jdi.ClassType
import com.sun.jdi.Type
-import org.jetbrains.kotlin.idea.debugger.isInKotlinSources
import java.util.concurrent.CompletableFuture
import java.util.function.Function
+import org.jetbrains.kotlin.idea.debugger.isInKotlinSources
/**
* Custom renderer for "MapEntry" type objects.
*
- * This is to precede the `Kotlin class` renderer, as [KotlinMapEntryRenderer] provides a more readable data view,
- * that the underlying `Map.Entry` renderer does the real work.
+ * This is to precede the `Kotlin class` renderer, as [KotlinMapEntryRenderer] provides a more
+ * readable data view, that the underlying `Map.Entry` renderer does the real work.
*/
class KotlinMapEntryRenderer : CompoundRendererProvider() {
private val MAP_ENTRY_FQCN = "java.util.Map\$Entry"
- private val mapEntryLabelRender = NodeRendererSettings.getInstance().alternateCollectionRenderers.find {
- it.name == "Map.Entry"
- }
+ private val mapEntryLabelRender =
+ NodeRendererSettings.getInstance().alternateCollectionRenderers.find { it.name == "Map.Entry" }
override fun isEnabled() = true
@@ -48,7 +47,8 @@
override fun getIsApplicableChecker(): Function<Type?, CompletableFuture<Boolean>> {
return Function { type: Type? ->
- if (type !is ClassType || !type.isInKotlinSources()) return@Function CompletableFuture.completedFuture(false)
+ if (type !is ClassType || !type.isInKotlinSources())
+ return@Function CompletableFuture.completedFuture(false)
DebuggerUtilsAsync.instanceOf(type, MAP_ENTRY_FQCN)
}
@@ -59,6 +59,8 @@
}
override fun getChildrenRenderer(): ChildrenRenderer {
- return NodeRendererSettings.createEnumerationChildrenRenderer(arrayOf(arrayOf("key", "getKey()"), arrayOf("value", "getValue()")))
+ return NodeRendererSettings.createEnumerationChildrenRenderer(
+ arrayOf(arrayOf("key", "getKey()"), arrayOf("value", "getValue()"))
+ )
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/formatting/ComposePostFormatProcessor.kt b/compose-ide-plugin/src/com/android/tools/compose/formatting/ComposePostFormatProcessor.kt
index eed507b..4160808 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/formatting/ComposePostFormatProcessor.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/formatting/ComposePostFormatProcessor.kt
@@ -35,24 +35,33 @@
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
/**
- * Runs after explicit code formatting invocation and for Modifier(androidx.compose.ui.Modifier) chain that is two modifiers or longer,
- * splits it in one modifier per line.
+ * Runs after explicit code formatting invocation and for Modifier(androidx.compose.ui.Modifier)
+ * chain that is two modifiers or longer, splits it in one modifier per line.
*/
class ComposePostFormatProcessor : PostFormatProcessor {
private fun isAvailable(psiElement: PsiElement, settings: CodeStyleSettings): Boolean {
return psiElement.containingFile is KtFile &&
- isComposeEnabled(psiElement) &&
- !DumbService.isDumb(psiElement.project) &&
- settings.getCustomSettings(ComposeCustomCodeStyleSettings::class.java).USE_CUSTOM_FORMATTING_FOR_MODIFIERS
+ isComposeEnabled(psiElement) &&
+ !DumbService.isDumb(psiElement.project) &&
+ settings
+ .getCustomSettings(ComposeCustomCodeStyleSettings::class.java)
+ .USE_CUSTOM_FORMATTING_FOR_MODIFIERS
}
override fun processElement(source: PsiElement, settings: CodeStyleSettings): PsiElement {
- return if (isAvailable(source, settings)) ComposeModifierProcessor(settings).process(source) else source
+ return if (isAvailable(source, settings)) ComposeModifierProcessor(settings).process(source)
+ else source
}
- override fun processText(source: PsiFile, rangeToReformat: TextRange, settings: CodeStyleSettings): TextRange {
- return if (isAvailable(source, settings)) ComposeModifierProcessor(settings).processText(source, rangeToReformat) else rangeToReformat
+ override fun processText(
+ source: PsiFile,
+ rangeToReformat: TextRange,
+ settings: CodeStyleSettings
+ ): TextRange {
+ return if (isAvailable(source, settings))
+ ComposeModifierProcessor(settings).processText(source, rangeToReformat)
+ else rangeToReformat
}
}
@@ -77,10 +86,7 @@
return formatted
}
- fun processText(
- source: PsiFile,
- rangeToReformat: TextRange
- ): TextRange {
+ fun processText(source: PsiFile, rangeToReformat: TextRange): TextRange {
myPostProcessor.resultTextRange = rangeToReformat
source.accept(this)
return myPostProcessor.resultTextRange
@@ -88,25 +94,22 @@
}
/**
- * Returns true if it's Modifier(androidx.compose.ui.Modifier) chain that is two modifiers or longer.
+ * Returns true if it's Modifier(androidx.compose.ui.Modifier) chain that is two modifiers or
+ * longer.
*/
private fun isModifierChainThatNeedToBeWrapped(element: KtElement): Boolean {
- // Take very top KtDotQualifiedExpression, e.g for `Modifier.adjust1().adjust2()` take whole expression, not only `Modifier.adjust1()`.
+ // Take very top KtDotQualifiedExpression, e.g for `Modifier.adjust1().adjust2()` take whole
+ // expression, not only `Modifier.adjust1()`.
return element is KtDotQualifiedExpression &&
- element.parent !is KtDotQualifiedExpression &&
- isModifierChainLongerThanTwo(element)
+ element.parent !is KtDotQualifiedExpression &&
+ isModifierChainLongerThanTwo(element)
}
-/**
- * Splits KtDotQualifiedExpression it one call per line.
- */
+/** Splits KtDotQualifiedExpression it one call per line. */
internal fun wrapModifierChain(element: KtDotQualifiedExpression, settings: CodeStyleSettings) {
- CodeStyle.doWithTemporarySettings(
- element.project,
- settings
- ) { tempSettings: CodeStyleSettings ->
+ CodeStyle.doWithTemporarySettings(element.project, settings) { tempSettings: CodeStyleSettings ->
tempSettings.kotlinCommonSettings.METHOD_CALL_CHAIN_WRAP = CommonCodeStyleSettings.WRAP_ALWAYS
tempSettings.kotlinCommonSettings.WRAP_FIRST_METHOD_IN_CALL_CHAIN = true
CodeFormatterFacade(tempSettings, element.language).processElement(element.node)
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/intentions/AddComposableToFunctionQuickFix.kt b/compose-ide-plugin/src/com/android/tools/compose/intentions/AddComposableToFunctionQuickFix.kt
index 4e0e196..2b34383 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/intentions/AddComposableToFunctionQuickFix.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/intentions/AddComposableToFunctionQuickFix.kt
@@ -15,20 +15,22 @@
*/
package com.android.tools.compose.intentions
+import androidx.compose.compiler.plugins.kotlin.ComposeClassIds
import androidx.compose.compiler.plugins.kotlin.k1.ComposeErrors
-import com.android.tools.compose.COMPOSABLE_ANNOTATION_FQ_NAME
import com.android.tools.compose.ComposeBundle
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KtCompilerPluginDiagnostic0
import org.jetbrains.kotlin.diagnostics.Diagnostic
+import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KotlinDiagnosticFixFactory
+import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.diagnosticFixFactory
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
import org.jetbrains.kotlin.idea.quickfix.QuickFixContributor
import org.jetbrains.kotlin.idea.quickfix.QuickFixes
import org.jetbrains.kotlin.idea.util.addAnnotation
-import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtElement
@@ -46,7 +48,6 @@
* Quick fix addressing @Composable function invocations from non-@Composable functions.
*
* As an example:
- *
* ```kotlin
* @Composable
* fun ComposableFunction() {}
@@ -56,56 +57,78 @@
* }
* ```
*
- * The call to `ComposableFunction()` within `NonComposableFunction` is not allowed. Both the invocation `ComposableFunction()` and the
- * function declaration `NonComposableFunction` will have an error.
+ * The call to `ComposableFunction()` within `NonComposableFunction` is not allowed. Both the
+ * invocation `ComposableFunction()` and the function declaration `NonComposableFunction` will have
+ * an error.
*
- * This quick fix appears on both errors, and offers to add `@Composable` to `NonComposableFunction`.
+ * This quick fix appears on both errors, and offers to add `@Composable` to
+ * `NonComposableFunction`.
*/
-class AddComposableToFunctionQuickFix private constructor(element: KtModifierListOwner, private val displayName: String)
- : KotlinQuickFixAction<KtModifierListOwner>(element) {
+class AddComposableToFunctionQuickFix
+private constructor(element: KtModifierListOwner, private val displayName: String) :
+ KotlinQuickFixAction<KtModifierListOwner>(element) {
override fun getFamilyName(): String = ComposeBundle.message("add.composable.to.function")
- override fun getText(): String = ComposeBundle.message("add.composable.to.function.with.name", displayName)
+ override fun getText(): String =
+ ComposeBundle.message("add.composable.to.function.with.name", displayName)
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
- element?.addAnnotation(FqName(COMPOSABLE_ANNOTATION_FQ_NAME))
+ element?.addAnnotation(ComposeClassIds.Composable)
}
/**
- * Creates a fix for the COMPOSABLE_EXPECTED error, which appears on a Composable function call from within a non-Composable function.
+ * Creates a fix for the COMPOSABLE_INVOCATION error, which appears on a non-Composable function
+ * that contains a Composable call.
*/
object ComposableInvocationFactory : KotlinSingleIntentionActionFactory() {
- override fun createAction(diagnostic: Diagnostic): IntentionAction? {
- // Look for the containing function. This logic is based on ComposableCallChecker.check, which walks up the tree and terminates at
+ override fun createAction(diagnostic: Diagnostic): IntentionAction? =
+ createAction(diagnostic.psiElement)
+
+ fun createAction(psiElement: PsiElement): AddComposableToFunctionQuickFix? {
+ // Look for the containing function. This logic is based on ComposableCallChecker.check, which
+ // walks up the tree and terminates at
// various places depending on the structure of the code.
- var node: PsiElement? = diagnostic.psiElement as? KtElement
- while(node != null) {
- // Order of when statements (and empty statements) are kept, to mimic the behavior in ComposableCallChecker.check.
- // In cases where we terminate without returning a fix, it indicates the compiler didn't identify a containing function that could
+ var node: PsiElement? = psiElement as? KtElement
+ while (node != null) {
+ // Order of when statements (and empty statements) are kept, to mimic the behavior in
+ // ComposableCallChecker.check.
+ // In cases where we terminate without returning a fix, it indicates the compiler didn't
+ // identify a containing function that could
// be annotated with @Composable to fix the error.
- when(node) {
- is KtFunctionLiteral -> { /* keep going */ }
+ when (node) {
+ is KtFunctionLiteral -> {
+ /* keep going */
+ }
is KtLambdaExpression -> {
- // Terminate at containing lambda. There are some scenarios where the compiler may not terminate (if the lambda is inline) and
- // therefore may go higher up to a containing named function, and we won't offer the fix in those scenarios even though it may
- // apply. This is a tactical decision because (1) the full logic is relatively complex to reimplement here, and (2) checking for
- // inline labmdas isn't actually correct (per the TODO in compiler code, it should be looking for CALLS_IN_PLACE instead).
+ // Terminate at containing lambda. There are some scenarios where the compiler may not
+ // terminate (if the lambda is inline) and
+ // therefore may go higher up to a containing named function, and we won't offer the fix
+ // in those scenarios even though it may
+ // apply. This is a tactical decision because (1) the full logic is relatively complex
+ // to reimplement here, and (2) checking for
+ // inline labmdas isn't actually correct (per the TODO in compiler code, it should be
+ // looking for CALLS_IN_PLACE instead).
//
- // This means the quick fix will not fire in some more complicated scenarios where it could actually be applicable; but that's
- // preferable to firing in scenarios where it doesn't actually fix a problem and actually makes things worse. But if there's a
- // containing function that can/should be annotated, it will also have an error which *will* have the quick fix, so it's still
+ // This means the quick fix will not fire in some more complicated scenarios where it
+ // could actually be applicable; but that's
+ // preferable to firing in scenarios where it doesn't actually fix a problem and
+ // actually makes things worse. But if there's a
+ // containing function that can/should be annotated, it will also have an error which
+ // *will* have the quick fix, so it's still
// available somewhere to the user.
return null
}
- is KtTryExpression -> { /* keep going */ }
+ is KtTryExpression -> {
+ /* keep going */
+ }
is KtFunction -> {
// Terminate at containing KtFunction.
if (node !is KtNamedFunction) return null
val displayName = node.name ?: return null
return AddComposableToFunctionQuickFix(node, displayName)
}
- is KtProperty -> return null // Terminate at containing property initializer.
+ is KtProperty -> return null // Terminate at containing property initializer.
is KtPropertyAccessor -> {
// Terminate at containing property accessor.
if (!node.isGetter) return null
@@ -124,17 +147,35 @@
}
/**
- * Creates a fix for the COMPOSABLE_INVOCATION error, which appears on a non-Composable function that contains a Composable call.
+ * Creates a fix for the COMPOSABLE_EXPECTED error, which appears on a Composable function call
+ * from within a non-Composable function.
*/
object ComposableExpectedFactory : KotlinSingleIntentionActionFactory() {
- override fun createAction(diagnostic: Diagnostic): IntentionAction? {
- val parentNamedFunction = diagnostic.psiElement.parent as? KtNamedFunction ?: return null
- val displayName = parentNamedFunction.name ?: return null
+ override fun createAction(diagnostic: Diagnostic): IntentionAction? =
+ createAction(diagnostic.psiElement.parent)
- return AddComposableToFunctionQuickFix(parentNamedFunction, displayName)
+ fun createAction(psiElement: PsiElement): AddComposableToFunctionQuickFix? {
+ val namedFunction = psiElement as? KtNamedFunction ?: return null
+ val displayName = namedFunction.name ?: return null
+
+ return AddComposableToFunctionQuickFix(namedFunction, displayName)
}
}
+ companion object {
+ val k2DiagnosticFixFactory: KotlinDiagnosticFixFactory<KtCompilerPluginDiagnostic0> =
+ diagnosticFixFactory(KtCompilerPluginDiagnostic0::class) { diagnostic ->
+ val psiElement = diagnostic.psi
+ listOfNotNull(
+ when (diagnostic.factoryName) {
+ "COMPOSABLE_INVOCATION" -> ComposableInvocationFactory.createAction(psiElement)
+ "COMPOSABLE_EXPECTED" -> ComposableExpectedFactory.createAction(psiElement)
+ else -> null
+ }
+ )
+ }
+ }
+
class Contributor : QuickFixContributor {
override fun registerQuickFixes(quickFixes: QuickFixes) {
// COMPOSABLE_INVOCATION: error goes on the Composable call in a non-Composable function
diff --git a/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeCreateComposableFunction.kt b/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeCreateComposableFunction.kt
index 2b2c2af..8cc9608 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeCreateComposableFunction.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeCreateComposableFunction.kt
@@ -51,7 +51,6 @@
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.typeUtil.replaceAnnotations
-
class ComposeUnresolvedFunctionFixContributor : QuickFixContributor {
override fun registerQuickFixes(quickFixes: QuickFixes) {
quickFixes.register(Errors.UNRESOLVED_REFERENCE, ComposeUnresolvedFunctionFixFactory())
@@ -63,21 +62,15 @@
*
* Created action creates new function with @Composable annotation.
*
- * Example:
- * For
+ * Example: For
*
- * @Composable
- * fun myComposable() {
- * <caret>newFunction()
- * }
+ * @Composable fun myComposable() { <caret>newFunction() }
*
* creates
*
- * @Composable
- * fun newFunction() {
- * TODO("Not yet implemented")
- * }
+ * @Composable fun newFunction() {
*
+ * TODO("Not yet implemented") }
*/
private class ComposeUnresolvedFunctionFixFactory : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
@@ -85,17 +78,22 @@
val parentFunction = unresolvedCall.getStrictParentOfType<KtNamedFunction>() ?: return null
if (!parentFunction.isComposableFunction()) return null
- val name = (unresolvedCall.calleeExpression as? KtSimpleNameExpression)?.getReferencedName() ?: return null
+ val name =
+ (unresolvedCall.calleeExpression as? KtSimpleNameExpression)?.getReferencedName()
+ ?: return null
// Composable function usually starts with uppercase first letter.
if (name.isBlank() || !name[0].isUpperCase()) return null
- val ktCreateCallableFromUsageFix = CreateCallableFromUsageFix(unresolvedCall) {
- listOfNotNull(createNewComposeFunctionInfo(name, it, parentFunction))
- }
+ val ktCreateCallableFromUsageFix =
+ CreateCallableFromUsageFix(unresolvedCall) {
+ listOfNotNull(createNewComposeFunctionInfo(name, it, parentFunction))
+ }
- // Since CreateCallableFromUsageFix is no longer an 'open' class, we instead use delegation to customize the text.
+ // Since CreateCallableFromUsageFix is no longer an 'open' class, we instead use delegation to
+ // customize the text.
return object : IntentionAction by ktCreateCallableFromUsageFix {
- override fun getText(): String = ComposeBundle.message("create.composable.function") + " '$name'"
+ override fun getText(): String =
+ ComposeBundle.message("create.composable.function") + " '$name'"
}
}
@@ -103,45 +101,65 @@
// n.b. Do not cache this CallableInfo anywhere, otherwise it is easy to leak Kotlin descriptors.
// (see https://github.com/JetBrains/intellij-community/commit/608589428c).
- private fun createNewComposeFunctionInfo(name: String,
- element: KtCallExpression,
- parentComposableFunction: KtNamedFunction): CallableInfo? {
+ private fun createNewComposeFunctionInfo(
+ name: String,
+ element: KtCallExpression,
+ parentComposableFunction: KtNamedFunction
+ ): CallableInfo? {
val analysisResult = element.analyzeAndGetResult()
val fullCallExpression = element.getQualifiedExpressionForSelectorOrThis()
- val expectedType = fullCallExpression.guessTypes(analysisResult.bindingContext, analysisResult.moduleDescriptor).singleOrNull()
+ val expectedType =
+ fullCallExpression
+ .guessTypes(analysisResult.bindingContext, analysisResult.moduleDescriptor)
+ .singleOrNull()
if (expectedType != null && KotlinBuiltIns.isUnit(expectedType)) {
val typeParameters = element.getTypeInfoForTypeArguments()
val returnType = TypeInfo(expectedType, Variance.OUT_VARIANCE)
val modifierList = KtPsiFactory(element.project).createModifierList(composableAnnotation)
val containers = element.getQualifiedExpressionForSelectorOrThis().getExtractionContainers()
- val parameters = if (element.valueArguments.lastOrNull() is KtLambdaArgument) {
- // If the last argument is a lambda, treat it as a `content` parameter containing another Composable.
- // In this case, we want the resulting argument name to be "content" and it should have a @Composable attribute.
- val parameterInfos = element.getParameterInfos()
- val modifiedLastParameter = parameterInfos.last().let {
- ParameterInfo(
- typeInfo = ComposableLambdaTypeInfo(it.typeInfo, parentComposableFunction),
- nameSuggestions = listOf("content") + it.nameSuggestions)
+ val parameters =
+ if (element.valueArguments.lastOrNull() is KtLambdaArgument) {
+ // If the last argument is a lambda, treat it as a `content` parameter containing another
+ // Composable.
+ // In this case, we want the resulting argument name to be "content" and it should have a
+ // @Composable attribute.
+ val parameterInfos = element.getParameterInfos()
+ val modifiedLastParameter =
+ parameterInfos.last().let {
+ ParameterInfo(
+ typeInfo = ComposableLambdaTypeInfo(it.typeInfo, parentComposableFunction),
+ nameSuggestions = listOf("content") + it.nameSuggestions
+ )
+ }
+
+ parameterInfos.dropLast(1) + listOf(modifiedLastParameter)
+ } else {
+ element.getParameterInfos()
}
- parameterInfos.dropLast(1) + listOf(modifiedLastParameter)
- }
- else {
- element.getParameterInfos()
- }
-
- return FunctionInfo(name, TypeInfo.Empty, returnType, containers, parameters, typeParameters, modifierList = modifierList)
+ return FunctionInfo(
+ name,
+ TypeInfo.Empty,
+ returnType,
+ containers,
+ parameters,
+ typeParameters,
+ modifierList = modifierList
+ )
}
return null
}
/** Wrapper around [TypeInfo] adding a @Composable annotation to the argument type. */
- private class ComposableLambdaTypeInfo(private val wrapped: TypeInfo,
- private val parentComposableFunction: KtNamedFunction) : TypeInfo(wrapped.variance) {
+ private class ComposableLambdaTypeInfo(
+ private val wrapped: TypeInfo,
+ private val parentComposableFunction: KtNamedFunction
+ ) : TypeInfo(wrapped.variance) {
override fun getPossibleTypes(builder: CallableBuilder): List<KotlinType> {
val wrappedTypes = wrapped.getPossibleTypes(builder)
- val composableAnnotationDescriptor = parentComposableFunction.getComposableAnnotation()?.resolveToDescriptorIfAny()
+ val composableAnnotationDescriptor =
+ parentComposableFunction.getComposableAnnotation()?.resolveToDescriptorIfAny()
if (composableAnnotationDescriptor == null) {
thisLogger().warn("Could not resolve @Composable annotation descriptor.")
@@ -149,7 +167,8 @@
}
return wrappedTypes.map {
- val newAnnotations = Annotations.create(it.annotations + listOf(composableAnnotationDescriptor))
+ val newAnnotations =
+ Annotations.create(it.annotations + listOf(composableAnnotationDescriptor))
it.replaceAnnotations(newAnnotations)
}
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeCreatePreviewAction.kt b/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeCreatePreviewAction.kt
index 50ffd4d..c1c63d2 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeCreatePreviewAction.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeCreatePreviewAction.kt
@@ -33,7 +33,8 @@
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespace
/**
- * Adds a @Preview annotation when a full @Composable is selected or cursor at @Composable annotation.
+ * Adds a @Preview annotation when a full @Composable is selected or cursor at @Composable
+ * annotation.
*/
class ComposeCreatePreviewAction : IntentionAction {
override fun startInWriteAction() = true
@@ -52,18 +53,25 @@
private fun getComposableAnnotationEntry(editor: Editor, file: PsiFile): KtAnnotationEntry? {
if (editor.selectionModel.hasSelection()) {
- val elementAtCaret = file.findElementAt(editor.selectionModel.selectionStart)?.parentOfType<KtAnnotationEntry>()
+ val elementAtCaret =
+ file.findElementAt(editor.selectionModel.selectionStart)?.parentOfType<KtAnnotationEntry>()
if (elementAtCaret?.isComposableAnnotation() == true) {
return elementAtCaret
- }
- else {
+ } else {
// Case when user selected few extra blank lines before @Composable annotation.
- val elementAtCaretAfterSpace = file.findElementAt(editor.selectionModel.selectionStart)?.getNextSiblingIgnoringWhitespace()
- return (elementAtCaretAfterSpace as? KtFunction)?.annotationEntries?.find { it.fqNameMatches(COMPOSABLE_ANNOTATION_FQ_NAME) }
+ val elementAtCaretAfterSpace =
+ file
+ .findElementAt(editor.selectionModel.selectionStart)
+ ?.getNextSiblingIgnoringWhitespace()
+ return (elementAtCaretAfterSpace as? KtFunction)?.annotationEntries?.find {
+ it.fqNameMatches(COMPOSABLE_ANNOTATION_FQ_NAME)
+ }
}
- }
- else {
- return file.findElementAt(editor.caretModel.offset)?.parentOfType<KtAnnotationEntry>()?.takeIf { it.isComposableAnnotation() }
+ } else {
+ return file
+ .findElementAt(editor.caretModel.offset)
+ ?.parentOfType<KtAnnotationEntry>()
+ ?.takeIf { it.isComposableAnnotation() }
}
}
@@ -71,8 +79,10 @@
if (editor == null || file == null) return
val composableAnnotationEntry = getComposableAnnotationEntry(editor, file) ?: return
val composableFunction = composableAnnotationEntry.parentOfType<KtFunction>() ?: return
- val previewAnnotationEntry = KtPsiFactory(project).createAnnotationEntry("@${COMPOSE_PREVIEW_ANNOTATION_FQN}")
+ val previewAnnotationEntry =
+ KtPsiFactory(project).createAnnotationEntry("@${COMPOSE_PREVIEW_ANNOTATION_FQN}")
- ShortenReferencesFacility.getInstance().shorten(composableFunction.addAnnotationEntry(previewAnnotationEntry))
+ ShortenReferencesFacility.getInstance()
+ .shorten(composableFunction.addAnnotationEntry(previewAnnotationEntry))
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeSurroundWithWidgetAction.kt b/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeSurroundWithWidgetAction.kt
index 777e777..7843808 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeSurroundWithWidgetAction.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeSurroundWithWidgetAction.kt
@@ -44,40 +44,54 @@
import org.jetbrains.kotlin.psi.KtNamedFunction
/**
- * Intention action that includes [ComposeSurroundWithBoxAction], [ComposeSurroundWithRowAction], [ComposeSurroundWithColumnAction].
+ * Intention action that includes [ComposeSurroundWithBoxAction], [ComposeSurroundWithRowAction],
+ * [ComposeSurroundWithColumnAction].
*
- * After this action is selected, a new pop-up appears, in which user can choose between actions listed above.
+ * After this action is selected, a new pop-up appears, in which user can choose between actions
+ * listed above.
*
* @see intentionDescriptions/ComposeSurroundWithWidgetActionGroup/before.kt.template
- * intentionDescriptions/ComposeSurroundWithWidgetActionGroup/after.kt.template
+ * intentionDescriptions/ComposeSurroundWithWidgetActionGroup/after.kt.template
*/
class ComposeSurroundWithWidgetActionGroup :
IntentionActionGroup<ComposeSurroundWithWidgetAction>(
- listOf(ComposeSurroundWithBoxAction(), ComposeSurroundWithRowAction(), ComposeSurroundWithColumnAction())
+ listOf(
+ ComposeSurroundWithBoxAction(),
+ ComposeSurroundWithRowAction(),
+ ComposeSurroundWithColumnAction()
+ )
) {
override fun getGroupText(actions: List<ComposeSurroundWithWidgetAction>) =
ComposeBundle.message("surround.with.widget.intention.text")
- override fun chooseAction(project: Project,
- editor: Editor,
- file: PsiFile,
- actions: List<ComposeSurroundWithWidgetAction>,
- invokeAction: (ComposeSurroundWithWidgetAction) -> Unit) {
+ override fun chooseAction(
+ project: Project,
+ editor: Editor,
+ file: PsiFile,
+ actions: List<ComposeSurroundWithWidgetAction>,
+ invokeAction: (ComposeSurroundWithWidgetAction) -> Unit
+ ) {
createPopup(project, actions, invokeAction).showInBestPositionFor(editor)
}
- private fun createPopup(project: Project,
- actions: List<ComposeSurroundWithWidgetAction>,
- invokeAction: (ComposeSurroundWithWidgetAction) -> Unit): ListPopup {
+ private fun createPopup(
+ project: Project,
+ actions: List<ComposeSurroundWithWidgetAction>,
+ invokeAction: (ComposeSurroundWithWidgetAction) -> Unit
+ ): ListPopup {
- val step = object : BaseListPopupStep<ComposeSurroundWithWidgetAction>(null, actions) {
- override fun getTextFor(action: ComposeSurroundWithWidgetAction) = action.text
+ val step =
+ object : BaseListPopupStep<ComposeSurroundWithWidgetAction>(null, actions) {
+ override fun getTextFor(action: ComposeSurroundWithWidgetAction) = action.text
- override fun onChosen(selectedValue: ComposeSurroundWithWidgetAction, finalChoice: Boolean): PopupStep<*>? {
- invokeAction(selectedValue)
- return FINAL_CHOICE
+ override fun onChosen(
+ selectedValue: ComposeSurroundWithWidgetAction,
+ finalChoice: Boolean
+ ): PopupStep<*>? {
+ invokeAction(selectedValue)
+ return FINAL_CHOICE
+ }
}
- }
return ListPopupImpl(project, step)
}
@@ -86,51 +100,66 @@
}
/**
- * Finds the first [KtCallExpression] at the given offset stopping if it finds any [KtNamedFunction] so it does not
- * exit the `Composable`.
+ * Finds the first [KtCallExpression] at the given offset stopping if it finds any [KtNamedFunction]
+ * so it does not exit the `Composable`.
*/
private fun PsiFile.findParentCallExpression(offset: Int): PsiElement? =
- PsiTreeUtil.findElementOfClassAtOffsetWithStopSet(this, offset, KtCallExpression::class.java,
- false, KtNamedFunction::class.java)
+ PsiTreeUtil.findElementOfClassAtOffsetWithStopSet(
+ this,
+ offset,
+ KtCallExpression::class.java,
+ false,
+ KtNamedFunction::class.java
+ )
/**
- * Finds the nearest surroundable [PsiElement] starting at the given offset and looking at the parents. If the offset is at
- * the end of a line, this method might look in the immediately previous offset.
+ * Finds the nearest surroundable [PsiElement] starting at the given offset and looking at the
+ * parents. If the offset is at the end of a line, this method might look in the immediately
+ * previous offset.
*/
private fun findNearestSurroundableElement(file: PsiFile, offset: Int): PsiElement? {
- val nearestElement = file.findElementAt(offset)?.let {
- if (it.isLineBreak()) {
- file.findParentCallExpression(it.prevLeaf(true)?.startOffset ?: (offset - 1))
+ val nearestElement =
+ file.findElementAt(offset)?.let {
+ if (it.isLineBreak()) {
+ file.findParentCallExpression(it.prevLeaf(true)?.startOffset ?: (offset - 1))
+ } else it
}
- else it
- } ?: return null
+ ?: return null
return file.findParentCallExpression(nearestElement.startOffset)
}
/**
- * Finds the [TextRange] to surround based on the current [editor] selection. It returns null if there is no block that
- * can be selected.
+ * Finds the [TextRange] to surround based on the current [editor] selection. It returns null if
+ * there is no block that can be selected.
*/
fun findSurroundingSelectionRange(file: PsiFile, editor: Editor): TextRange? {
if (!editor.selectionModel.hasSelection()) return null
- // We try to select full call elements to avoid the selection falling in the middle of, for example, a string.
- // This way, selecting the middle of two strings would still wrap the parent calls like for the following example:
+ // We try to select full call elements to avoid the selection falling in the middle of, for
+ // example, a string.
+ // This way, selecting the middle of two strings would still wrap the parent calls like for the
+ // following example:
//
// Text("Hello <selection>world!")
// Button(...)
// Text("By</selection>e")
//
// Would wrap the three elements instead of just the Button.
- val startSelectionOffset = findNearestSurroundableElement(file, editor.selectionModel.selectionStart)?.startOffset ?: Int.MAX_VALUE
- val endSelectionOffset = findNearestSurroundableElement(file, editor.selectionModel.selectionEnd)?.endOffset ?: -1
+ val startSelectionOffset =
+ findNearestSurroundableElement(file, editor.selectionModel.selectionStart)?.startOffset
+ ?: Int.MAX_VALUE
+ val endSelectionOffset =
+ findNearestSurroundableElement(file, editor.selectionModel.selectionEnd)?.endOffset ?: -1
- val statements = findElements(file,
- minOf(editor.selectionModel.selectionStart, startSelectionOffset),
- maxOf(editor.selectionModel.selectionEnd, endSelectionOffset),
- ElementKind.EXPRESSION)
- .filter { it.isInsideComposableCode() }
+ val statements =
+ findElements(
+ file,
+ minOf(editor.selectionModel.selectionStart, startSelectionOffset),
+ maxOf(editor.selectionModel.selectionEnd, endSelectionOffset),
+ ElementKind.EXPRESSION
+ )
+ .filter { it.isInsideComposableCode() }
if (statements.isNotEmpty()) {
return TextRange.create(statements.minOf { it.startOffset }, statements.maxOf { it.endOffset })
}
@@ -141,25 +170,26 @@
* Surrounds selected statements inside a @Composable function with a widget.
*
* @see intentionDescriptions/ComposeSurroundWithWidgetActionGroup/before.kt.template
- * intentionDescriptions/ComposeSurroundWithWidgetActionGroup/after.kt.template
+ * intentionDescriptions/ComposeSurroundWithWidgetActionGroup/after.kt.template
*/
abstract class ComposeSurroundWithWidgetAction : IntentionAction, HighPriorityAction {
override fun getFamilyName() = "Compose Surround With Action"
override fun startInWriteAction(): Boolean = true
- private fun findSurroundableRange(file: PsiFile, editor: Editor): TextRange? = if (editor.selectionModel.hasSelection()) {
- findSurroundingSelectionRange(file, editor)
- }
- else {
- findNearestSurroundableElement(file, editor.caretModel.offset)?.textRange
- }
+ private fun findSurroundableRange(file: PsiFile, editor: Editor): TextRange? =
+ if (editor.selectionModel.hasSelection()) {
+ findSurroundingSelectionRange(file, editor)
+ } else {
+ findNearestSurroundableElement(file, editor.caretModel.offset)?.textRange
+ }
- override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean = when {
- file == null || editor == null -> false
- !file.isWritable || file !is KtFile -> false
- else -> findSurroundableRange(file, editor) != null
- }
+ override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean =
+ when {
+ file == null || editor == null -> false
+ !file.isWritable || file !is KtFile -> false
+ else -> findSurroundableRange(file, editor) != null
+ }
protected abstract fun getTemplate(): TemplateImpl?
@@ -168,18 +198,17 @@
val surroundRange = findSurroundableRange(file, editor) ?: return
// Extend the selection if it does not match the inferred range
- if (editor.selectionModel.selectionStart != surroundRange.startOffset ||
- editor.selectionModel.selectionEnd != surroundRange.endOffset) {
+ if (
+ editor.selectionModel.selectionStart != surroundRange.startOffset ||
+ editor.selectionModel.selectionEnd != surroundRange.endOffset
+ ) {
editor.selectionModel.setSelection(surroundRange.startOffset, surroundRange.endOffset)
}
InvokeTemplateAction(getTemplate(), editor, project, HashSet()).perform()
}
-
}
-/**
- * Surrounds selected statements inside a @Composable function with Box widget.
- */
+/** Surrounds selected statements inside a @Composable function with Box widget. */
class ComposeSurroundWithBoxAction : ComposeSurroundWithWidgetAction() {
override fun getText(): String = ComposeBundle.message("surround.with.box.intention.text")
@@ -188,9 +217,7 @@
}
}
-/**
- * Surrounds selected statements inside a @Composable function with Row widget.
- */
+/** Surrounds selected statements inside a @Composable function with Row widget. */
class ComposeSurroundWithRowAction : ComposeSurroundWithWidgetAction() {
override fun getText(): String = ComposeBundle.message("surround.with.row.intention.text")
@@ -199,9 +226,7 @@
}
}
-/**
- * Surrounds selected statements inside a @Composable function with Column widget.
- */
+/** Surrounds selected statements inside a @Composable function with Column widget. */
class ComposeSurroundWithColumnAction : ComposeSurroundWithWidgetAction() {
override fun getText(): String = ComposeBundle.message("surround.with.column.intention.text")
@@ -209,4 +234,3 @@
return TemplateSettings.getInstance().getTemplate("WC", "AndroidCompose")
}
}
-
diff --git a/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeUnwrapAction.kt b/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeUnwrapAction.kt
index bfab3f2..ddb28c5 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeUnwrapAction.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeUnwrapAction.kt
@@ -29,16 +29,14 @@
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
-
-/**
- * Removes wrappers like Row, Column and Box around widgets.
- */
+/** Removes wrappers like Row, Column and Box around widgets. */
class ComposeUnwrapAction : IntentionAction {
- private val WRAPPERS_FQ_NAMES = setOf(
- "androidx.compose.foundation.layout.Box",
- "androidx.compose.foundation.layout.Row",
- "androidx.compose.foundation.layout.Column"
- )
+ private val WRAPPERS_FQ_NAMES =
+ setOf(
+ "androidx.compose.foundation.layout.Box",
+ "androidx.compose.foundation.layout.Row",
+ "androidx.compose.foundation.layout.Column"
+ )
override fun startInWriteAction() = true
@@ -55,17 +53,21 @@
}
private fun isCaretAtWrapper(editor: Editor, file: PsiFile): Boolean {
- val elementAtCaret = file.findElementAt(editor.caretModel.offset)?.parentOfType<KtNameReferenceExpression>() ?: return false
+ val elementAtCaret =
+ file.findElementAt(editor.caretModel.offset)?.parentOfType<KtNameReferenceExpression>()
+ ?: return false
val name = (elementAtCaret.resolve() as? KtNamedFunction)?.fqName?.asString() ?: return false
return WRAPPERS_FQ_NAMES.contains(name)
}
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
if (file == null || editor == null) return
- val wrapper = file.findElementAt(editor.caretModel.offset)?.parentOfType<KtNameReferenceExpression>() ?: return
+ val wrapper =
+ file.findElementAt(editor.caretModel.offset)?.parentOfType<KtNameReferenceExpression>()
+ ?: return
val outerBlock = wrapper.parent as? KtCallExpression ?: return
- val lambdaBlock = PsiTreeUtil.findChildOfType(outerBlock, KtBlockExpression::class.java, true) ?: return
+ val lambdaBlock =
+ PsiTreeUtil.findChildOfType(outerBlock, KtBlockExpression::class.java, true) ?: return
outerBlock.replace(lambdaBlock)
}
-
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeWrapModifiersAction.kt b/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeWrapModifiersAction.kt
index ed18073..f7b616d 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeWrapModifiersAction.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/intentions/ComposeWrapModifiersAction.kt
@@ -29,7 +29,8 @@
import org.jetbrains.kotlin.psi.psiUtil.getLastParentOfTypeInRowWithSelf
/**
- * Wraps Modifier(androidx.compose.ui.Modifier) chain that is two modifiers or longer, in one modifier per line.
+ * Wraps Modifier(androidx.compose.ui.Modifier) chain that is two modifiers or longer, in one
+ * modifier per line.
*/
class ComposeWrapModifiersAction : IntentionAction {
private companion object {
@@ -47,19 +48,23 @@
file == null || editor == null -> false
!file.isWritable || file !is KtFile -> false
else -> {
- val elementAtCaret = file.findElementAt(editor.caretModel.offset)?.parentOfType<KtDotQualifiedExpression>()
- val topLevelExpression = elementAtCaret?.getLastParentOfTypeInRowWithSelf<KtDotQualifiedExpression>() ?: return false
+ val elementAtCaret =
+ file.findElementAt(editor.caretModel.offset)?.parentOfType<KtDotQualifiedExpression>()
+ val topLevelExpression =
+ elementAtCaret?.getLastParentOfTypeInRowWithSelf<KtDotQualifiedExpression>()
+ ?: return false
isModifierChainLongerThanTwo(topLevelExpression) &&
- NO_NEW_LINE_BEFORE_DOT.containsMatchIn(topLevelExpression.text)
+ NO_NEW_LINE_BEFORE_DOT.containsMatchIn(topLevelExpression.text)
}
}
}
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
if (file == null || editor == null) return
- val elementAtCaret = file.findElementAt(editor.caretModel.offset)?.parentOfType<KtDotQualifiedExpression>()
- val topLevelExpression = elementAtCaret?.getLastParentOfTypeInRowWithSelf<KtDotQualifiedExpression>() ?: return
+ val elementAtCaret =
+ file.findElementAt(editor.caretModel.offset)?.parentOfType<KtDotQualifiedExpression>()
+ val topLevelExpression =
+ elementAtCaret?.getLastParentOfTypeInRowWithSelf<KtDotQualifiedExpression>() ?: return
wrapModifierChain(topLevelExpression, CodeStyle.getSettings(file))
}
-
}
diff --git a/compose-ide-plugin/src/com/android/tools/compose/settings/ComposeFormattingCodeStyleSettingsProvider.kt b/compose-ide-plugin/src/com/android/tools/compose/settings/ComposeFormattingCodeStyleSettingsProvider.kt
index 7ba8444..5a74c7c 100644
--- a/compose-ide-plugin/src/com/android/tools/compose/settings/ComposeFormattingCodeStyleSettingsProvider.kt
+++ b/compose-ide-plugin/src/com/android/tools/compose/settings/ComposeFormattingCodeStyleSettingsProvider.kt
@@ -23,13 +23,10 @@
import com.intellij.psi.codeStyle.CustomCodeStyleSettings
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.dsl.builder.selected
-import org.jetbrains.kotlin.idea.KotlinLanguage
import javax.swing.JCheckBox
+import org.jetbrains.kotlin.idea.KotlinLanguage
-
-/**
- * Allows to turn on and off [ComposePostFormatProcessor] in Code Style settings.
- */
+/** Allows to turn on and off [ComposePostFormatProcessor] in Code Style settings. */
class ComposeFormattingCodeStyleSettingsProvider : CodeStyleSettingsProvider() {
override fun hasSettingsPage() = false
@@ -41,7 +38,10 @@
override fun getConfigurableDisplayName(): String = ComposeBundle.message("compose")
override fun getLanguage() = KotlinLanguage.INSTANCE
- override fun createConfigurable(originalSettings: CodeStyleSettings, modelSettings: CodeStyleSettings): CodeStyleConfigurable {
+ override fun createConfigurable(
+ originalSettings: CodeStyleSettings,
+ modelSettings: CodeStyleSettings
+ ): CodeStyleConfigurable {
return object : CodeStyleConfigurable {
private lateinit var checkBox: JCheckBox
@@ -50,29 +50,35 @@
return panel {
group("Compose formatting") {
row {
- checkBox = checkBox(ComposeBundle.message("compose.enable.formatting.for.modifiers"))
- .selected(ComposeCustomCodeStyleSettings.getInstance(originalSettings).USE_CUSTOM_FORMATTING_FOR_MODIFIERS)
- .component
+ checkBox =
+ checkBox(ComposeBundle.message("compose.enable.formatting.for.modifiers"))
+ .selected(
+ ComposeCustomCodeStyleSettings.getInstance(originalSettings)
+ .USE_CUSTOM_FORMATTING_FOR_MODIFIERS
+ )
+ .component
}
}
}
}
- override fun isModified() = ComposeCustomCodeStyleSettings.getInstance(
- originalSettings).USE_CUSTOM_FORMATTING_FOR_MODIFIERS != checkBox.isSelected
+ override fun isModified() =
+ ComposeCustomCodeStyleSettings.getInstance(originalSettings)
+ .USE_CUSTOM_FORMATTING_FOR_MODIFIERS != checkBox.isSelected
override fun apply(settings: CodeStyleSettings) {
- ComposeCustomCodeStyleSettings.getInstance(settings).USE_CUSTOM_FORMATTING_FOR_MODIFIERS = checkBox.isSelected
+ ComposeCustomCodeStyleSettings.getInstance(settings).USE_CUSTOM_FORMATTING_FOR_MODIFIERS =
+ checkBox.isSelected
}
override fun apply() = apply(originalSettings)
override fun reset(settings: CodeStyleSettings) {
- checkBox.isSelected = ComposeCustomCodeStyleSettings.getInstance(settings).USE_CUSTOM_FORMATTING_FOR_MODIFIERS
+ checkBox.isSelected =
+ ComposeCustomCodeStyleSettings.getInstance(settings).USE_CUSTOM_FORMATTING_FOR_MODIFIERS
}
override fun getDisplayName() = ComposeBundle.message("compose")
}
-
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testData/projects/appWithLibWithSamples/app/src/main/java/com/example/appforsamplestest/Main.kt b/compose-ide-plugin/testData/projects/appWithLibWithSamples/app/src/main/java/com/example/appforsamplestest/Main.kt
index 1083c86..20f5344 100644
--- a/compose-ide-plugin/testData/projects/appWithLibWithSamples/app/src/main/java/com/example/appforsamplestest/Main.kt
+++ b/compose-ide-plugin/testData/projects/appWithLibWithSamples/app/src/main/java/com/example/appforsamplestest/Main.kt
@@ -3,5 +3,5 @@
import app.main.myFunction
fun main() {
- myFunction()
+ myFunction()
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableElementAutomaticRenamerFactoryTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableElementAutomaticRenamerFactoryTest.kt
index 9c58650..487c8ce 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableElementAutomaticRenamerFactoryTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableElementAutomaticRenamerFactoryTest.kt
@@ -29,10 +29,11 @@
import org.junit.Test
class ComposableElementAutomaticRenamerFactoryTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory().onEdt()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory().onEdt()
- private val myFixture: CodeInsightTestFixtureImpl by lazy { projectRule.fixture as CodeInsightTestFixtureImpl }
+ private val myFixture: CodeInsightTestFixtureImpl by lazy {
+ projectRule.fixture as CodeInsightTestFixtureImpl
+ }
@Before
fun setUp() {
@@ -43,22 +44,26 @@
@RunsInEdt
@Test
fun testRenaming() {
- val kotlinFile = myFixture.addFileToProject(
- "/scr/com/example/Greeting.kt",
- //language=kotlin
- """
+ val kotlinFile =
+ myFixture.addFileToProject(
+ "/scr/com/example/Greeting.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@Composable
fun Greeting() {}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
- val javaFile = myFixture.addFileToProject(
- "src/com/example/MyClass.java",
- //language=Java
- """
+ val javaFile =
+ myFixture.addFileToProject(
+ "src/com/example/MyClass.java",
+ // language=Java
+ """
package com.example;
public class MyClass {
@@ -66,7 +71,9 @@
GreetingKt.Greeting();
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.openFileInEditor(kotlinFile.virtualFile)
myFixture.moveCaret("Gree|ting")
@@ -80,13 +87,14 @@
@Composable
fun GreetingNew() {}
- """.trimIndent()
+ """
+ .trimIndent()
)
// Check the file name is changed.
assertThat(myFixture.file?.name).isEqualTo("GreetingNew.kt")
- //Check references to the file name are changed.
+ // Check references to the file name are changed.
myFixture.openFileInEditor(javaFile.virtualFile)
myFixture.checkResult(
"""
@@ -97,7 +105,8 @@
GreetingNewKt.GreetingNew();
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableFunctionExtractableAnalyserTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableFunctionExtractableAnalyserTest.kt
index 70c7caa..3cb07e1 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableFunctionExtractableAnalyserTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableFunctionExtractableAnalyserTest.kt
@@ -24,6 +24,7 @@
import com.intellij.openapi.project.Project
import com.intellij.testFramework.RunsInEdt
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl
+import java.util.Collections
import org.jetbrains.android.compose.stubComposableAnnotation
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.EXTRACT_FUNCTION
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler
@@ -35,14 +36,14 @@
import org.junit.Before
import org.junit.Rule
import org.junit.Test
-import java.util.Collections
class ComposableFunctionExtractableAnalyserTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory().onEdt()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory().onEdt()
- private val myFixture: CodeInsightTestFixtureImpl by lazy { projectRule.fixture as CodeInsightTestFixtureImpl }
+ private val myFixture: CodeInsightTestFixtureImpl by lazy {
+ projectRule.fixture as CodeInsightTestFixtureImpl
+ }
@Before
fun setUp() {
@@ -50,15 +51,24 @@
myFixture.stubComposableAnnotation()
}
- private val helper = object : ExtractionEngineHelper(EXTRACT_FUNCTION) {
- override fun configureAndRun(project: Project,
- editor: Editor,
- descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
- onFinish: (ExtractionResult) -> Unit) {
- val newDescriptor = descriptorWithConflicts.descriptor.copy(suggestedNames = Collections.singletonList("newComposableFunction"))
- doRefactor(ExtractionGeneratorConfiguration(newDescriptor, ExtractionGeneratorOptions.DEFAULT), onFinish)
+ private val helper =
+ object : ExtractionEngineHelper(EXTRACT_FUNCTION) {
+ override fun configureAndRun(
+ project: Project,
+ editor: Editor,
+ descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
+ onFinish: (ExtractionResult) -> Unit
+ ) {
+ val newDescriptor =
+ descriptorWithConflicts.descriptor.copy(
+ suggestedNames = Collections.singletonList("newComposableFunction")
+ )
+ doRefactor(
+ ExtractionGeneratorConfiguration(newDescriptor, ExtractionGeneratorOptions.DEFAULT),
+ onFinish
+ )
+ }
}
- }
@RunsInEdt
@Test
@@ -75,13 +85,15 @@
fun sourceFunction() {
<selection>print(true)</selection>
}
- """.trimIndent()
+ """
+ .trimIndent()
)
- ExtractKotlinFunctionHandler(helper = helper).invoke(myFixture.project, myFixture.editor, myFixture.file!!, null)
+ ExtractKotlinFunctionHandler(helper = helper)
+ .invoke(myFixture.project, myFixture.editor, myFixture.file!!, null)
myFixture.checkResult(
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -96,7 +108,8 @@
private fun newComposableFunction() {
print(true)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -119,13 +132,15 @@
<selection>print(true)</selection>
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
- ExtractKotlinFunctionHandler(helper = helper).invoke(myFixture.project, myFixture.editor, myFixture.file!!, null)
+ ExtractKotlinFunctionHandler(helper = helper)
+ .invoke(myFixture.project, myFixture.editor, myFixture.file!!, null)
myFixture.checkResult(
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -144,7 +159,8 @@
private fun newComposableFunction() {
print(true)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableHighlighterExtensionTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableHighlighterExtensionTest.kt
index 40a5225..ac38b91 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableHighlighterExtensionTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableHighlighterExtensionTest.kt
@@ -41,8 +41,7 @@
@RunWith(JUnit4::class)
class ComposableHighlighterExtensionTest {
- @get:Rule
- var projectRule = AndroidProjectRule.inMemory()
+ @get:Rule var projectRule = AndroidProjectRule.inMemory()
private val highlighter = ComposableHighlighterExtension()
@@ -76,10 +75,13 @@
whenever(mockContainingFile.isValid).thenReturn(true)
whenever(mockContainingFile.project).thenReturn(projectRule.project)
whenever(mockContainingFile.virtualFile).thenReturn(mockVirtualFile)
- whenever(mockContainingFile.getUserData(ModuleUtilCore.KEY_MODULE)).thenReturn(projectRule.module)
+ whenever(mockContainingFile.getUserData(ModuleUtilCore.KEY_MODULE))
+ .thenReturn(projectRule.module)
// Setup mocks to return whether the function is composable
- whenever(mockProjectFileIndex.isInLibrarySource(mockVirtualFile)).thenAnswer { isInLibrarySource }
+ whenever(mockProjectFileIndex.isInLibrarySource(mockVirtualFile)).thenAnswer {
+ isInLibrarySource
+ }
// Setup mocks to return whether the function is in library source.
whenever(mockResolvedCall.candidateDescriptor).thenReturn(mockCandidateDescriptor)
@@ -123,24 +125,25 @@
isComposableInvocation = true
moduleUsesCompose = true
- // If the call is outside a compose-enabled module or library source, there should be no highlighting.
+ // If the call is outside a compose-enabled module or library source, there should be no
+ // highlighting.
moduleUsesCompose = false
isInLibrarySource = false
assertThat(highlighter.highlightCall(mockElement, mockResolvedCall)).isNull()
moduleUsesCompose = false
isInLibrarySource = true
- assertThat(highlighter.highlightCall(mockElement, mockResolvedCall)).isEqualTo(
- ComposableHighlighterExtension.COMPOSABLE_CALL_TEXT_TYPE)
+ assertThat(highlighter.highlightCall(mockElement, mockResolvedCall))
+ .isEqualTo(ComposableHighlighterExtension.COMPOSABLE_CALL_TEXT_TYPE)
moduleUsesCompose = true
isInLibrarySource = false
- assertThat(highlighter.highlightCall(mockElement, mockResolvedCall)).isEqualTo(
- ComposableHighlighterExtension.COMPOSABLE_CALL_TEXT_TYPE)
+ assertThat(highlighter.highlightCall(mockElement, mockResolvedCall))
+ .isEqualTo(ComposableHighlighterExtension.COMPOSABLE_CALL_TEXT_TYPE)
moduleUsesCompose = true
isInLibrarySource = true
- assertThat(highlighter.highlightCall(mockElement, mockResolvedCall)).isEqualTo(
- ComposableHighlighterExtension.COMPOSABLE_CALL_TEXT_TYPE)
+ assertThat(highlighter.highlightCall(mockElement, mockResolvedCall))
+ .isEqualTo(ComposableHighlighterExtension.COMPOSABLE_CALL_TEXT_TYPE)
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableIconProviderTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableIconProviderTest.kt
index 75104a5..6e51e42 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableIconProviderTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableIconProviderTest.kt
@@ -32,26 +32,31 @@
@RunWith(JUnit4::class)
class ComposableIconProviderTest {
- @get:Rule
- var projectRule = AndroidProjectRule.inMemory()
+ @get:Rule var projectRule = AndroidProjectRule.inMemory()
@Before
fun setup() {
// Allow @Composable attribute to be used in snippets below.
- projectRule.fixture.addFileToProject("androidx/compose/runtime/Composable.kt", """
+ projectRule.fixture.addFileToProject(
+ "androidx/compose/runtime/Composable.kt",
+ """
package androidx.compose.runtime
-annotation class Composable""")
+annotation class Composable"""
+ )
}
@Test
fun getPresentation_notAFunction() {
- projectRule.fixture.configureByText(KotlinFileType.INSTANCE, """
+ projectRule.fixture.configureByText(
+ KotlinFileType.INSTANCE,
+ """
package com.example
val fo<caret>o = 1234
-""")
+"""
+ )
runReadAction {
val element = projectRule.fixture.elementAtCaret
@@ -63,12 +68,15 @@
@Test
fun getPresentation_notComposeFunction() {
- projectRule.fixture.configureByText(KotlinFileType.INSTANCE, """
+ projectRule.fixture.configureByText(
+ KotlinFileType.INSTANCE,
+ """
package com.example
fun testFun<caret>ction() {}
-""")
+"""
+ )
runReadAction {
val element = projectRule.fixture.elementAtCaret
@@ -80,7 +88,9 @@
@Test
fun getPresentation_composeFunctionWithVisibility() {
- projectRule.fixture.configureByText(KotlinFileType.INSTANCE, """
+ projectRule.fixture.configureByText(
+ KotlinFileType.INSTANCE,
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -88,7 +98,8 @@
@Composable
fun testFun<caret>ction() {}
-""")
+"""
+ )
runReadAction {
val element = projectRule.fixture.elementAtCaret
@@ -105,7 +116,9 @@
@Test
fun getPresentation_composeFunctionWithoutVisibility() {
- projectRule.fixture.configureByText(KotlinFileType.INSTANCE, """
+ projectRule.fixture.configureByText(
+ KotlinFileType.INSTANCE,
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -113,7 +126,8 @@
@Composable
fun testFun<caret>ction() {}
-""")
+"""
+ )
runReadAction {
val element = projectRule.fixture.elementAtCaret
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableItemPresentationProviderTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableItemPresentationProviderTest.kt
index db4d478..60720b9 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableItemPresentationProviderTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposableItemPresentationProviderTest.kt
@@ -28,8 +28,7 @@
@RunWith(JUnit4::class)
class ComposableItemPresentationProviderTest {
- @get:Rule
- var projectRule = AndroidProjectRule.inMemory()
+ @get:Rule var projectRule = AndroidProjectRule.inMemory()
private val provider = ComposableItemPresentationProvider()
@@ -42,7 +41,9 @@
val testFunction = <caret>{ }
- """.trimIndent())
+ """
+ .trimIndent()
+ )
runReadAction {
val function = runReadAction { projectRule.fixture.elementAtCaret }
@@ -61,7 +62,9 @@
fun testFun<caret>ction(arg0: Int, arg1: Int) {}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
runReadAction {
val function = projectRule.fixture.elementAtCaret
@@ -73,12 +76,16 @@
@Test
fun getPresentation_functionIsComposable_composablePresentationReturned() {
- projectRule.fixture.addFileToProject("androidx/compose/runtime/Composable.kt", """
+ projectRule.fixture.addFileToProject(
+ "androidx/compose/runtime/Composable.kt",
+ """
package androidx.compose.runtime
annotation class Composable
- """.trimIndent())
+ """
+ .trimIndent()
+ )
projectRule.fixture.configureByText(
KotlinFileType.INSTANCE,
@@ -90,7 +97,9 @@
@Composable
fun testFun<caret>ction(arg0: Int, arg1: Int = 0) {}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
runReadAction {
val function = projectRule.fixture.elementAtCaret
@@ -103,12 +112,16 @@
@Test
fun getPresentation_functionIsComposable_composablePresentationReturnedWithLambda() {
- projectRule.fixture.addFileToProject("androidx/compose/runtime/Composable.kt", """
+ projectRule.fixture.addFileToProject(
+ "androidx/compose/runtime/Composable.kt",
+ """
package androidx.compose.runtime
annotation class Composable
- """.trimIndent())
+ """
+ .trimIndent()
+ )
projectRule.fixture.configureByText(
KotlinFileType.INSTANCE,
@@ -120,14 +133,17 @@
@Composable
fun testFun<caret>ction(arg0: Int, arg1: Int = 0, arg2: @Composable () -> Unit) {}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
runReadAction {
val function = projectRule.fixture.elementAtCaret
assertThat(function).isInstanceOf(KtFunction::class.java)
val presentation = provider.getPresentation(function as KtFunction)!!
- assertThat(presentation.presentableText).isEqualTo("@Composable testFunction(arg0: Int, ...) {...}")
+ assertThat(presentation.presentableText)
+ .isEqualTo("@Composable testFunction(arg0: Int, ...) {...}")
}
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeAutoDocumentationTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeAutoDocumentationTest.kt
index ede9c39..79c380d 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeAutoDocumentationTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeAutoDocumentationTest.kt
@@ -34,8 +34,7 @@
@RunWith(JUnit4::class)
class ComposeAutoDocumentationTest {
- @get:Rule
- val projectRule = AndroidProjectRule.onDisk().onEdt()
+ @get:Rule val projectRule = AndroidProjectRule.onDisk().onEdt()
private val fixture by lazy { projectRule.fixture as JavaCodeInsightTestFixture }
@@ -50,10 +49,11 @@
fun documentationForComposables() {
(fixture.module.getModuleSystem() as DefaultModuleSystem).usesCompose = true
fixture.stubComposableAnnotation()
- val file = fixture.addFileToProject(
- "/src/the/hold/steady/Albums.kt",
- // language=kotlin
- """
+ val file =
+ fixture.addFileToProject(
+ "/src/the/hold/steady/Albums.kt",
+ // language=kotlin
+ """
package the.hold.steady
import androidx.compose.runtime.Composable
@@ -68,8 +68,9 @@
fun ThePriceOfProgress(optional: Int = 42, children: @Composable() () -> Unit) {}
fun OpenDoorPolicy() {}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
fixture.openFileInEditor(file.virtualFile)
@@ -79,7 +80,8 @@
assertThat(fixture.findParentElement<KtNamedFunction>(it).shouldShowDocumentation()).isTrue()
}
- assertThat(fixture.findParentElement<KtNamedFunction>("Open|Door").shouldShowDocumentation()).isFalse()
+ assertThat(fixture.findParentElement<KtNamedFunction>("Open|Door").shouldShowDocumentation())
+ .isFalse()
}
@RunsInEdt
@@ -91,19 +93,22 @@
"""
package androidx.compose.ui
interface Modifier
- """.trimIndent()
- )
- val file = fixture.addFileToProject(
- "/src/metric/Albums.kt",
- // language=kotlin
"""
+ .trimIndent()
+ )
+ val file =
+ fixture.addFileToProject(
+ "/src/metric/Albums.kt",
+ // language=kotlin
+ """
package metric
// For whatever reason, these don't come back qualified in the test, so fully qualify here.
fun androidx.compose.ui.Modifier.artOfDoubt(): Modifier = this
fun androidx.compose.ui.Modifier.formentera(): Modifier = this
fun String.growUpAndBlowAway(): Int = 8675309
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
fixture.openFileInEditor(file.virtualFile)
val windows = listOf("artOf|Doubt", "formen|tera")
@@ -112,16 +117,18 @@
assertThat(fixture.findParentElement<KtNamedFunction>(it).shouldShowDocumentation()).isTrue()
}
- assertThat(fixture.findParentElement<KtNamedFunction>("Blow|Away").shouldShowDocumentation()).isFalse()
+ assertThat(fixture.findParentElement<KtNamedFunction>("Blow|Away").shouldShowDocumentation())
+ .isFalse()
}
@RunsInEdt
@Test
fun documentationForModifierBlahBlah() {
- val file = fixture.addFileToProject(
- "/src/androidx/compose/ui/Modifier.kt",
- // language=kotlin
- """
+ val file =
+ fixture.addFileToProject(
+ "/src/androidx/compose/ui/Modifier.kt",
+ // language=kotlin
+ """
package androidx.compose.ui
interface Modifier {
fun fantasies() {}
@@ -130,8 +137,9 @@
fun pagansInVegas() = 42L
}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
fixture.openFileInEditor(file.virtualFile)
@@ -141,4 +149,4 @@
assertThat(fixture.findParentElement<KtNamedFunction>(it).shouldShowDocumentation()).isTrue()
}
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeColorAnnotatorTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeColorLineMarkerProviderDescriptorTest.kt
similarity index 73%
rename from compose-ide-plugin/testSrc/com/android/tools/compose/ComposeColorAnnotatorTest.kt
rename to compose-ide-plugin/testSrc/com/android/tools/compose/ComposeColorLineMarkerProviderDescriptorTest.kt
index b83f18e..4a47eec 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeColorAnnotatorTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeColorLineMarkerProviderDescriptorTest.kt
@@ -23,47 +23,43 @@
import com.android.tools.idea.testing.moveCaret
import com.android.tools.idea.ui.resourcemanager.rendering.MultipleColorIcon
import com.google.common.truth.Truth.assertThat
+import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl
+import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.command.WriteCommandAction
-import com.intellij.psi.util.parentOfType
import com.intellij.testFramework.EdtRule
import com.intellij.testFramework.RunsInEdt
-import com.intellij.testFramework.fixtures.CodeInsightTestUtil
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import com.intellij.testFramework.runInEdtAndGet
import com.intellij.testFramework.runInEdtAndWait
+import java.awt.Color
import org.jetbrains.android.AndroidAnnotatorUtil
import org.jetbrains.android.compose.stubComposableAnnotation
-import com.intellij.openapi.application.runReadAction
-import org.jetbrains.kotlin.psi.KtCallExpression
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
-import java.awt.Color
-/**
- * Tests for [ComposeColorAnnotator]
- */
-class ComposeColorAnnotatorTest {
- @get:Rule
- val projectRule = AndroidProjectRule.onDisk()
+/** Tests for [ComposeColorLineMarkerProviderDescriptor] */
+class ComposeColorLineMarkerProviderDescriptorTest {
+ @get:Rule val projectRule = AndroidProjectRule.onDisk()
- private val myFixture: JavaCodeInsightTestFixture by lazy { projectRule.fixture as JavaCodeInsightTestFixture }
+ private val myFixture: JavaCodeInsightTestFixture by lazy { projectRule.fixture }
@Before
fun setUp() {
(myFixture.module.getModuleSystem() as DefaultModuleSystem).usesCompose = true
myFixture.addClass(
- //language=java
+ // language=java
"""
package androidx.compose.ui.graphics;
class ColorSpace {
public static final ColorSpace TEST_SPACE = ColorSpace();
}
- """)
+ """
+ )
myFixture.addFileToProject(
"src/com/androidx/compose/ui/graphics/Color.kt",
- //language=kotlin
+ // language=kotlin
"""
package androidx.compose.ui.graphics
fun Color(color: Int): Long = 1L
@@ -81,15 +77,18 @@
alpha: Float = 1f,
colorSpace: ColorSpace? = null
): Long = 1L
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
fun testColorLong() {
- val psiFile = myFixture.addFileToProject(
- "src/com/android/test/A.kt",
- //language=kotlin
- """
+ val psiFile =
+ myFixture.addFileToProject(
+ "src/com/android/test/A.kt",
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
@@ -101,7 +100,9 @@
val secondaryVariant = Color(color = 0x8057AD28)
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.configureFromExistingVirtualFile(psiFile.virtualFile)
checkGutterIconInfos(
listOf(
@@ -110,14 +111,14 @@
Color(74, 138, 123, 255),
Color(87, 173, 40, 255),
Color(87, 173, 40, 128)
- ),
- includeClickAction = true
+ )
)
setNewColor("Co|lor(0xFF4A8A7B)", Color(0xFFAABBCC.toInt()))
setNewColor("Co|lor(color = 0xFF57AD28)", Color(0xFFAABBCC.toInt()))
- assertThat(myFixture.editor.document.text).isEqualTo(
- //language=kotlin
- """
+ assertThat(myFixture.editor.document.text)
+ .isEqualTo(
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
@@ -129,48 +130,54 @@
val secondaryVariant = Color(color = 0x8057AD28)
}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
}
@Test
fun testColorWithLeadingZero() {
- val psiFile = myFixture.addFileToProject(
- "src/com/android/test/A.kt",
- //language=kotlin
- """
+ val psiFile =
+ myFixture.addFileToProject(
+ "src/com/android/test/A.kt",
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
val other = Color(0xFFFF0000)
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.configureFromExistingVirtualFile(psiFile.virtualFile)
checkGutterIconInfos(
listOf(
Color(255, 0, 0, 255),
- ),
- includeClickAction = true
+ )
)
setNewColor("Co|lor(0xFFFF0000)", Color(0x0DFF0000, true))
- assertThat(myFixture.editor.document.text).isEqualTo(
- //language=kotlin
- """
+ assertThat(myFixture.editor.document.text)
+ .isEqualTo(
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
val other = Color(0x0DFF0000)
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
}
@Test
fun testColorInt() {
- val psiFile = myFixture.addFileToProject(
- "src/com/android/test/A.kt",
- //language=kotlin
- """
+ val psiFile =
+ myFixture.addFileToProject(
+ "src/com/android/test/A.kt",
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
@@ -182,7 +189,9 @@
val secondaryVariant = Color(color = 0x4057AD28)
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.configureFromExistingVirtualFile(psiFile.virtualFile)
checkGutterIconInfos(
listOf(
@@ -190,14 +199,15 @@
Color(74, 138, 123, 0),
Color(74, 138, 123, 128),
Color(87, 173, 40, 0),
- Color(87, 173, 40, 64)),
- includeClickAction = true
+ Color(87, 173, 40, 64)
+ )
)
setNewColor("Co|lor(0x4A8A7B)", Color(0xFFAABBCC.toInt()))
setNewColor("Co|lor(color = 0x57AD28)", Color(0xFFAABBCC.toInt()))
- assertThat(myFixture.editor.document.text).isEqualTo(
- //language=kotlin
- """
+ assertThat(myFixture.editor.document.text)
+ .isEqualTo(
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
@@ -209,15 +219,18 @@
val secondaryVariant = Color(color = 0x4057AD28)
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
fun testColorInt_X3() {
- val psiFile = myFixture.addFileToProject(
- "src/com/android/test/A.kt",
- //language=kotlin
- """
+ val psiFile =
+ myFixture.addFileToProject(
+ "src/com/android/test/A.kt",
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
@@ -229,7 +242,9 @@
val secondaryVariant = Color(green = 200, red = 180, blue = 120)
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.configureFromExistingVirtualFile(psiFile.virtualFile)
checkGutterIconInfos(
listOf(
@@ -237,16 +252,17 @@
Color(74, 138, 123),
Color(170, 187, 204),
Color(87, 173, 40),
- Color(180, 200, 120)),
- includeClickAction = true
+ Color(180, 200, 120)
+ )
)
setNewColor("Co|lor(0x4A, 0x8A, 0x7B)", Color(0xFFAABBCC.toInt()))
setNewColor("Co|lor(170, 187, 204)", Color(0xFF406080.toInt()))
setNewColor("Co|lor(red = 0x57, green = 0xAD, blue = 0x28)", Color(0xFFAABBCC.toInt()))
setNewColor("Co|lor(green = 200, red = 180, blue = 120)", Color(0x80112233.toInt()))
- assertThat(myFixture.editor.document.text).isEqualTo(
- //language=kotlin
- """
+ assertThat(myFixture.editor.document.text)
+ .isEqualTo(
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
@@ -258,15 +274,18 @@
val secondaryVariant = Color(red = 17, green = 34, blue = 51, alpha = 255)
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
fun testColorInt_X4() {
- val psiFile = myFixture.addFileToProject(
- "src/com/android/test/A.kt",
- //language=kotlin
- """
+ val psiFile =
+ myFixture.addFileToProject(
+ "src/com/android/test/A.kt",
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
@@ -278,7 +297,9 @@
val secondaryVariant = Color(green = 120, red = 64, alpha = 255, blue = 192)
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.configureFromExistingVirtualFile(psiFile.virtualFile)
checkGutterIconInfos(
listOf(
@@ -286,14 +307,15 @@
Color(74, 138, 123),
Color(170, 187, 204),
Color(87, 173, 40),
- Color(64, 120, 192)),
- includeClickAction = true
+ Color(64, 120, 192)
+ )
)
setNewColor("Co|lor(0x4A, 0x8A, 0x7B, 0xFF)", Color(0xFFAABBCC.toInt()))
setNewColor("Co|lor(green = 120, red = 64, alpha = 255, blue = 192)", Color(0xFFAABBCC.toInt()))
- assertThat(myFixture.editor.document.text).isEqualTo(
- //language=kotlin
- """
+ assertThat(myFixture.editor.document.text)
+ .isEqualTo(
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
@@ -305,16 +327,18 @@
val secondaryVariant = Color(red = 170, green = 187, blue = 204, alpha = 255)
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
-
@Test
fun testColorFloat_X3() {
- val psiFile = myFixture.addFileToProject(
- "src/com/android/test/A.kt",
- //language=kotlin
- """
+ val psiFile =
+ myFixture.addFileToProject(
+ "src/com/android/test/A.kt",
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
@@ -326,7 +350,9 @@
val primaryVariant = Color(green = 0.68f, red = 0.34f, blue = 0.15f)
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.configureFromExistingVirtualFile(psiFile.virtualFile)
checkGutterIconInfos(
listOf(
@@ -334,14 +360,15 @@
Color(77, 138, 122),
Color(77, 138, 122),
Color(87, 173, 38),
- Color(87, 173, 38)),
- includeClickAction = true
+ Color(87, 173, 38)
+ )
)
setNewColor("Co|lor(0.3f, 0.54f, 0.48f)", Color(0xFFAABBCC.toInt()))
setNewColor("Co|lor(green = 0.68f, red = 0.34f, blue = 0.15f)", Color(0xFFAABBCC.toInt()))
- assertThat(myFixture.editor.document.text).isEqualTo(
- //language=kotlin
- """
+ assertThat(myFixture.editor.document.text)
+ .isEqualTo(
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
@@ -353,15 +380,18 @@
val primaryVariant = Color(red = 0.667f, green = 0.733f, blue = 0.8f, alpha = 1.0f)
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
fun testColorFloat_X4() {
- val psiFile = myFixture.addFileToProject(
- "src/com/android/test/A.kt",
- //language=kotlin
- """
+ val psiFile =
+ myFixture.addFileToProject(
+ "src/com/android/test/A.kt",
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
@@ -373,7 +403,9 @@
val primaryVariant = Color(alpha = 0.25f, green = 0.173f, blue = 0.4f, red = 0.87f)
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.configureFromExistingVirtualFile(psiFile.virtualFile)
checkGutterIconInfos(
listOf(
@@ -381,14 +413,18 @@
Color(189, 35, 77, 215),
Color(189, 35, 77, 215),
Color(222, 44, 102, 64),
- Color(222, 44, 102, 64)),
- includeClickAction = true
+ Color(222, 44, 102, 64)
+ )
)
setNewColor("Co|lor(0.74f, 0.138f, 0.3f, 0.845f)", Color(0xFFAABBCC.toInt()))
- setNewColor("Co|lor(alpha = 0.25f, green = 0.173f, blue = 0.4f, red = 0.87f)", Color(0xFFAABBCC.toInt()))
- assertThat(myFixture.editor.document.text).isEqualTo(
- //language=kotlin
- """
+ setNewColor(
+ "Co|lor(alpha = 0.25f, green = 0.173f, blue = 0.4f, red = 0.87f)",
+ Color(0xFFAABBCC.toInt())
+ )
+ assertThat(myFixture.editor.document.text)
+ .isEqualTo(
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
class A {
@@ -400,16 +436,20 @@
val primaryVariant = Color(red = 0.667f, green = 0.733f, blue = 0.8f, alpha = 1.0f)
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
fun testColorFloat_X4_ColorSpace() {
- // Note: We don't offer neither color preview nor picker for Color(Float, Float, Float, Float, ColorSpace) function.
- val psiFile = myFixture.addFileToProject(
- "src/com/android/test/A.kt",
- //language=kotlin
- """
+ // Note: We don't offer neither color preview nor picker for Color(Float, Float, Float, Float,
+ // ColorSpace) function.
+ val psiFile =
+ myFixture.addFileToProject(
+ "src/com/android/test/A.kt",
+ // language=kotlin
+ """
package com.android.test
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorSpace
@@ -422,53 +462,67 @@
val primaryVariant = Color(red = 1.0f, green = 1.0f, blue = 1.0f, alpha = 1.0f, colorSpace = ColorSpace.TEST_SPACE)
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.configureFromExistingVirtualFile(psiFile.virtualFile)
// No gutter for color space.
- checkGutterIconInfos(listOf(), includeClickAction = false)
+ checkGutterIconInfos(listOf())
}
private fun setNewColor(window: String, newColor: Color) {
val element = runInEdtAndGet { myFixture.moveCaret(window) }
- val annotations = runReadAction {
- CodeInsightTestUtil.testAnnotator(ComposeColorAnnotator(), element.parentOfType<KtCallExpression>()!!)
+
+ myFixture.doHighlighting()
+ val highlightInfo = runReadAction {
+ DaemonCodeAnalyzerImpl.getLineMarkers(myFixture.editor.document, myFixture.project).single {
+ lineMarkerInfo ->
+ lineMarkerInfo.navigationHandler is ColorIconRenderer && lineMarkerInfo.element == element
+ }
}
+
runInEdtAndWait {
- val iconRenderer = annotations[0].gutterIconRenderer as ColorIconRenderer
val project = myFixture.project
- val setColorTask = iconRenderer.getSetColorTask() ?: return@runInEdtAndWait
- WriteCommandAction.runWriteCommandAction(project, "Change Color", null, { setColorTask.invoke(newColor) })
+ val setColorTask =
+ (highlightInfo.navigationHandler as ColorIconRenderer).getSetColorTask()
+ ?: return@runInEdtAndWait
+ WriteCommandAction.runWriteCommandAction(
+ project,
+ "Change Color",
+ null,
+ { setColorTask.invoke(newColor) }
+ )
}
}
- private fun checkGutterIconInfos(expectedColorIcons: List<Color>, includeClickAction: Boolean) {
- val iconList = myFixture.doHighlighting().filter { it.gutterIconRenderer is ColorIconRenderer }.sortedBy { it.startOffset }
- assertThat(iconList).hasSize(expectedColorIcons.size)
- iconList.forEach {
- assertThat(it.gutterIconRenderer.icon).isNotNull()
- if (includeClickAction) {
- val action = runReadAction { (it.gutterIconRenderer as ColorIconRenderer).clickAction }
- assertThat(action).isNotNull()
- }
- else {
- val action = runReadAction { (it.gutterIconRenderer as ColorIconRenderer).clickAction }
- assertThat(action).isNull()
- }
+ private fun checkGutterIconInfos(expectedColorIcons: List<Color>) {
+ myFixture.doHighlighting()
+ val highlightInfos = runReadAction {
+ DaemonCodeAnalyzerImpl.getLineMarkers(myFixture.editor.document, myFixture.project)
+ .filter { lineMarkerInfo -> lineMarkerInfo.navigationHandler is ColorIconRenderer }
+ .sortedBy { it.startOffset }
}
- assertThat(iconList.map { (it.gutterIconRenderer as ColorIconRenderer).color }).containsExactlyElementsIn(expectedColorIcons)
+
+ assertThat(highlightInfos).hasSize(expectedColorIcons.size)
+ highlightInfos.forEach {
+ assertThat(it.icon).isNotNull()
+ assertThat(it.navigationHandler).isNotNull()
+ }
+
+ assertThat(highlightInfos.map { (it.navigationHandler as ColorIconRenderer).color })
+ .containsExactlyElementsIn(expectedColorIcons)
}
}
-/**
- * Tests for [AndroidKotlinResourceExternalAnnotator]
- */
+/** Tests for [AndroidKotlinResourceExternalAnnotator] */
class ComposeColorReferenceAnnotatorTest {
private val projectRule = AndroidProjectRule.onDisk()
- @get:Rule
- val ruleChain: RuleChain = RuleChain.outerRule(projectRule).around(EdtRule())
+ @get:Rule val ruleChain: RuleChain = RuleChain.outerRule(projectRule).around(EdtRule())
- private val myFixture: JavaCodeInsightTestFixture by lazy { projectRule.fixture as JavaCodeInsightTestFixture }
+ private val myFixture: JavaCodeInsightTestFixture by lazy {
+ projectRule.fixture as JavaCodeInsightTestFixture
+ }
@Before
fun setUp() {
@@ -476,7 +530,10 @@
myFixture.stubComposableAnnotation()
myFixture.testDataPath = getComposePluginTestDataPath()
myFixture.copyFileToProject("annotator/colors.xml", "res/values/colors.xml")
- myFixture.copyFileToProject("annotator/AndroidManifest.xml", SdkConstants.FN_ANDROID_MANIFEST_XML)
+ myFixture.copyFileToProject(
+ "annotator/AndroidManifest.xml",
+ SdkConstants.FN_ANDROID_MANIFEST_XML
+ )
}
// Regression test for https://issuetracker.google.com/144560843
@@ -496,11 +553,13 @@
val drawable = R.drawable.ic_tick
val color = R.color.color1
}
- """.trimIndent()
+ """
+ .trimIndent()
)
val icons = myFixture.findAllGutters()
- val colorGutterIconRenderer = icons.first {it is AndroidAnnotatorUtil.ColorRenderer}
- assertThat((colorGutterIconRenderer.icon as MultipleColorIcon).colors).containsExactlyElementsIn(arrayOf(Color(63, 81, 181)))
+ val colorGutterIconRenderer = icons.first { it is AndroidAnnotatorUtil.ColorRenderer }
+ assertThat((colorGutterIconRenderer.icon as MultipleColorIcon).colors)
+ .containsExactlyElementsIn(arrayOf(Color(63, 81, 181)))
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeFoldingBuilderTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeFoldingBuilderTest.kt
index 0e99c99..430eabb 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeFoldingBuilderTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeFoldingBuilderTest.kt
@@ -15,7 +15,6 @@
*/
package com.android.tools.compose
-
import com.android.tools.idea.project.DefaultModuleSystem
import com.android.tools.idea.projectsystem.getModuleSystem
import com.android.tools.idea.testing.AndroidProjectRule
@@ -27,14 +26,13 @@
import org.junit.Rule
import org.junit.Test
-/**
- * Test for [ComposeFoldingBuilder].
- */
+/** Test for [ComposeFoldingBuilder]. */
class ComposeFoldingBuilderTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory()
- private val myFixture: CodeInsightTestFixtureImpl by lazy { projectRule.fixture as CodeInsightTestFixtureImpl }
+ private val myFixture: CodeInsightTestFixtureImpl by lazy {
+ projectRule.fixture as CodeInsightTestFixtureImpl
+ }
@Before
fun setUp() {
@@ -52,17 +50,19 @@
fun adjust():Modifier {}
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@Test
fun test() {
- // We can't use standard [myFixture.testFolding] because we need properly load file to be able resolve references inside
+ // We can't use standard [myFixture.testFolding] because we need properly load file to be able
+ // resolve references inside
// [ComposeFoldingBuilder].
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -75,12 +75,15 @@
.adjust()
.adjust()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
val res = myFixture.getFoldingDescription(false)
- assertThat(res).isEqualTo("""
+ assertThat(res)
+ .isEqualTo(
+ """
package com.example
import <fold text='...'>androidx.compose.runtime.Composable
@@ -92,6 +95,8 @@
.adjust()
.adjust()</fold>
}</fold>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeOverrideImplementsAnnotationsFilterTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeOverrideImplementsAnnotationsFilterTest.kt
index 4970c9b..23c0349 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeOverrideImplementsAnnotationsFilterTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeOverrideImplementsAnnotationsFilterTest.kt
@@ -27,11 +27,13 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-/** Tests both [ComposeOverrideImplementsAnnotationsFilter] and that it's defined correctly in extension XML. */
+/**
+ * Tests both [ComposeOverrideImplementsAnnotationsFilter] and that it's defined correctly in
+ * extension XML.
+ */
@RunWith(JUnit4::class)
class ComposeOverrideImplementsAnnotationsFilterTest {
- @get:Rule
- var projectRule = AndroidProjectRule.inMemory()
+ @get:Rule var projectRule = AndroidProjectRule.inMemory()
@Before
fun setup() {
@@ -42,10 +44,11 @@
@Test
fun composableAnnotationRetainedOnFunction() {
val fixture = projectRule.fixture
- val file = fixture.loadNewFile(
- "src/com/example/Foo.kt",
- // language=kotlin
- """
+ val file =
+ fixture.loadNewFile(
+ "src/com/example/Foo.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -57,10 +60,13 @@
class Impleme<caret>ntation : Interface {
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
- val intention = fixture.availableIntentions.singleOrNull { it.familyName == "Implement members" } ?: error("Intention not found")
+ val intention =
+ fixture.availableIntentions.singleOrNull { it.familyName == "Implement members" }
+ ?: error("Intention not found")
WriteCommandAction.runWriteCommandAction(projectRule.project) {
intention.invoke(fixture.project, fixture.editor, file)
}
@@ -83,7 +89,8 @@
TODO("Not yet implemented")
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -91,10 +98,11 @@
fun composableAnnotationRetainedOnArgument() {
val fixture = projectRule.fixture
- val file = fixture.loadNewFile(
- "src/com/example/Foo.kt",
- // language=kotlin
- """
+ val file =
+ fixture.loadNewFile(
+ "src/com/example/Foo.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -105,10 +113,13 @@
class Impleme<caret>ntation : Interface {
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
- val intention = fixture.availableIntentions.singleOrNull { it.familyName == "Implement members" } ?: error("Intention not found")
+ val intention =
+ fixture.availableIntentions.singleOrNull { it.familyName == "Implement members" }
+ ?: error("Intention not found")
WriteCommandAction.runWriteCommandAction(projectRule.project) {
intention.invoke(fixture.project, fixture.editor, file)
}
@@ -129,7 +140,8 @@
TODO("Not yet implemented")
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeSampleResolutionServiceTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeSampleResolutionServiceTest.kt
index b2928d8..a5f33d6 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeSampleResolutionServiceTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeSampleResolutionServiceTest.kt
@@ -15,8 +15,6 @@
*/
package com.android.tools.compose
-import com.android.builder.model.SyncIssue
-import com.android.ide.gradle.model.artifacts.AdditionalClassifierArtifactsModel
import com.android.tools.idea.flags.StudioFlags
import com.android.tools.idea.gradle.LibraryFilePaths
import com.android.tools.idea.testing.AndroidGradleTestCase
@@ -27,17 +25,15 @@
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.PathUtil
+import java.io.File
+import java.nio.file.Paths
import org.jetbrains.annotations.SystemIndependent
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
import org.jetbrains.kotlin.psi.KtNamedFunction
-import java.io.File
-import java.nio.file.Paths
-/**
- * Tests for [ComposeSampleResolutionService]
- */
+/** Tests for [ComposeSampleResolutionService] */
class ComposeSampleResolutionServiceTest : AndroidGradleTestCase() {
override fun setUp() {
super.setUp()
@@ -45,10 +41,16 @@
StudioFlags.SAMPLES_SUPPORT_ENABLED.override(true)
}
- override fun getTestDataDirectoryWorkspaceRelativePath(): @SystemIndependent String = "tools/adt/idea/compose-ide-plugin/testData"
+ override fun getTestDataDirectoryWorkspaceRelativePath(): @SystemIndependent String =
+ "tools/adt/idea/compose-ide-plugin/testData"
- override fun getAdditionalRepos() = listOf(
- File(getComposePluginTestDataPath(), PathUtil.toSystemDependentName(TestProjectPaths.REPO_FOR_SAMPLES_ARTIFACT_TEST)))
+ override fun getAdditionalRepos() =
+ listOf(
+ File(
+ getComposePluginTestDataPath(),
+ PathUtil.toSystemDependentName(TestProjectPaths.REPO_FOR_SAMPLES_ARTIFACT_TEST)
+ )
+ )
fun testDownloadingAndAttachingSamples() {
loadProject(TestProjectPaths.APP_WITH_LIB_WITH_SAMPLES)
@@ -59,10 +61,13 @@
// We download samples only for androidx libraries.
assume().that(samples).isNull()
- val androidxSamples = libraryFilePaths.getCachedPathsForArtifact("androidx.ui:lib:3.0")?.sampleSource
+ val androidxSamples =
+ libraryFilePaths.getCachedPathsForArtifact("androidx.ui:lib:3.0")?.sampleSource
assume().that(androidxSamples).isNotNull()
- // Note: the classifer here is not the same as what is required by the Gradle metadata, this was an accident but we leave
- // as is to also test that this actually correctly picks up the name of the artifact by relying on the Gradle module metadata rather
+ // Note: the classifer here is not the same as what is required by the Gradle metadata, this was
+ // an accident but we leave
+ // as is to also test that this actually correctly picks up the name of the artifact by relying
+ // on the Gradle module metadata rather
// than just the classifer.
assertThat(androidxSamples!!.name).isEqualTo("lib-3.0-samplesources.jar")
}
@@ -70,7 +75,11 @@
fun testResolveSampleReference() {
loadProject(TestProjectPaths.APP_WITH_LIB_WITH_SAMPLES)
- val file = VfsUtil.findFile(Paths.get(project.basePath, "/app/src/main/java/com/example/appforsamplestest/Main.kt"), false)
+ val file =
+ VfsUtil.findFile(
+ Paths.get(project.basePath, "/app/src/main/java/com/example/appforsamplestest/Main.kt"),
+ false
+ )
assume().that(file).isNotNull()
myFixture.openFileInEditor(file!!)
@@ -79,8 +88,11 @@
assume().that(librarySourceFunction).isNotNull()
val sampleTag = librarySourceFunction.docComment!!.getDefaultSection().findTagByName("sample")!!
- val sample = PsiTreeUtil.findChildOfType<KDocName>(sampleTag, KDocName::class.java)?.mainReference?.resolve()
+ val sample =
+ PsiTreeUtil.findChildOfType<KDocName>(sampleTag, KDocName::class.java)
+ ?.mainReference
+ ?.resolve()
assume().that(sample).isNotNull()
assertThat(sample!!.getKotlinFqName()!!.asString()).isEqualTo("app.samples.sampleFunction")
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeSuppressorTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeSuppressorTest.kt
index ae9cda8..546f484 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeSuppressorTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeSuppressorTest.kt
@@ -19,18 +19,18 @@
import org.jetbrains.android.compose.stubComposableAnnotation
import org.jetbrains.kotlin.idea.inspections.FunctionNameInspection
-/**
- * Test for [ComposeSuppressor].
- */
+/** Test for [ComposeSuppressor]. */
class ComposeSuppressorTest : JavaCodeInsightFixtureTestCase() {
- fun testFunctionNameWarning(): Unit = myFixture.run {
- enableInspections(FunctionNameInspection::class.java)
- stubComposableAnnotation()
+ fun testFunctionNameWarning(): Unit =
+ myFixture.run {
+ enableInspections(FunctionNameInspection::class.java)
+ stubComposableAnnotation()
- val file = addFileToProject(
- "src/com/example/views.kt",
- """
+ val file =
+ addFileToProject(
+ "src/com/example/views.kt",
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -39,10 +39,11 @@
fun MyView() {}
fun <weak_warning descr="Function name 'NormalFunction' should start with a lowercase letter">NormalFunction</weak_warning>() {}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
- configureFromExistingVirtualFile(file.virtualFile)
- checkHighlighting()
- }
+ configureFromExistingVirtualFile(file.virtualFile)
+ checkHighlighting()
+ }
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeUsageGroupingRuleProviderTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeUsageGroupingRuleProviderTest.kt
index 466abe4..cca5948 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeUsageGroupingRuleProviderTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/ComposeUsageGroupingRuleProviderTest.kt
@@ -15,7 +15,6 @@
*/
package com.android.tools.compose
-
import androidx.compose.compiler.plugins.kotlin.ComposeFqNames
import com.android.testutils.MockitoKt.mock
import com.android.tools.idea.project.DefaultModuleSystem
@@ -46,17 +45,19 @@
/** Test basic cases for the [ComposeUsageGroupingRuleProvider]. */
@RunWith(JUnit4::class)
class ComposeUsageGroupingRuleProviderTest {
- @get:Rule
- val projectRule = AndroidProjectRule.onDisk()
+ @get:Rule val projectRule = AndroidProjectRule.onDisk()
private val project by lazy { projectRule.project }
private val groupingRuleProvider = ComposeUsageGroupingRuleProvider()
- private val groupingRule by lazy { groupingRuleProvider.getActiveRules(project).single() as UsageGroupingRuleEx }
+ private val groupingRule by lazy {
+ groupingRuleProvider.getActiveRules(project).single() as UsageGroupingRuleEx
+ }
@Test
fun activeRulesAreAllRules() {
- assertThat(groupingRuleProvider.getActiveRules(project)).isEqualTo(groupingRuleProvider.getAllRules(project, null))
+ assertThat(groupingRuleProvider.getActiveRules(project))
+ .isEqualTo(groupingRuleProvider.getAllRules(project, null))
}
@Test
@@ -72,8 +73,10 @@
@Test
fun groupingRuleHasCorrectRank() {
- assertThat(groupingRule.rank).isGreaterThan(UsageGroupingRulesDefaultRanks.AFTER_SCOPE.absoluteRank)
- assertThat(groupingRule.rank).isAtMost(UsageGroupingRulesDefaultRanks.BEFORE_USAGE_TYPE.absoluteRank)
+ assertThat(groupingRule.rank)
+ .isGreaterThan(UsageGroupingRulesDefaultRanks.AFTER_SCOPE.absoluteRank)
+ assertThat(groupingRule.rank)
+ .isAtMost(UsageGroupingRulesDefaultRanks.BEFORE_USAGE_TYPE.absoluteRank)
}
@Test
@@ -89,12 +92,14 @@
@Test
fun previewGroupHasCorrectText() {
- assertThat(PreviewUsageGroup.presentableGroupText).isEqualTo(ComposeBundle.message("usage.group.in.preview.function"))
+ assertThat(PreviewUsageGroup.presentableGroupText)
+ .isEqualTo(ComposeBundle.message("usage.group.in.preview.function"))
}
@Test
fun productionGroupHasCorrectText() {
- assertThat(ProductionUsageGroup.presentableGroupText).isEqualTo(ComposeBundle.message("usage.group.in.nonpreview.function"))
+ assertThat(ProductionUsageGroup.presentableGroupText)
+ .isEqualTo(ComposeBundle.message("usage.group.in.nonpreview.function"))
}
@Test
@@ -106,34 +111,39 @@
}
}
-/** Test more complex cases for the [ComposeUsageGroupingRuleProvider], covering all relevant potential annotations. */
+/**
+ * Test more complex cases for the [ComposeUsageGroupingRuleProvider], covering all relevant
+ * potential annotations.
+ */
@RunWith(Parameterized::class)
class ComposeUsageGroupingRuleProviderParameterizedTest(
private val targetAnnotations: List<String>,
private val usageAnnotations: List<String>
) {
- @get:Rule
- val projectRule = AndroidProjectRule.onDisk().onEdt()
+ @get:Rule val projectRule = AndroidProjectRule.onDisk().onEdt()
private val fixture by lazy { projectRule.fixture }
private val project by lazy { projectRule.project }
private val groupingRuleProvider = ComposeUsageGroupingRuleProvider()
- private val groupingRule by lazy { groupingRuleProvider.getActiveRules(project).single() as UsageGroupingRuleEx }
+ private val groupingRule by lazy {
+ groupingRuleProvider.getActiveRules(project).single() as UsageGroupingRuleEx
+ }
companion object {
@Parameterized.Parameters(name = "{0}_target_{1}_element")
@JvmStatic
- fun data(): List<Array<List<String>>> = listOf(
- arrayOf(listOf(), listOf()),
- arrayOf(listOf(), listOf(COMPOSABLE)),
- arrayOf(listOf(), listOf(PREVIEW)),
- arrayOf(listOf(), listOf(PREVIEW, COMPOSABLE)),
- arrayOf(listOf(COMPOSABLE), listOf()),
- arrayOf(listOf(COMPOSABLE), listOf(COMPOSABLE)),
- arrayOf(listOf(COMPOSABLE), listOf(PREVIEW)),
- arrayOf(listOf(COMPOSABLE), listOf(PREVIEW, COMPOSABLE)),
- )
+ fun data(): List<Array<List<String>>> =
+ listOf(
+ arrayOf(listOf(), listOf()),
+ arrayOf(listOf(), listOf(COMPOSABLE)),
+ arrayOf(listOf(), listOf(PREVIEW)),
+ arrayOf(listOf(), listOf(PREVIEW, COMPOSABLE)),
+ arrayOf(listOf(COMPOSABLE), listOf()),
+ arrayOf(listOf(COMPOSABLE), listOf(COMPOSABLE)),
+ arrayOf(listOf(COMPOSABLE), listOf(PREVIEW)),
+ arrayOf(listOf(COMPOSABLE), listOf(PREVIEW, COMPOSABLE)),
+ )
private const val COMPOSABLE = "Composable"
private const val PREVIEW = "Preview"
@@ -142,42 +152,67 @@
@RunsInEdt
@Test
fun getParentGroupsFor() {
- val (usage, targets) = fixture.configureCode(
- targetAnnotations, usageAnnotations, "target|Function() // usage", "fun target|Function()")
+ val (usage, targets) =
+ fixture.configureCode(
+ targetAnnotations,
+ usageAnnotations,
+ "target|Function() // usage",
+ "fun target|Function()"
+ )
checkUsageGroups(groupingRule.getParentGroupsFor(usage, targets))
}
@RunsInEdt
@Test
fun getParentGroupsFor_nested() {
- val (usage, targets) = fixture.configureCode(
- targetAnnotations, usageAnnotations, "target|Function() // nested usage", "fun target|Function()")
+ val (usage, targets) =
+ fixture.configureCode(
+ targetAnnotations,
+ usageAnnotations,
+ "target|Function() // nested usage",
+ "fun target|Function()"
+ )
checkUsageGroups(groupingRule.getParentGroupsFor(usage, targets))
}
@RunsInEdt
@Test
fun ignoresNonKtFunctionTarget() {
- val (usage, targets) = fixture.configureCode(
- targetAnnotations, usageAnnotations, "target|Function() // usage", "PROP|ERTY", "fun target|Function()")
+ val (usage, targets) =
+ fixture.configureCode(
+ targetAnnotations,
+ usageAnnotations,
+ "target|Function() // usage",
+ "PROP|ERTY",
+ "fun target|Function()"
+ )
checkUsageGroups(groupingRule.getParentGroupsFor(usage, targets))
}
@RunsInEdt
@Test
fun ignoresNonPsiElementTarget() {
- val (usage, targets) = fixture.configureCode(
- targetAnnotations, usageAnnotations, "target|Function() // usage", "fun target|Function()")
+ val (usage, targets) =
+ fixture.configureCode(
+ targetAnnotations,
+ usageAnnotations,
+ "target|Function() // usage",
+ "fun target|Function()"
+ )
val usageTarget: UsageTarget = mock()
checkUsageGroups(groupingRule.getParentGroupsFor(usage, arrayOf(usageTarget, *targets)))
verifyNoInteractions(usageTarget)
}
- /** Asserts that we get [PreviewUsageGroup] iff all the annotations are present, else [ProductionUsageGroup]. */
+ /**
+ * Asserts that we get [PreviewUsageGroup] iff all the annotations are present, else
+ * [ProductionUsageGroup].
+ */
private fun checkUsageGroups(usageGroups: List<UsageGroup>) {
when {
COMPOSABLE !in targetAnnotations -> assertThat(usageGroups).isEmpty()
- COMPOSABLE in usageAnnotations && PREVIEW in usageAnnotations -> assertThat(usageGroups).containsExactly(PreviewUsageGroup)
+ COMPOSABLE in usageAnnotations && PREVIEW in usageAnnotations ->
+ assertThat(usageGroups).containsExactly(PreviewUsageGroup)
else -> assertThat(usageGroups).containsExactly(ProductionUsageGroup)
}
}
@@ -210,12 +245,14 @@
}
targetFunction() // usage
}
- """.trimIndent()
+ """
+ .trimIndent()
val file = addFileToProject("/src/the/regrettes/LaDiDa.kt", contents)
openFileInEditor(file.virtualFile)
val element = findParentElement<PsiElement>(elementWindow)
- val targets = targetWindows.map { findParentElement<PsiElement>(it) }.map(::TestUsageTarget).toTypedArray()
+ val targets =
+ targetWindows.map { findParentElement<PsiElement>(it) }.map(::TestUsageTarget).toTypedArray()
return TestUsage(element) to targets
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/TestUtils.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/TestUtils.kt
index c75746f..9f3a6f4 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/TestUtils.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/TestUtils.kt
@@ -19,8 +19,8 @@
import com.intellij.openapi.application.ex.PathManagerEx
import java.nio.file.Files
-fun getComposePluginTestDataPath():String {
+fun getComposePluginTestDataPath(): String {
val adtPath = resolveWorkspacePath("tools/adt/idea/compose-ide-plugin/testData")
return if (Files.exists(adtPath)) adtPath.toString()
- else PathManagerEx.findFileUnderCommunityHome("plugins/android-compose-ide-plugin").path
+ else PathManagerEx.findFileUnderCommunityHome("plugins/android-compose-ide-plugin").path
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/analysis/ComposableDeclarationCheckerTests.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/analysis/ComposableDeclarationCheckerTests.kt
index 732b9f4..2a0555a 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/analysis/ComposableDeclarationCheckerTests.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/analysis/ComposableDeclarationCheckerTests.kt
@@ -474,10 +474,11 @@
@Test
fun testMissingOverrideComposableLambda() {
- val functionDeclarationWithError = if (!isK2Plugin())
- "<error descr=\"[CONFLICTING_OVERLOADS] @Composable annotation mismatch with overridden function: public open fun invoke(): Unit defined in com.example.Impl, public abstract operator fun invoke(): Unit defined in kotlin.Function0\" textAttributesKey=\"ERRORS_ATTRIBUTES\">override fun invoke()</error> {}"
- else
- "<error descr=\"[CONFLICTING_OVERLOADS] Conflicting overloads: [fun invoke(): Unit, @Composable() fun invoke(): R]\" textAttributesKey=\"ERRORS_ATTRIBUTES\">override fun invoke()</error> {}"
+ val functionDeclarationWithError =
+ if (!isK2Plugin())
+ "<error descr=\"[CONFLICTING_OVERLOADS] @Composable annotation mismatch with overridden function: public open fun invoke(): Unit defined in com.example.Impl, public abstract operator fun invoke(): Unit defined in kotlin.Function0\" textAttributesKey=\"ERRORS_ATTRIBUTES\">override fun invoke()</error> {}"
+ else
+ "<error descr=\"[CONFLICTING_OVERLOADS] Conflicting overloads: [fun invoke(): Unit, @Composable() fun invoke(): R]\" textAttributesKey=\"ERRORS_ATTRIBUTES\">override fun invoke()</error> {}"
doTest(
"""
import androidx.compose.runtime.Composable
@@ -491,10 +492,11 @@
@Test
fun testWrongOverrideLambda() {
- val functionDeclarationWithError = if (!isK2Plugin())
- "<error descr=\"[CONFLICTING_OVERLOADS] @Composable annotation mismatch with overridden function: @Composable public open fun invoke(): Unit defined in com.example.Impl, public abstract operator fun invoke(): Unit defined in kotlin.Function0\" textAttributesKey=\"ERRORS_ATTRIBUTES\">@Composable override fun invoke()</error> {}"
- else
- "<error descr=\"[CONFLICTING_OVERLOADS] Conflicting overloads: [@Composable() fun invoke(): Unit, fun invoke(): R]\" textAttributesKey=\"ERRORS_ATTRIBUTES\">@Composable override fun invoke()</error> {}"
+ val functionDeclarationWithError =
+ if (!isK2Plugin())
+ "<error descr=\"[CONFLICTING_OVERLOADS] @Composable annotation mismatch with overridden function: @Composable public open fun invoke(): Unit defined in com.example.Impl, public abstract operator fun invoke(): Unit defined in kotlin.Function0\" textAttributesKey=\"ERRORS_ATTRIBUTES\">@Composable override fun invoke()</error> {}"
+ else
+ "<error descr=\"[CONFLICTING_OVERLOADS] Conflicting overloads: [@Composable() fun invoke(): Unit, fun invoke(): R]\" textAttributesKey=\"ERRORS_ATTRIBUTES\">@Composable override fun invoke()</error> {}"
doTest(
"""
import androidx.compose.runtime.Composable
@@ -508,10 +510,11 @@
@Test
fun testMultipleOverrideLambda() {
- val functionDeclarationWithError = if (!isK2Plugin())
- "<error descr=\"[CONFLICTING_OVERLOADS] @Composable annotation mismatch with overridden function: @Composable public open fun invoke(): Unit defined in com.example.Impl, public abstract operator fun invoke(): Unit defined in kotlin.Function0\" textAttributesKey=\"ERRORS_ATTRIBUTES\">@Composable override fun invoke()</error> {}"
- else
- "<error descr=\"[CONFLICTING_OVERLOADS] Conflicting overloads: [@Composable() fun invoke(): Unit, fun invoke(): R]\" textAttributesKey=\"ERRORS_ATTRIBUTES\">@Composable override fun invoke()</error> {}"
+ val functionDeclarationWithError =
+ if (!isK2Plugin())
+ "<error descr=\"[CONFLICTING_OVERLOADS] @Composable annotation mismatch with overridden function: @Composable public open fun invoke(): Unit defined in com.example.Impl, public abstract operator fun invoke(): Unit defined in kotlin.Function0\" textAttributesKey=\"ERRORS_ATTRIBUTES\">@Composable override fun invoke()</error> {}"
+ else
+ "<error descr=\"[CONFLICTING_OVERLOADS] Conflicting overloads: [@Composable() fun invoke(): Unit, fun invoke(): R]\" textAttributesKey=\"ERRORS_ATTRIBUTES\">@Composable override fun invoke()</error> {}"
doTest(
"""
import androidx.compose.runtime.Composable
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/code/ComposableFunctionRenderingTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/code/ComposableFunctionRenderingTest.kt
index 7443e02..b5527e6 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/code/ComposableFunctionRenderingTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/code/ComposableFunctionRenderingTest.kt
@@ -33,8 +33,7 @@
@RunWith(JUnit4::class)
class ComposableFunctionRenderingTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory()
private val myFixture: CodeInsightTestFixture by lazy { projectRule.fixture }
@@ -56,7 +55,8 @@
@Composable
fun ${caret}HomeScreen() {}
- """.trimIndent()
+ """
+ .trimIndent()
)
with(getComposableFunctionRenderPartsAtCaret()) {
@@ -77,7 +77,8 @@
@Composable
fun ${caret}HomeScreen(foo: Int = 0, bar: String = "") {}
- """.trimIndent()
+ """
+ .trimIndent()
)
with(getComposableFunctionRenderPartsAtCaret()) {
@@ -98,7 +99,8 @@
@Composable
fun ${caret}HomeScreen(foo: Int, bar: String) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
with(getComposableFunctionRenderPartsAtCaret()) {
@@ -119,7 +121,8 @@
@Composable
fun ${caret}HomeScreen(foo: Int, bar: String = "", baz: Int = "") {}
- """.trimIndent()
+ """
+ .trimIndent()
)
with(getComposableFunctionRenderPartsAtCaret()) {
@@ -140,7 +143,8 @@
@Composable
fun ${caret}HomeScreen(foo: @Composable () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
with(getComposableFunctionRenderPartsAtCaret()) {
@@ -161,7 +165,8 @@
@Composable
fun ${caret}HomeScreen(foo: () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
with(getComposableFunctionRenderPartsAtCaret()) {
@@ -182,7 +187,8 @@
@Composable
fun ${caret}HomeScreen(a: Int, b: Int = 0, foo: @Composable () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
with(getComposableFunctionRenderPartsAtCaret()) {
@@ -203,10 +209,12 @@
@Composable
fun ${caret}HomeScreen(a: Int, b: Int = 0, foo: () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
- // This seems like odd behavior, but it's documenting the existing behavior at the time this test is being written.
+ // This seems like odd behavior, but it's documenting the existing behavior at the time this
+ // test is being written.
with(getComposableFunctionRenderPartsAtCaret()) {
assertThat(parameters).isEqualTo("(a: Int, foo: () -> Unit, ...)")
assertThat(tail).isNull()
@@ -215,6 +223,7 @@
private fun getComposableFunctionRenderPartsAtCaret() = runReadAction {
val element = myFixture.elementAtCaret as KtDeclaration
- element.getComposableFunctionRenderParts() ?: throw AssertionError("Test must contain a valid composable function")
+ element.getComposableFunctionRenderParts()
+ ?: throw AssertionError("Test must contain a valid composable function")
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/code/ComposeLineMarkerProviderDescriptorTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/code/ComposeLineMarkerProviderDescriptorTest.kt
index 2dce28f..9c02263 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/code/ComposeLineMarkerProviderDescriptorTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/code/ComposeLineMarkerProviderDescriptorTest.kt
@@ -35,8 +35,7 @@
@RunWith(JUnit4::class)
class ComposeLineMarkerProviderDescriptorTest {
- @get:Rule
- val projectRule = AndroidProjectRule.onDisk()
+ @get:Rule val projectRule = AndroidProjectRule.onDisk()
private lateinit var myFixture: CodeInsightTestFixture
@@ -50,10 +49,11 @@
@Test
fun composableFunction_identifierHasMarker() {
- val psiFile = myFixture.addFileToProject(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val psiFile =
+ myFixture.addFileToProject(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -65,8 +65,9 @@
fun HomeScreen() {
MyButton() // invocation
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
var identifier: LeafPsiElement? = null
ApplicationManager.getApplication().invokeAndWait {
@@ -74,16 +75,19 @@
identifier = myFixture.moveCaret("MyBut|ton() // invocation") as LeafPsiElement
}
- val lineMarkerInfo = runReadAction { ComposeLineMarkerProviderDescriptor().getLineMarkerInfo(identifier!!) }
+ val lineMarkerInfo = runReadAction {
+ ComposeLineMarkerProviderDescriptor().getLineMarkerInfo(identifier!!)
+ }
assertThat(lineMarkerInfo).isNotNull()
}
@Test
fun composableFunction_ktFunctionElementHasNoMarker() {
- val psiFile = myFixture.addFileToProject(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val psiFile =
+ myFixture.addFileToProject(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -95,8 +99,9 @@
fun HomeScreen() {
MyButton() // invocation
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
var functionElement: KtNamedFunction? = null
ApplicationManager.getApplication().invokeAndWait {
@@ -104,16 +109,19 @@
functionElement = myFixture.moveCaret("MyBut|ton() // invocation").parentOfType()!!
}
- val lineMarkerInfo = runReadAction { ComposeLineMarkerProviderDescriptor().getLineMarkerInfo(functionElement!!) }
+ val lineMarkerInfo = runReadAction {
+ ComposeLineMarkerProviderDescriptor().getLineMarkerInfo(functionElement!!)
+ }
assertThat(lineMarkerInfo).isNull()
}
@Test
fun nonComposableFunction_noMarker() {
- val psiFile = myFixture.addFileToProject(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val psiFile =
+ myFixture.addFileToProject(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
fun MyButton() {}
@@ -121,8 +129,9 @@
fun HomeScreen() {
MyButton() // invocation
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
var identifier: LeafPsiElement? = null
ApplicationManager.getApplication().invokeAndWait {
@@ -130,7 +139,9 @@
identifier = myFixture.moveCaret("MyBut|ton() // invocation") as LeafPsiElement
}
- val lineMarkerInfo = runReadAction { ComposeLineMarkerProviderDescriptor().getLineMarkerInfo(identifier!!) }
+ val lineMarkerInfo = runReadAction {
+ ComposeLineMarkerProviderDescriptor().getLineMarkerInfo(identifier!!)
+ }
assertThat(lineMarkerInfo).isNull()
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/code/ComposeStateReadAnnotatorTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/code/ComposeStateReadAnnotatorTest.kt
index 0df0ff36..1ccc8ce 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/code/ComposeStateReadAnnotatorTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/code/ComposeStateReadAnnotatorTest.kt
@@ -43,8 +43,7 @@
@RunWith(JUnit4::class)
@RunsInEdt
class ComposeStateReadAnnotatorTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory().onEdt()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory().onEdt()
private val fixture: CodeInsightTestFixture by lazy { projectRule.fixture }
private val annotator = ComposeStateReadAnnotator()
@@ -60,10 +59,11 @@
@Test
fun delegatedPropertyRead() {
- val psiFile = fixture.loadNewFile(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val psiFile =
+ fixture.loadNewFile(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -80,20 +80,22 @@
@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) { }
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
val allElements = psiFile.collectDescendantsOfType<PsiElement>()
val annotations = CodeInsightTestUtil.testAnnotator(annotator, *allElements.toTypedArray())
- assertThat(annotations).hasSize(1);
+ assertThat(annotations).hasSize(1)
with(annotations.first()) {
assertThat(message).isEqualTo(createMessage("name", "HelloScreen"))
assertThat(message).isEqualTo(message)
assertThat(gutterIconRenderer).isNotNull()
assertThat(gutterIconRenderer!!.icon).isEqualTo(EXPECTED_ICON)
assertThat(gutterIconRenderer!!.tooltipText).isEqualTo(message)
- assertThat(textAttributes).isEqualTo(ComposeStateReadAnnotator.COMPOSE_STATE_READ_TEXT_ATTRIBUTES_KEY)
+ assertThat(textAttributes)
+ .isEqualTo(ComposeStateReadAnnotator.COMPOSE_STATE_READ_TEXT_ATTRIBUTES_KEY)
assertThat(startOffset).isEqualTo(fixture.offsetForWindow("HelloContent(name = |name)"))
assertThat(endOffset).isEqualTo(fixture.offsetForWindow("HelloContent(name = name|)"))
}
@@ -101,10 +103,11 @@
@Test
fun assignedPropertyRead() {
- val psiFile = fixture.loadNewFile(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val psiFile =
+ fixture.loadNewFile(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -119,30 +122,35 @@
@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) { }
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
val allElements = psiFile.collectDescendantsOfType<PsiElement>()
val annotations = CodeInsightTestUtil.testAnnotator(annotator, *allElements.toTypedArray())
- assertThat(annotations).hasSize(1);
+ assertThat(annotations).hasSize(1)
with(annotations.first()) {
assertThat(message).isEqualTo(createMessage("assignedName", "HelloScreen"))
assertThat(message).isEqualTo(message)
assertThat(gutterIconRenderer).isNotNull()
assertThat(gutterIconRenderer!!.icon).isEqualTo(EXPECTED_ICON)
assertThat(gutterIconRenderer!!.tooltipText).isEqualTo(message)
- assertThat(textAttributes).isEqualTo(ComposeStateReadAnnotator.COMPOSE_STATE_READ_TEXT_ATTRIBUTES_KEY)
- assertThat(startOffset).isEqualTo(fixture.offsetForWindow("HelloContent(name = assignedName.|value)"))
- assertThat(endOffset).isEqualTo(fixture.offsetForWindow("HelloContent(name = assignedName.value|)"))
+ assertThat(textAttributes)
+ .isEqualTo(ComposeStateReadAnnotator.COMPOSE_STATE_READ_TEXT_ATTRIBUTES_KEY)
+ assertThat(startOffset)
+ .isEqualTo(fixture.offsetForWindow("HelloContent(name = assignedName.|value)"))
+ assertThat(endOffset)
+ .isEqualTo(fixture.offsetForWindow("HelloContent(name = assignedName.value|)"))
}
}
@Test
fun listPropertyRead() {
- val psiFile = fixture.loadNewFile(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val psiFile =
+ fixture.loadNewFile(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -157,30 +165,35 @@
@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) { }
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
val allElements = psiFile.collectDescendantsOfType<PsiElement>()
val annotations = CodeInsightTestUtil.testAnnotator(annotator, *allElements.toTypedArray())
- assertThat(annotations).hasSize(1);
+ assertThat(annotations).hasSize(1)
with(annotations.first()) {
assertThat(message).isEqualTo(createMessage("listName[0]", "HelloScreen"))
assertThat(gutterIconRenderer).isNotNull()
assertThat(gutterIconRenderer!!.icon).isEqualTo(EXPECTED_ICON)
assertThat(gutterIconRenderer!!.tooltipText).isEqualTo(message)
- assertThat(textAttributes).isEqualTo(ComposeStateReadAnnotator.COMPOSE_STATE_READ_TEXT_ATTRIBUTES_KEY)
- assertThat(startOffset).isEqualTo(fixture.offsetForWindow("HelloContent(name = listName[0].|value)"))
- assertThat(endOffset).isEqualTo(fixture.offsetForWindow("HelloContent(name = listName[0].value|)"))
+ assertThat(textAttributes)
+ .isEqualTo(ComposeStateReadAnnotator.COMPOSE_STATE_READ_TEXT_ATTRIBUTES_KEY)
+ assertThat(startOffset)
+ .isEqualTo(fixture.offsetForWindow("HelloContent(name = listName[0].|value)"))
+ assertThat(endOffset)
+ .isEqualTo(fixture.offsetForWindow("HelloContent(name = listName[0].value|)"))
}
}
@Test
fun nestedPropertyRead() {
- val psiFile = fixture.loadNewFile(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val psiFile =
+ fixture.loadNewFile(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -198,21 +211,25 @@
fun HelloContent(name: String, onNameChange: (String) -> Unit) { }
class StateHolder(val name: MutableState<String>)
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
val allElements = psiFile.collectDescendantsOfType<PsiElement>()
val annotations = CodeInsightTestUtil.testAnnotator(annotator, *allElements.toTypedArray())
- assertThat(annotations).hasSize(1);
+ assertThat(annotations).hasSize(1)
with(annotations.first()) {
assertThat(message).isEqualTo(createMessage("name", "HelloScreen"))
assertThat(gutterIconRenderer).isNotNull()
assertThat(gutterIconRenderer!!.icon).isEqualTo(EXPECTED_ICON)
assertThat(gutterIconRenderer!!.tooltipText).isEqualTo(message)
- assertThat(textAttributes).isEqualTo(ComposeStateReadAnnotator.COMPOSE_STATE_READ_TEXT_ATTRIBUTES_KEY)
- assertThat(startOffset).isEqualTo(fixture.offsetForWindow("HelloContent(name = container.name.|value)"))
- assertThat(endOffset).isEqualTo(fixture.offsetForWindow("HelloContent(name = container.name.value|)"))
+ assertThat(textAttributes)
+ .isEqualTo(ComposeStateReadAnnotator.COMPOSE_STATE_READ_TEXT_ATTRIBUTES_KEY)
+ assertThat(startOffset)
+ .isEqualTo(fixture.offsetForWindow("HelloContent(name = container.name.|value)"))
+ assertThat(endOffset)
+ .isEqualTo(fixture.offsetForWindow("HelloContent(name = container.name.value|)"))
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/code/actions/ComposeProximityWeigherTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/code/actions/ComposeProximityWeigherTest.kt
index e06cb01..44f5d5a 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/code/actions/ComposeProximityWeigherTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/code/actions/ComposeProximityWeigherTest.kt
@@ -40,18 +40,19 @@
/**
* Tests for [ComposeProximityWeigher].
*
- * Unfortunately it's not possible to validate the order of items in the "Add Imports" list end to end in a unit test, because most of the
- * logic involved is internal in the Kotlin plugin. We can execute the correct intention, but in a unit test it will not pop up a dialog,
- * instead just selecting the first item.
+ * Unfortunately it's not possible to validate the order of items in the "Add Imports" list end to
+ * end in a unit test, because most of the logic involved is internal in the Kotlin plugin. We can
+ * execute the correct intention, but in a unit test it will not pop up a dialog, instead just
+ * selecting the first item.
*
- * This file contains some tests that validate that the Weigher is correctly wired up, by validating that the intention results in an import
- * being added that wouldn't have been used if [ComposeProximityWeigher] isn't running. The remaining tests are more traditional unit tests,
+ * This file contains some tests that validate that the Weigher is correctly wired up, by validating
+ * that the intention results in an import being added that wouldn't have been used if
+ * [ComposeProximityWeigher] isn't running. The remaining tests are more traditional unit tests,
* working directly with [ComposeProximityWeigher] outside the context of the intention.
*/
@RunWith(JUnit4::class)
class ComposeProximityWeigherTest {
- @get:Rule
- val projectRule = AndroidProjectRule.onDisk()
+ @get:Rule val projectRule = AndroidProjectRule.onDisk()
private val myFixture: CodeInsightTestFixture by lazy { projectRule.fixture }
@@ -63,8 +64,10 @@
@Test
fun validateWeigherIsWiredUp() {
- // When two imports have equal weight (which will be the default in the unit test), the Kotlin plugin's logic falls back to using
- // lexicographic ordering. This "Modifier" class will begin with "aaa", and thus would be listed first absent [ComposeProximityWeigher]
+ // When two imports have equal weight (which will be the default in the unit test), the Kotlin
+ // plugin's logic falls back to using
+ // lexicographic ordering. This "Modifier" class will begin with "aaa", and thus would be listed
+ // first absent [ComposeProximityWeigher]
// changing the order.
myFixture.addFileToProject(
"src/aaa/example/foo/Modifier.kt",
@@ -73,7 +76,8 @@
package aaa.example.foo
interface Modifier
- """)
+ """
+ )
myFixture.addFileToProject(
"src/androidx/compose/ui/Modifier.kt",
@@ -82,25 +86,26 @@
package androidx.compose.ui
interface Modifier
- """)
-
- val psiFile = myFixture.loadNewFile(
- "src/com/example/Test.kt",
- // language=kotlin
"""
+ )
+
+ val psiFile =
+ myFixture.loadNewFile(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@Composable
fun HomeScreen(modifier: Mod${caret}ifier) {}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
val action = myFixture.getIntentionAction("Import class 'Modifier'")!!
- runInEdt {
- action.invoke(myFixture.project, myFixture.editor, psiFile)
- }
+ runInEdt { action.invoke(myFixture.project, myFixture.editor, psiFile) }
myFixture.checkResult(
// language=kotlin
@@ -112,15 +117,19 @@
@Composable
fun HomeScreen(modifier: Mod${caret}ifier) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@Test
fun validateWeigherIsBeforeJavaInheritance() {
- // IntelliJ's com.intellij.psi.util.proximity.JavaInheritanceWeigher is promoting Java classes above everything else in the import list,
- // presumably by accident. We can avoid that behavior for @Composable functions by ensuring our weigher runs before that one. This test
- // validates that scenario, by including a Java class in the potential import list and ensuring it's below a promoted class.
+ // IntelliJ's com.intellij.psi.util.proximity.JavaInheritanceWeigher is promoting Java classes
+ // above everything else in the import list,
+ // presumably by accident. We can avoid that behavior for @Composable functions by ensuring our
+ // weigher runs before that one. This test
+ // validates that scenario, by including a Java class in the potential import list and ensuring
+ // it's below a promoted class.
myFixture.addFileToProject(
"src/android/graphics/Color.java",
// language=java
@@ -128,7 +137,8 @@
package android.graphics;
public class Color {}
- """)
+ """
+ )
myFixture.addFileToProject(
"src/androidx/compose/ui/graphics/Color.kt",
@@ -137,7 +147,8 @@
package androidx.compose.ui.graphics
value class Color
- """)
+ """
+ )
myFixture.addFileToProject(
"src/androidx/compose/material/Surface.kt",
@@ -150,12 +161,14 @@
@Composable
fun Surface(color: Color) {}
- """)
-
- val psiFile = myFixture.loadNewFile(
- "src/com/example/Test.kt",
- // language=kotlin
"""
+ )
+
+ val psiFile =
+ myFixture.loadNewFile(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.material.Surface
@@ -166,13 +179,12 @@
Surface(color = Co<caret>lor.White) {
}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
val action = myFixture.getIntentionAction("Import class 'Color'")!!
- runInEdt {
- action.invoke(myFixture.project, myFixture.editor, psiFile)
- }
+ runInEdt { action.invoke(myFixture.project, myFixture.editor, psiFile) }
myFixture.checkResult(
// language=kotlin
@@ -188,31 +200,35 @@
Surface(color = Co<caret>lor.White) {
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@Test
fun validateBasicOrdering() {
- val composableFunction = addFileAndFindElement(
- "src/com/example/composable/ComposableFunction.kt",
- // language=kotlin
- """
+ val composableFunction =
+ addFileAndFindElement(
+ "src/com/example/composable/ComposableFunction.kt",
+ // language=kotlin
+ """
package com.example.composable
import androidx.compose.runtime.Composable
@Composable
fun ComposableFunction()
- """.trimIndent(),
- "ComposableFunction",
- KtNamedFunction::class.java
- )
-
- val deprecatedComposableFunction = addFileAndFindElement(
- "src/com/example/composable/DeprecatedComposableFunction.kt",
- // language=kotlin
"""
+ .trimIndent(),
+ "ComposableFunction",
+ KtNamedFunction::class.java
+ )
+
+ val deprecatedComposableFunction =
+ addFileAndFindElement(
+ "src/com/example/composable/DeprecatedComposableFunction.kt",
+ // language=kotlin
+ """
package com.example.composable
import androidx.compose.runtime.Composable
@@ -220,60 +236,85 @@
@Composable
@Deprecated
fun DeprecatedComposableFunction()
- """.trimIndent(),
- "ComposableFunction",
- KtNamedFunction::class.java
- )
-
- val nonComposableFunction = addFileAndFindElement(
- "src/com/example/noncomposable/NonComposableFunction.kt",
- // language=kotlin
"""
+ .trimIndent(),
+ "ComposableFunction",
+ KtNamedFunction::class.java
+ )
+
+ val nonComposableFunction =
+ addFileAndFindElement(
+ "src/com/example/noncomposable/NonComposableFunction.kt",
+ // language=kotlin
+ """
package com.example.noncomposable
fun NonComposableFunction()
- """.trimIndent(),
- "NonComposableFunction",
- KtNamedFunction::class.java
- )
-
- val manuallyWeightedElement = addFileAndFindElement(
- "src/androidx/compose/ui/Modifier.kt",
- // language=kotlin
"""
+ .trimIndent(),
+ "NonComposableFunction",
+ KtNamedFunction::class.java
+ )
+
+ val manuallyWeightedElement =
+ addFileAndFindElement(
+ "src/androidx/compose/ui/Modifier.kt",
+ // language=kotlin
+ """
package androidx.compose.ui
object Modifier
- """.trimIndent(),
- "Modifier",
- KtObjectDeclaration::class.java
- )
-
- val locationFile = myFixture.loadNewFile(
- "src/com/example/Test.kt",
- // language=kotlin
"""
+ .trimIndent(),
+ "Modifier",
+ KtObjectDeclaration::class.java
+ )
+
+ val locationFile =
+ myFixture.loadNewFile(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
val proximityLocation = ProximityLocation(locationFile, myFixture.module)
val sortedList = runReadAction {
- listOf(nonComposableFunction, deprecatedComposableFunction, composableFunction, manuallyWeightedElement)
- .sortedByDescending { element -> ComposeProximityWeigher().weigh(element, proximityLocation) }
+ listOf(
+ nonComposableFunction,
+ deprecatedComposableFunction,
+ composableFunction,
+ manuallyWeightedElement
+ )
+ .sortedByDescending { element ->
+ ComposeProximityWeigher().weigh(element, proximityLocation)
+ }
}
- assertThat(sortedList).containsExactly(manuallyWeightedElement, composableFunction, nonComposableFunction,
- deprecatedComposableFunction).inOrder()
+ assertThat(sortedList)
+ .containsExactly(
+ manuallyWeightedElement,
+ composableFunction,
+ nonComposableFunction,
+ deprecatedComposableFunction
+ )
+ .inOrder()
}
- fun <T : PsiElement> addFileAndFindElement(relativePath: String,
- fileText: String,
- targetElementText: String,
- targetElementClass: Class<T>): T {
+ fun <T : PsiElement> addFileAndFindElement(
+ relativePath: String,
+ fileText: String,
+ targetElementText: String,
+ targetElementClass: Class<T>
+ ): T {
val psiFile = myFixture.addFileToProject(relativePath, fileText)
return runReadAction {
- PsiTreeUtil.getParentOfType(psiFile.findElementAt(psiFile.text.indexOf(targetElementText)), targetElementClass)!!
+ PsiTreeUtil.getParentOfType(
+ psiFile.findElementAt(psiFile.text.indexOf(targetElementText)),
+ targetElementClass
+ )!!
}
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeCompletionContributorTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeCompletionContributorTest.kt
index afe7ff8..f422eeb 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeCompletionContributorTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeCompletionContributorTest.kt
@@ -27,18 +27,16 @@
import com.intellij.openapi.application.runReadAction
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import org.jetbrains.android.compose.stubComposableAnnotation
+import org.jetbrains.kotlin.idea.base.plugin.isK2Plugin
import org.jetbrains.kotlin.psi.KtProperty
import org.junit.Before
import org.junit.Rule
import org.junit.Test
-/**
- * Tests for [ComposeCompletionContributor].
- */
+/** Tests for [ComposeCompletionContributor]. */
class ComposeCompletionContributorTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory()
private val myFixture: CodeInsightTestFixture by lazy { projectRule.fixture }
@@ -74,16 +72,18 @@
@Composable
fun FoobarFive(icon: String, onClick: () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val expectedLookupItems = listOf(
- "FoobarOne(required: Int)",
- "FoobarTwo(required: Int, ...)",
- "FoobarThree(...) {...}",
- "FoobarFour {...}",
- "FoobarFive(icon: String, onClick: () -> Unit)"
- )
+ val expectedLookupItems =
+ listOf(
+ "FoobarOne(required: Int)",
+ "FoobarTwo(required: Int, ...)",
+ "FoobarThree(...) {...}",
+ "FoobarFour {...}",
+ "FoobarFive(icon: String, onClick: () -> Unit)"
+ )
// Given:
myFixture.loadNewFile(
@@ -98,14 +98,16 @@
fun HomeScreen() {
Foobar${caret}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
// When:
myFixture.completeBasic()
// Then:
- // Order doesn't matter here, since we're just validating that the elements are displayed with the correct signature text.
+ // Order doesn't matter here, since we're just validating that the elements are displayed with
+ // the correct signature text.
assertThat(myFixture.renderedLookupElements).containsExactlyElementsIn(expectedLookupItems)
// Given:
@@ -126,14 +128,16 @@
}
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
// When:
myFixture.completeBasic()
// Then:
- // Order doesn't matter here, since we're just validating that the elements are displayed with the correct signature text.
+ // Order doesn't matter here, since we're just validating that the elements are displayed with
+ // the correct signature text.
assertThat(myFixture.renderedLookupElements).containsExactlyElementsIn(expectedLookupItems)
}
@@ -151,13 +155,15 @@
@Composable
fun FoobarOne(first: Int, second: String, third: String? = null) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val file = myFixture.addFileToProject(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val file =
+ myFixture.addFileToProject(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -166,8 +172,9 @@
fun HomeScreen() {
Foobar${caret}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
// When:
myFixture.configureFromExistingVirtualFile(file.virtualFile)
@@ -185,7 +192,8 @@
fun HomeScreen() {
FoobarOne(first = , second = )
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -203,13 +211,15 @@
@Composable
fun FoobarOne(first: Int, second: String, third: String? = null) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
- var file = myFixture.addFileToProject(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ var file =
+ myFixture.addFileToProject(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -218,8 +228,9 @@
fun HomeScreen() {
Foobar${caret}()
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
// When:
myFixture.configureFromExistingVirtualFile(file.virtualFile)
@@ -237,15 +248,16 @@
fun HomeScreen() {
FoobarOne()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
-
// Check completion with tab
- file = myFixture.addFileToProject(
- "src/com/example/Test2.kt",
- // language=kotlin
- """
+ file =
+ myFixture.addFileToProject(
+ "src/com/example/Test2.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -254,8 +266,9 @@
fun HomeScreen() {
${caret}()
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
// When:
myFixture.configureFromExistingVirtualFile(file.virtualFile)
@@ -274,7 +287,8 @@
fun HomeScreen() {
FoobarOne()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -295,13 +309,15 @@
@Composable
fun FoobarTwo(children: () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val file = myFixture.addFileToProject(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val file =
+ myFixture.addFileToProject(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -310,8 +326,9 @@
fun HomeScreen() {
FoobarO${caret}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
// When:
myFixture.configureFromExistingVirtualFile(file.virtualFile)
@@ -331,13 +348,16 @@
}
}
- """.trimIndent()
- , true)
-
- val file2 = myFixture.loadNewFile(
- "src/com/example/Test2.kt",
- // language=kotlin
"""
+ .trimIndent(),
+ true
+ )
+
+ val file2 =
+ myFixture.loadNewFile(
+ "src/com/example/Test2.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -346,8 +366,9 @@
fun HomeScreen() {
FoobarT${caret}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
// When:
myFixture.configureFromExistingVirtualFile(file2.virtualFile)
@@ -367,8 +388,10 @@
}
}
- """.trimIndent()
- , true)
+ """
+ .trimIndent(),
+ true
+ )
}
@Test
@@ -385,13 +408,15 @@
@Composable
fun FoobarOne(children: @Composable() () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
- var file = myFixture.addFileToProject(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ var file =
+ myFixture.addFileToProject(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -403,8 +428,9 @@
}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
// When:
myFixture.configureFromExistingVirtualFile(file.virtualFile)
@@ -426,14 +452,17 @@
}
}
- """.trimIndent()
- , true)
+ """
+ .trimIndent(),
+ true
+ )
// Given:
- file = myFixture.addFileToProject(
- "src/com/example/Test2.kt",
- // language=kotlin
- """
+ file =
+ myFixture.addFileToProject(
+ "src/com/example/Test2.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -445,8 +474,9 @@
}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
// When:
myFixture.configureFromExistingVirtualFile(file.virtualFile)
@@ -468,8 +498,10 @@
}
}
- """.trimIndent()
- , true)
+ """
+ .trimIndent(),
+ true
+ )
}
@Test
@@ -486,13 +518,15 @@
@Composable
fun FoobarOne(optional: String? = null, children: @Composable() () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val file = myFixture.addFileToProject(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val file =
+ myFixture.addFileToProject(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -501,8 +535,9 @@
fun HomeScreen() {
Foobar${caret}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
// When:
myFixture.configureFromExistingVirtualFile(file.virtualFile)
@@ -522,8 +557,10 @@
}
}
- """.trimIndent()
- , true)
+ """
+ .trimIndent(),
+ true
+ )
}
@Test
@@ -540,13 +577,15 @@
@Composable
fun AppBarIcon(icon: String, onClick: () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val file = myFixture.addFileToProject(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val file =
+ myFixture.addFileToProject(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -555,8 +594,9 @@
fun HomeScreen() {
AppBarIcon${caret}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
// When:
myFixture.configureFromExistingVirtualFile(file.virtualFile)
@@ -576,8 +616,10 @@
}
}
- """.trimIndent()
- , true)
+ """
+ .trimIndent(),
+ true
+ )
}
@Test
@@ -594,13 +636,15 @@
@Composable
fun RadioButton(text: String, onClick: () -> Unit, label: String = "label") {}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val file = myFixture.addFileToProject(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val file =
+ myFixture.addFileToProject(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -609,8 +653,9 @@
fun HomeScreen() {
RadioButton${caret}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
// When:
myFixture.configureFromExistingVirtualFile(file.virtualFile)
@@ -628,8 +673,10 @@
fun HomeScreen() {
RadioButton(text = , onClick = { /*TODO*/ })
}
- """.trimIndent()
- , true)
+ """
+ .trimIndent(),
+ true
+ )
}
@Test
@@ -646,13 +693,15 @@
@Composable
fun FoobarOne(first: Int, second: String, third: String? = null) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val file = myFixture.addFileToProject(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val file =
+ myFixture.addFileToProject(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -661,8 +710,9 @@
fun HomeScreen() {
Foobar${caret}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
try {
// When:
@@ -671,6 +721,7 @@
myFixture.completeBasic()
// Then:
+ val indentation = if (isK2Plugin()) " " else ""
myFixture.checkResult(
// language=kotlin
"""
@@ -680,9 +731,10 @@
@Composable
fun HomeScreen() {
- FoobarOne()
+ ${indentation}FoobarOne()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
} finally {
ComposeSettings.getInstance().state.isComposeInsertHandlerEnabled = true
@@ -703,13 +755,15 @@
@Composable
fun FoobarOne(first: Int, second: String, third: String? = null) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val file = myFixture.addFileToProject(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val file =
+ myFixture.addFileToProject(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -720,8 +774,9 @@
@Composable
fun HomeScreen() {
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
// When:
myFixture.configureFromExistingVirtualFile(file.virtualFile)
@@ -741,14 +796,15 @@
@Composable
fun HomeScreen() {
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
/**
- * Regression test for b/153769933. The Compose insertion handler adds the parameters automatically when completing the name
- * of a Composable. This is incorrect if the insertion point is not a call statement. This ensures that the insertion is not triggered
- * for imports.
+ * Regression test for b/153769933. The Compose insertion handler adds the parameters
+ * automatically when completing the name of a Composable. This is incorrect if the insertion
+ * point is not a call statement. This ensures that the insertion is not triggered for imports.
*/
@Test
fun testImportCompletionDoesNotTriggerInsertionHandler() {
@@ -763,8 +819,8 @@
// This simulates the Canvas composable
@Composable
fun Canvas(children: @Composable() () -> Unit) {}
- """)
-
+ """
+ )
// Given:
myFixture.loadNewFile(
@@ -779,7 +835,8 @@
@Composable
fun Test() {
}
- """.trimIndent()
+ """
+ .trimIndent()
)
// When:
@@ -797,12 +854,15 @@
@Composable
fun Test() {
}
- """.trimIndent()
- , true)
+ """
+ .trimIndent(),
+ true
+ )
}
/**
- * Regression test for b/209672710. Ensure that completing Composables that are not top-level does not fully qualify them incorrectly.
+ * Regression test for b/209672710. Ensure that completing Composables that are not top-level does
+ * not fully qualify them incorrectly.
*/
@Test
fun testCompletingComposablesWithinObjects() {
@@ -819,8 +879,8 @@
@Composable
fun TestMethod(children: @Composable() () -> Unit) {}
}
- """)
-
+ """
+ )
// Given:
myFixture.loadNewFile(
@@ -835,16 +895,18 @@
fun Test() {
ObjectWithComposables.Test${caret}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
// When:
myFixture.completeBasic()
// Then:
- myFixture.checkResult(
- // language=kotlin
- """
+ if (!isK2Plugin()) {
+ myFixture.checkResult(
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -855,13 +917,35 @@
}
}
- """.trimIndent()
- , true)
+ """
+ .trimIndent(),
+ true
+ )
+ } else {
+ myFixture.checkResult(
+ // language=kotlin
+ """
+ package com.example
+
+ import androidx.compose.runtime.Composable
+ import com.example.ObjectWithComposables.TestMethod
+
+ @Composable
+ fun Test() {
+ TestMethod {
+
+ }
+ }
+ """
+ .trimIndent(),
+ true
+ )
+ }
}
/**
- * Regression test for b/209060418. Autocomplete should not treat required composable method specially if it's not the final argument (ie,
- * there are optional arguments specified after it.
+ * Regression test for b/209060418. Autocomplete should not treat required composable method
+ * specially if it's not the final argument (ie, there are optional arguments specified after it.
*/
@Test
fun testSignaturesWithRequiredComposableBeforeOptionalArgs() {
@@ -890,17 +974,21 @@
@Composable
fun FoobarSix(optionalArg: Int = 0) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val expectedLookupItems = listOf(
- "FoobarOne(requiredArg: () -> Unit, ...)",
- "FoobarTwo(...)",
- "FoobarThree(requiredArg: () -> Unit, optionalArg: Int = ...) (com.example) Unit",
- "FoobarFour(optionalArg: Int = ...) (com.example) Unit",
- "FoobarFive(requiredArg: () -> Unit, ...)",
- "FoobarSix(...)",
- )
+ val parameterWithComposeAnnotation =
+ if (isK2Plugin()) "@Composable (() -> Unit)" else "() -> Unit"
+ val expectedLookupItems =
+ listOf(
+ "FoobarOne(requiredArg: $parameterWithComposeAnnotation, ...)",
+ "FoobarTwo(...)",
+ "FoobarThree(requiredArg: $parameterWithComposeAnnotation, optionalArg: Int = ...) (com.example) Unit",
+ "FoobarFour(optionalArg: Int = ...) (com.example) Unit",
+ "FoobarFive(requiredArg: () -> Unit, ...)",
+ "FoobarSix(...)",
+ )
// Given:
myFixture.loadNewFile(
@@ -915,20 +1003,22 @@
fun HomeScreen() {
Foobar${caret}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
// When:
myFixture.completeBasic()
// Then:
- // Order doesn't matter here, since we're just validating that the elements are displayed with the correct signature text.
+ // Order doesn't matter here, since we're just validating that the elements are displayed with
+ // the correct signature text.
assertThat(myFixture.renderedLookupElements).containsExactlyElementsIn(expectedLookupItems)
}
/**
- * Regression test for b/209060418. Autocomplete should not treat required composable method specially if it's not the final argument (ie,
- * there are optional arguments specified after it.
+ * Regression test for b/209060418. Autocomplete should not treat required composable method
+ * specially if it's not the final argument (ie, there are optional arguments specified after it.
*/
@Test
fun testInsertHandlerWithRequiredComposableBeforeOptionalArgs() {
@@ -944,7 +1034,8 @@
@Composable
fun FoobarOne(requiredArg: @Composable () -> Unit, optionalArg: Int = 0) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
// Given:
@@ -960,7 +1051,8 @@
fun HomeScreen() {
Foobar${caret}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
// When:
@@ -978,13 +1070,13 @@
fun HomeScreen() {
FoobarOne(requiredArg = { /*TODO*/ })
}
- """.trimIndent()
- , true)
+ """
+ .trimIndent(),
+ true
+ )
}
- /**
- * Regression test for b/182564317. Autocomplete should not treat varargs as required.
- */
+ /** Regression test for b/182564317. Autocomplete should not treat varargs as required. */
@Test
fun testInsertHandlerWithVarArgs() {
myFixture.addFileToProject(
@@ -999,7 +1091,8 @@
@Composable
fun FoobarOne(vararg inputs: Any?, children: @Composable () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
// Given:
@@ -1015,7 +1108,8 @@
fun HomeScreen() {
Foobar${caret}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
// When:
@@ -1035,13 +1129,13 @@
}
}
- """.trimIndent()
- , true)
+ """
+ .trimIndent(),
+ true
+ )
}
- /**
- * Regression test for b/182564317. Autocomplete should not treat varargs as required.
- */
+ /** Regression test for b/182564317. Autocomplete should not treat varargs as required. */
@Test
fun testInsertHandlerWithVarArgsLambda() {
myFixture.addFileToProject(
@@ -1056,7 +1150,8 @@
@Composable
fun FoobarOne(vararg children: @Composable () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
// Given:
@@ -1072,7 +1167,8 @@
fun HomeScreen() {
Foobar${caret}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
// When:
@@ -1090,20 +1186,24 @@
fun HomeScreen() {
FoobarOne()
}
- """.trimIndent()
- , true)
+ """
+ .trimIndent(),
+ true
+ )
}
/**
- * Regression test for b/271675885. Autocomplete changes should apply to function invocations, not function definitions.
+ * Regression test for b/271675885. Autocomplete changes should apply to function invocations, not
+ * function definitions.
*/
@Test
fun testInsertHandler_functionDefinition() {
// Given:
- val file = myFixture.addFileToProject(
- "src/com/example/Test.kt",
- // language=kotlin
- """
+ val file =
+ myFixture.addFileToProject(
+ "src/com/example/Test.kt",
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -1117,8 +1217,9 @@
class MyComposablesImpl : MyComposables {
override fun Foo${caret}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
// When:
myFixture.configureFromExistingVirtualFile(file.virtualFile)
@@ -1143,7 +1244,8 @@
TODO("Not yet implemented")
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -1161,7 +1263,8 @@
.isEqualTo("images/material/icons/materialiconsoutlined/adb/outline_adb_24.xml")
assertThat("androidx.compose.material.icons.unknown.Adb".resourcePathFromFqName()).isNull()
- assertThat("androidx.compose.material.icons.filled.extrapackage.Adb".resourcePathFromFqName()).isNull()
+ assertThat("androidx.compose.material.icons.filled.extrapackage.Adb".resourcePathFromFqName())
+ .isNull()
// Ensure numbers in camel case are converted as expected.
assertThat("androidx.compose.material.icons.filled.Shop2".resourcePathFromFqName())
@@ -1181,7 +1284,8 @@
package androidx.compose.ui.graphics.vector
class ImageVector
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.addFileToProject(
@@ -1193,7 +1297,8 @@
object Icons {
object Filled
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.loadNewFile(
@@ -1207,7 +1312,8 @@
val androidx.compose.material.icons.Icons.Filled.Accoun<caret>tBox: ImageVector
get() = ImageVector()
- """.trimIndent()
+ """
+ .trimIndent()
)
val accountBox = runReadAction { myFixture.elementAtCaret }
@@ -1225,7 +1331,8 @@
package androidx.compose.ui.graphics.vector
class ImageVector
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.addFileToProject(
@@ -1237,7 +1344,8 @@
object Icons {
object Unknown
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.loadNewFile(
@@ -1251,7 +1359,8 @@
val androidx.compose.material.icons.Icons.Unknown.Accoun<caret>tBox: ImageVector
get() = ImageVector()
- """.trimIndent()
+ """
+ .trimIndent()
)
val accountBox = runReadAction { myFixture.elementAtCaret }
@@ -1263,10 +1372,14 @@
@Test
fun composeMaterialIconLookupElement_getIcon() {
assertThat(
- ComposeMaterialIconLookupElement.getIcon("androidx.compose.material.icons.filled.AccountBox"))
+ ComposeMaterialIconLookupElement.getIcon(
+ "androidx.compose.material.icons.filled.AccountBox"
+ )
+ )
.isNotNull()
assertThat(
- ComposeMaterialIconLookupElement.getIcon("androidx.compose.material.icons.filled.Unknown"))
+ ComposeMaterialIconLookupElement.getIcon("androidx.compose.material.icons.filled.Unknown")
+ )
.isNull()
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeCompletionWeigherTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeCompletionWeigherTest.kt
index 4c2c0cc..5252dec 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeCompletionWeigherTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeCompletionWeigherTest.kt
@@ -34,8 +34,7 @@
@RunWith(JUnit4::class)
class ComposeCompletionWeigherTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory()
private val myFixture: CodeInsightTestFixture by lazy { projectRule.fixture }
@@ -72,10 +71,11 @@
// This simulates the MaterialTheme object that should be promoted instead of the MaterialTheme
object MaterialTheme
- """)
+ """
+ )
-
- // Add a MaterialTheme that is not part of androidx to ensure is not affected by the promotion/demotion
+ // Add a MaterialTheme that is not part of androidx to ensure is not affected by the
+ // promotion/demotion
myFixture.addFileToProject(
"src/com/example/MaterialTheme.kt",
// language=kotlin
@@ -83,8 +83,8 @@
package com.example
object MaterialTheme
- """)
-
+ """
+ )
// Given:
myFixture.loadNewFile(
@@ -99,18 +99,21 @@
fun HomeScreen() {
Material$caret
}
- """.trimIndent()
+ """
+ .trimIndent()
)
// When:
myFixture.completeBasic()
// Then:
- Truth.assertThat(myFixture.renderedLookupElements).containsExactly(
- "MaterialTheme ($materialThemePackage)",
- "MaterialTheme {...}",
- "MaterialTheme (com.example)",
- ).inOrder()
+ Truth.assertThat(myFixture.renderedLookupElements)
+ .containsExactly(
+ "MaterialTheme ($materialThemePackage)",
+ "MaterialTheme {...}",
+ "MaterialTheme (com.example)",
+ )
+ .inOrder()
}
/** Regression test for b/155314487. */
@@ -140,10 +143,11 @@
// This simulates the MaterialTheme object that should be promoted instead of the MaterialTheme
object MaterialTheme
- """)
+ """
+ )
-
- // Add a MaterialTheme that is not part of androidx to ensure is not affected by the promotion/demotion
+ // Add a MaterialTheme that is not part of androidx to ensure is not affected by the
+ // promotion/demotion
myFixture.addFileToProject(
"src/com/example/MaterialTheme.kt",
// language=kotlin
@@ -151,8 +155,8 @@
package com.example
object MaterialTheme
- """)
-
+ """
+ )
// Add Color so it can be referenced without causing a missing reference.
myFixture.addFileToProject(
@@ -162,8 +166,8 @@
package androidx.compose.ui.graphics
class Color
- """)
-
+ """
+ )
// Given:
myFixture.loadNewFile(
@@ -182,24 +186,29 @@
@Composable
fun HomeScreenElement(color: Color) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
// When:
myFixture.completeBasic()
// Then:
- Truth.assertThat(myFixture.renderedLookupElements).containsExactly(
- "MaterialTheme ($materialThemePackage)",
- "MaterialTheme (com.example)",
- "MaterialTheme {...}",
- ).inOrder()
+ Truth.assertThat(myFixture.renderedLookupElements)
+ .containsExactly(
+ "MaterialTheme ($materialThemePackage)",
+ "MaterialTheme (com.example)",
+ "MaterialTheme {...}",
+ )
+ .inOrder()
}
@Test
fun testLookupElementOrder_valueArgumentWithDotExpression() {
- // This test applies to any value argument being filled in with a dot expression, as in "icon = Icons.<caret>". Using Icons specifically
- // just because they can fulfill that scenario, and the autocomplete list would be in a different order if the weighing code didn't run.
+ // This test applies to any value argument being filled in with a dot expression, as in "icon =
+ // Icons.<caret>". Using Icons specifically
+ // just because they can fulfill that scenario, and the autocomplete list would be in a
+ // different order if the weighing code didn't run.
myFixture.addFileToProject(
"src/androidx/compose/material/icons/Icons.kt",
// language=kotlin
@@ -214,7 +223,8 @@
object Sharp
object TwoTone
}
- """)
+ """
+ )
// Given:
myFixture.loadNewFile(
@@ -233,7 +243,8 @@
@Composable
fun HomeScreenElement(icon: Any) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
// When:
@@ -242,17 +253,20 @@
// Then:
val renderedLookupElements = myFixture.renderedLookupElements
- // There should be at least one more suggestion that's not one of the Icons object, but we don't really care what it is as long as it's
+ // There should be at least one more suggestion that's not one of the Icons object, but we don't
+ // really care what it is as long as it's
// ranked lower than the Icons entries.
Truth.assertThat(renderedLookupElements.size).isAtLeast(7)
- Truth.assertThat(renderedLookupElements.toList().subList(0, 6)).containsExactly(
- "Defaultnull Icons.Filled",
- "Filled (androidx.compose.material.icons.Icons)",
- "Outlined (androidx.compose.material.icons.Icons)",
- "Rounded (androidx.compose.material.icons.Icons)",
- "Sharp (androidx.compose.material.icons.Icons)",
- "TwoTone (androidx.compose.material.icons.Icons)"
- ).inOrder()
+ Truth.assertThat(renderedLookupElements.toList().subList(0, 6))
+ .containsExactly(
+ "Defaultnull Icons.Filled",
+ "Filled (androidx.compose.material.icons.Icons)",
+ "Outlined (androidx.compose.material.icons.Icons)",
+ "Rounded (androidx.compose.material.icons.Icons)",
+ "Sharp (androidx.compose.material.icons.Icons)",
+ "TwoTone (androidx.compose.material.icons.Icons)"
+ )
+ .inOrder()
}
@Test
@@ -273,7 +287,8 @@
@Composable
fun WrappingFunction(foobarArg: Int) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
// Given:
@@ -289,18 +304,21 @@
fun HomeScreen() {
WrappingFunction(foobar<caret>)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
// When:
myFixture.completeBasic()
// Then:
- Truth.assertThat(myFixture.renderedLookupElements).containsExactly(
- "foobarArg = Int",
- "foobarOne(required: Int) (com.example) Int",
- "foobarTwo(required: Int, optional: Int = ...) (com.example) Int",
- ).inOrder()
+ Truth.assertThat(myFixture.renderedLookupElements)
+ .containsExactly(
+ "foobarArg = Int",
+ "foobarOne(required: Int) (com.example) Int",
+ "foobarTwo(required: Int, optional: Int = ...) (com.example) Int",
+ )
+ .inOrder()
}
private val CodeInsightTestFixture.renderedLookupElements: Collection<String>
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeMaterialIconServiceTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeMaterialIconServiceTest.kt
index 92a57b1..8cffc6d 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeMaterialIconServiceTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeMaterialIconServiceTest.kt
@@ -22,13 +22,13 @@
import com.android.tools.idea.MaterialVdIconsProvider
import com.android.tools.idea.material.icons.MaterialVdIcons
import com.google.common.truth.Truth.assertThat
+import java.awt.image.BufferedImage
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.invocation.InvocationOnMock
-import java.awt.image.BufferedImage
class ComposeMaterialIconServiceTest {
@@ -43,7 +43,8 @@
}
private fun storeCallback(invocation: InvocationOnMock) {
- this.callback = invocation.arguments[0] as (MaterialVdIcons, MaterialVdIconsProvider.Status) -> Unit
+ this.callback =
+ invocation.arguments[0] as (MaterialVdIcons, MaterialVdIconsProvider.Status) -> Unit
}
@Test
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeModifierCompletionContributorTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeModifierCompletionContributorTest.kt
index 0011b57..3ea38ea 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeModifierCompletionContributorTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposeModifierCompletionContributorTest.kt
@@ -45,13 +45,10 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-/**
- * Test for [ComposeModifierCompletionContributor].
- */
+/** Test for [ComposeModifierCompletionContributor]. */
@RunWith(JUnit4::class)
class ComposeModifierCompletionContributorTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory().onEdt()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory().onEdt()
private val myFixture: CodeInsightTestFixture by lazy { projectRule.fixture }
@@ -74,7 +71,8 @@
fun Modifier.extensionFunction():Modifier { return this }
fun Modifier.extensionFunctionReturnsNonModifier():Int { return 1 }
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.addFileToProject(
@@ -87,7 +85,9 @@
@Composable
fun myWidgetWithModifier(modifier: Modifier) {}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
@@ -104,14 +104,19 @@
fun HomeScreen() {
val m = Modifier.$caret
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
var lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.indexOf("extensionFunction")).isLessThan(lookupStrings.indexOf("function"))
- assertThat(lookupStrings.indexOf("extensionFunction")).isLessThan(lookupStrings.indexOf("extensionFunctionReturnsNonModifier"))
- assertThat(lookupStrings.indexOf("extensionFunctionReturnsNonModifier")).isLessThan(lookupStrings.indexOf("function"))
+ assertThat(lookupStrings.indexOf("extensionFunction"))
+ .isLessThan(lookupStrings.indexOf("function"))
+ assertThat(lookupStrings.indexOf("extensionFunction"))
+ .isLessThan(lookupStrings.indexOf("extensionFunctionReturnsNonModifier"))
+ assertThat(lookupStrings.indexOf("extensionFunctionReturnsNonModifier"))
+ .isLessThan(lookupStrings.indexOf("function"))
myFixture.loadNewFile(
"src/com/example/Test2.kt",
@@ -126,12 +131,15 @@
fun HomeScreen2() {
val m = Modifier.extensionFunction().$caret
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.indexOf("extensionFunction")).isLessThan(lookupStrings.indexOf("function"))
+ assertThat(lookupStrings.indexOf("extensionFunction"))
+ .isLessThan(lookupStrings.indexOf("function"))
myFixture.loadNewFile(
"src/com/example/Test3.kt",
@@ -145,12 +153,15 @@
fun HomeScreen3(modifier: Modifier = Modifier) {
modifier.$caret
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.indexOf("extensionFunction")).isLessThan(lookupStrings.indexOf("function"))
+ assertThat(lookupStrings.indexOf("extensionFunction"))
+ .isLessThan(lookupStrings.indexOf("function"))
myFixture.loadNewFile(
"src/com/example/Test4.kt",
@@ -164,19 +175,23 @@
fun HomeScreen4() {
Modifier.<caret>
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.indexOf("extensionFunction")).isLessThan(lookupStrings.indexOf("function"))
+ assertThat(lookupStrings.indexOf("extensionFunction"))
+ .isLessThan(lookupStrings.indexOf("function"))
}
@RunsInEdt
@Test
fun modifierAsArgument() {
fun checkArgumentCompletion() {
- myFixture.lookup.currentItem = myFixture.lookupElements.find { it.lookupString.contains("extensionFunction") }
+ myFixture.lookup.currentItem =
+ myFixture.lookupElements.find { it.lookupString.contains("extensionFunction") }
myFixture.finishLookup('\n')
myFixture.checkResult(
"""
@@ -190,12 +205,14 @@
fun myWidget() {
myWidgetWithModifier(Modifier.extensionFunction()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
fun checkNamedArgumentCompletion() {
- myFixture.lookup.currentItem = myFixture.lookupElements.find { it.lookupString.contains("extensionFunction") }
+ myFixture.lookup.currentItem =
+ myFixture.lookupElements.find { it.lookupString.contains("extensionFunction") }
myFixture.finishLookup('\n')
myFixture.checkResult(
"""
@@ -209,7 +226,8 @@
fun myWidget() {
myWidgetWithModifier(modifier = Modifier.extensionFunction()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -225,7 +243,8 @@
fun myWidget() {
myWidgetWithModifier(Modifier.<caret>
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.completeBasic()
@@ -248,7 +267,8 @@
fun myWidget() {
myWidgetWithModifier(<caret>
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.completeBasic()
@@ -258,7 +278,8 @@
assertThat(lookupStrings).doesNotContain("Modifier.extensionFunctionReturnsNonModifier")
assertThat(lookupStrings.indexOf("Modifier.extensionFunction")).isEqualTo(0)
- // to check that we still suggest "Modifier.extensionFunction" when prefix doesn't much with function name and only with "Modifier".
+ // to check that we still suggest "Modifier.extensionFunction" when prefix doesn't much with
+ // function name and only with "Modifier".
// See [ComposeModifierCompletionContributor.ModifierLookupElement.getAllLookupStrings]
myFixture.type("M")
@@ -275,7 +296,8 @@
fun myWidget() {
myWidgetWithModifier(modifier = <caret>
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.completeBasic()
@@ -298,7 +320,8 @@
fun myWidget() {
myWidgetWithModifier(modifier = Modifier.<caret>
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.completeBasic()
@@ -326,7 +349,8 @@
fun myWidget() {
val myModifier:Modifier = <caret>
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.completeBasic()
@@ -350,7 +374,8 @@
fun myWidget() {
val myModifier:Modifier = Modifier.extensionFunction()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.loadNewFile(
@@ -365,7 +390,8 @@
fun myWidget() {
val myModifier:Modifier = Modifier.<caret>
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.completeBasic()
@@ -373,7 +399,6 @@
assertThat(lookupStrings).contains("extensionFunction")
assertThat(lookupStrings).contains("extensionFunctionReturnsNonModifier")
-
myFixture.loadNewFile(
"src/com/example/Test.kt",
"""
@@ -387,7 +412,8 @@
fun myWidget() {
val myModifier:Modifier = Modifier.extensionFunction().<caret>
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.completeBasic()
@@ -411,7 +437,8 @@
fun myWidget() {
myWidgetWithModifier(modifier = Modifier.<caret>
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.completeBasic()
@@ -420,7 +447,8 @@
assertThat(lookupStrings).contains("extensionFunctionReturnsNonModifier")
assertThat(lookupStrings.indexOf("extensionFunction")).isEqualTo(0)
- myFixture.lookup.currentItem = myFixture.lookupElements.find { it.lookupString == "extensionFunction" }
+ myFixture.lookup.currentItem =
+ myFixture.lookupElements.find { it.lookupString == "extensionFunction" }
myFixture.finishLookup('\n')
myFixture.checkResult(
@@ -435,7 +463,8 @@
fun myWidget() {
myWidgetWithModifier(modifier = Modifier.extensionFunction()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -452,7 +481,8 @@
fun Modifier.foo() = extensionFunction().<caret>
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.completeBasic()
@@ -468,14 +498,16 @@
val contributor = ComposeModifierCompletionContributor()
val mockResultSet: CompletionResultSet = mock()
- // This is a super-contrived example to try to regression test b/279049842. I haven't been able to reproduce the real scenario, which
- // involves internal items coming from other libs/modules and relies on Kotlin plugin bugs which (hopefully) would get fixed at some
+ // This is a super-contrived example to try to regression test b/279049842. I haven't been able
+ // to reproduce the real scenario, which
+ // involves internal items coming from other libs/modules and relies on Kotlin plugin bugs which
+ // (hopefully) would get fixed at some
// some point.
ApplicationManager.getApplication().invokeAndWait {
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -500,23 +532,37 @@
fun doSomething() {
functionNeedingModifier(modifier = MyModifier.)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
runReadAction {
- val functionCompletionCall: KtCallExpression = myFixture.findParentElement("functionNeeding|Modifier(modifier = MyModifier")
+ val functionCompletionCall: KtCallExpression =
+ myFixture.findParentElement("functionNeeding|Modifier(modifier = MyModifier")
- val visibleChildFunctionCompletion = mockCompletionResult(
- myFixture.findParentElement<KtFunction>("Modifier.visibleChild|Function():"))
+ val visibleChildFunctionCompletion =
+ mockCompletionResult(
+ myFixture.findParentElement<KtFunction>("Modifier.visibleChild|Function():")
+ )
contributor.consumerCompletionResultFromRemainingContributor(
- visibleChildFunctionCompletion, emptySet(), functionCompletionCall, mockResultSet)
+ visibleChildFunctionCompletion,
+ emptySet(),
+ functionCompletionCall,
+ mockResultSet
+ )
verify(mockResultSet).passResult(visibleChildFunctionCompletion)
- val notVisibleChildFunctionCompletion = mockCompletionResult(
- myFixture.findParentElement<KtFunction>("Modifier.notVisibleChild|Function():"))
+ val notVisibleChildFunctionCompletion =
+ mockCompletionResult(
+ myFixture.findParentElement<KtFunction>("Modifier.notVisibleChild|Function():")
+ )
contributor.consumerCompletionResultFromRemainingContributor(
- notVisibleChildFunctionCompletion, emptySet(), functionCompletionCall, mockResultSet)
+ notVisibleChildFunctionCompletion,
+ emptySet(),
+ functionCompletionCall,
+ mockResultSet
+ )
verify(mockResultSet, never()).passResult(notVisibleChildFunctionCompletion)
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposePositioningCompletionContributorTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposePositioningCompletionContributorTest.kt
index b4a93ef..020285a 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposePositioningCompletionContributorTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/ComposePositioningCompletionContributorTest.kt
@@ -30,13 +30,10 @@
import org.junit.Rule
import org.junit.Test
-/**
- * Test for [ComposePositioningCompletionContributor].
- */
+/** Test for [ComposePositioningCompletionContributor]. */
class ComposePositioningCompletionContributorTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory().onEdt()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory().onEdt()
private val myFixture: CodeInsightTestFixture by lazy { projectRule.fixture }
@@ -47,7 +44,7 @@
myFixture.addFileToProject(
"/src/androidx/compose/ui/Alignment.kt",
- //language=kotlin
+ // language=kotlin
"""
package androidx.compose.ui
@@ -92,12 +89,13 @@
val Left: Alignment.Horizontal = object : Alignment.Horizontal {}
val Right: Alignment.Horizontal = object : Alignment.Horizontal {}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.addFileToProject(
"/src/androidx/compose/foundation/layout/Arrangement.kt",
- //language=kotlin
+ // language=kotlin
"""
package androidx.compose.foundation.layout
@@ -124,12 +122,13 @@
val SpaceAround = object : Horizontal {}
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.addFileToProject(
"/src/com/example/Rows.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -152,7 +151,9 @@
horizontalOrVerticalArrangement: Arrangement.HorizontalOrVertical = Arrangement.Center,
content: @Composable () -> Unit
) {}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@RunsInEdt
@@ -161,7 +162,7 @@
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -171,23 +172,29 @@
fun HomeScreen() {
RowWithAlignment(<caret>)
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
- // Ordering: all Horizontal entries on `Alignment` should come first, followed by those on `AbsoluteAlignment`.
+ // Ordering: all Horizontal entries on `Alignment` should come first, followed by those on
+ // `AbsoluteAlignment`.
val lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.subList(0, 3)).containsExactly(
- "Alignment.Start",
- "Alignment.CenterHorizontally",
- "Alignment.End",
- )
- assertThat(lookupStrings.subList(3, 5)).containsExactly(
- "AbsoluteAlignment.Left",
- "AbsoluteAlignment.Right",
- )
+ assertThat(lookupStrings.subList(0, 3))
+ .containsExactly(
+ "Alignment.Start",
+ "Alignment.CenterHorizontally",
+ "Alignment.End",
+ )
+ assertThat(lookupStrings.subList(3, 5))
+ .containsExactly(
+ "AbsoluteAlignment.Left",
+ "AbsoluteAlignment.Right",
+ )
- val alignmentStartLookupItem = myFixture.lookupElements?.find { it.lookupString == "Alignment.Start" }!!
+ val alignmentStartLookupItem =
+ myFixture.lookupElements?.find { it.lookupString == "Alignment.Start" }!!
val presentation = LookupElementPresentation()
alignmentStartLookupItem.renderElement(presentation)
@@ -207,7 +214,8 @@
fun HomeScreen() {
RowWithAlignment(Alignment.Start)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -217,7 +225,7 @@
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -227,23 +235,29 @@
fun HomeScreen() {
RowWithAlignment(<caret>)
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
- // Ordering: all Horizontal entries on `Alignment` should come first, followed by those on `AbsoluteAlignment`.
+ // Ordering: all Horizontal entries on `Alignment` should come first, followed by those on
+ // `AbsoluteAlignment`.
val lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.subList(0, 3)).containsExactly(
- "Alignment.Start",
- "Alignment.CenterHorizontally",
- "Alignment.End",
- )
- assertThat(lookupStrings.subList(3, 5)).containsExactly(
- "AbsoluteAlignment.Left",
- "AbsoluteAlignment.Right",
- )
+ assertThat(lookupStrings.subList(0, 3))
+ .containsExactly(
+ "Alignment.Start",
+ "Alignment.CenterHorizontally",
+ "Alignment.End",
+ )
+ assertThat(lookupStrings.subList(3, 5))
+ .containsExactly(
+ "AbsoluteAlignment.Left",
+ "AbsoluteAlignment.Right",
+ )
- val alignmentStartLookupItem = myFixture.lookupElements?.find { it.lookupString == "AbsoluteAlignment.Left" }!!
+ val alignmentStartLookupItem =
+ myFixture.lookupElements?.find { it.lookupString == "AbsoluteAlignment.Left" }!!
val presentation = LookupElementPresentation()
alignmentStartLookupItem.renderElement(presentation)
@@ -263,7 +277,8 @@
fun HomeScreen() {
RowWithAlignment(AbsoluteAlignment.Left)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -273,7 +288,7 @@
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -283,16 +298,20 @@
fun HomeScreen() {
RowWithAlignment(Alignment.<caret>)
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
- // Ordering: all Horizontal entries on `Alignment` should come first. No entries from `AbsoluteAlignment` should be present.
+ // Ordering: all Horizontal entries on `Alignment` should come first. No entries from
+ // `AbsoluteAlignment` should be present.
val lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.subList(0, 3)).containsExactly(
- "Start",
- "CenterHorizontally",
- "End",
+ assertThat(lookupStrings.subList(0, 3))
+ .containsExactly(
+ "Start",
+ "CenterHorizontally",
+ "End",
)
assertThat(lookupStrings).doesNotContain("AbsoluteAlignment.Left")
@@ -320,7 +339,8 @@
fun HomeScreen() {
RowWithAlignment(Alignment.Start)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -329,7 +349,7 @@
fun verticalAlignmentCompletion() {
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -339,18 +359,23 @@
fun HomeScreen() {
RowWithAlignment(verticalAlignment = <caret>)
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
- // Ordering: all Vertical alignments on `Alignment` should come at the top. There are no Vertical entries on `AbsoluteAlignment`.
+ // Ordering: all Vertical alignments on `Alignment` should come at the top. There are no
+ // Vertical entries on `AbsoluteAlignment`.
val lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.subList(0, 3)).containsExactly(
- "Alignment.Top",
- "Alignment.CenterVertically",
- "Alignment.Bottom",
+ assertThat(lookupStrings.subList(0, 3))
+ .containsExactly(
+ "Alignment.Top",
+ "Alignment.CenterVertically",
+ "Alignment.Bottom",
)
- val centerVerticallyLookupElement = myFixture.lookupElements?.find { it.lookupString == "Alignment.CenterVertically" }!!
+ val centerVerticallyLookupElement =
+ myFixture.lookupElements?.find { it.lookupString == "Alignment.CenterVertically" }!!
val presentation = LookupElementPresentation()
centerVerticallyLookupElement.renderElement(presentation)
@@ -370,7 +395,8 @@
fun HomeScreen() {
RowWithAlignment(verticalAlignment = Alignment.CenterVertically)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -379,7 +405,7 @@
fun twoDimensionalAlignmentCompletion() {
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -389,32 +415,38 @@
fun HomeScreen() {
RowWithAlignment(twoDimensionalAlignment = <caret>)
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
- // Ordering: all 2D entries on `Alignment` should come first, followed by those on `AbsoluteAlignment`.
+ // Ordering: all 2D entries on `Alignment` should come first, followed by those on
+ // `AbsoluteAlignment`.
val lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.subList(0, 9)).containsExactly(
- "Alignment.TopStart",
- "Alignment.TopCenter",
- "Alignment.TopEnd",
- "Alignment.CenterStart",
- "Alignment.Center",
- "Alignment.CenterEnd",
- "Alignment.BottomStart",
- "Alignment.BottomCenter",
- "Alignment.BottomEnd",
- )
- assertThat(lookupStrings.subList(9, 15)).containsExactly(
- "AbsoluteAlignment.TopLeft",
- "AbsoluteAlignment.TopRight",
- "AbsoluteAlignment.CenterLeft",
- "AbsoluteAlignment.CenterRight",
- "AbsoluteAlignment.BottomLeft",
- "AbsoluteAlignment.BottomRight",
- )
+ assertThat(lookupStrings.subList(0, 9))
+ .containsExactly(
+ "Alignment.TopStart",
+ "Alignment.TopCenter",
+ "Alignment.TopEnd",
+ "Alignment.CenterStart",
+ "Alignment.Center",
+ "Alignment.CenterEnd",
+ "Alignment.BottomStart",
+ "Alignment.BottomCenter",
+ "Alignment.BottomEnd",
+ )
+ assertThat(lookupStrings.subList(9, 15))
+ .containsExactly(
+ "AbsoluteAlignment.TopLeft",
+ "AbsoluteAlignment.TopRight",
+ "AbsoluteAlignment.CenterLeft",
+ "AbsoluteAlignment.CenterRight",
+ "AbsoluteAlignment.BottomLeft",
+ "AbsoluteAlignment.BottomRight",
+ )
- val centerVerticallyLookupElement = myFixture.lookupElements?.find { it.lookupString == "Alignment.CenterStart" }!!
+ val centerVerticallyLookupElement =
+ myFixture.lookupElements?.find { it.lookupString == "Alignment.CenterStart" }!!
val presentation = LookupElementPresentation()
centerVerticallyLookupElement.renderElement(presentation)
@@ -434,7 +466,8 @@
fun HomeScreen() {
RowWithAlignment(twoDimensionalAlignment = Alignment.CenterStart)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -443,7 +476,7 @@
fun horizontalArrangementCompletion() {
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -453,30 +486,36 @@
fun HomeScreen() {
RowWithArrangement(<caret>)
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
- // Ordering: all Horizontal entries on `Arrangement` should come first, followed by those on `Arrangement.Absolute`.
+ // Ordering: all Horizontal entries on `Arrangement` should come first, followed by those on
+ // `Arrangement.Absolute`.
val lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.subList(0, 6)).containsExactly(
- "Arrangement.Start",
- "Arrangement.End",
- "Arrangement.Center",
- "Arrangement.SpaceEvenly",
- "Arrangement.SpaceBetween",
- "Arrangement.SpaceAround",
- )
- assertThat(lookupStrings.subList(6, 12)).containsExactly(
- "Arrangement.Absolute.Left",
- "Arrangement.Absolute.Center",
- "Arrangement.Absolute.Right",
- "Arrangement.Absolute.SpaceBetween",
- "Arrangement.Absolute.SpaceEvenly",
- "Arrangement.Absolute.SpaceAround",
- )
+ assertThat(lookupStrings.subList(0, 6))
+ .containsExactly(
+ "Arrangement.Start",
+ "Arrangement.End",
+ "Arrangement.Center",
+ "Arrangement.SpaceEvenly",
+ "Arrangement.SpaceBetween",
+ "Arrangement.SpaceAround",
+ )
+ assertThat(lookupStrings.subList(6, 12))
+ .containsExactly(
+ "Arrangement.Absolute.Left",
+ "Arrangement.Absolute.Center",
+ "Arrangement.Absolute.Right",
+ "Arrangement.Absolute.SpaceBetween",
+ "Arrangement.Absolute.SpaceEvenly",
+ "Arrangement.Absolute.SpaceAround",
+ )
- val startLookupElement = myFixture.lookupElements?.find { it.lookupString == "Arrangement.Center" }!!
+ val startLookupElement =
+ myFixture.lookupElements?.find { it.lookupString == "Arrangement.Center" }!!
val presentation = LookupElementPresentation()
startLookupElement.renderElement(presentation)
@@ -496,7 +535,8 @@
fun HomeScreen() {
RowWithArrangement(Arrangement.Center)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -505,7 +545,7 @@
fun horizontalArrangementCompletion_choosesAbsoluteArrangement() {
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -515,30 +555,36 @@
fun HomeScreen() {
RowWithArrangement(<caret>)
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
- // Ordering: all Horizontal entries on `Arrangement` should come first, followed by those on `Arrangement.Absolute`.
+ // Ordering: all Horizontal entries on `Arrangement` should come first, followed by those on
+ // `Arrangement.Absolute`.
val lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.subList(0, 6)).containsExactly(
- "Arrangement.Start",
- "Arrangement.End",
- "Arrangement.Center",
- "Arrangement.SpaceEvenly",
- "Arrangement.SpaceBetween",
- "Arrangement.SpaceAround",
- )
- assertThat(lookupStrings.subList(6, 12)).containsExactly(
- "Arrangement.Absolute.Left",
- "Arrangement.Absolute.Center",
- "Arrangement.Absolute.Right",
- "Arrangement.Absolute.SpaceBetween",
- "Arrangement.Absolute.SpaceEvenly",
- "Arrangement.Absolute.SpaceAround",
- )
+ assertThat(lookupStrings.subList(0, 6))
+ .containsExactly(
+ "Arrangement.Start",
+ "Arrangement.End",
+ "Arrangement.Center",
+ "Arrangement.SpaceEvenly",
+ "Arrangement.SpaceBetween",
+ "Arrangement.SpaceAround",
+ )
+ assertThat(lookupStrings.subList(6, 12))
+ .containsExactly(
+ "Arrangement.Absolute.Left",
+ "Arrangement.Absolute.Center",
+ "Arrangement.Absolute.Right",
+ "Arrangement.Absolute.SpaceBetween",
+ "Arrangement.Absolute.SpaceEvenly",
+ "Arrangement.Absolute.SpaceAround",
+ )
- val startLookupElement = myFixture.lookupElements?.find { it.lookupString == "Arrangement.Absolute.Center" }!!
+ val startLookupElement =
+ myFixture.lookupElements?.find { it.lookupString == "Arrangement.Absolute.Center" }!!
val presentation = LookupElementPresentation()
startLookupElement.renderElement(presentation)
@@ -558,7 +604,8 @@
fun HomeScreen() {
RowWithArrangement(Arrangement.Absolute.Center)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -567,7 +614,7 @@
fun horizontalArrangementCompletion_arrangementAlreadyCompleted() {
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -577,30 +624,36 @@
fun HomeScreen() {
RowWithArrangement(Arrangement.<caret>)
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
- // Ordering: all Horizontal entries on `Arrangement` should come first, followed by those on `Arrangement.Absolute`.
+ // Ordering: all Horizontal entries on `Arrangement` should come first, followed by those on
+ // `Arrangement.Absolute`.
val lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.subList(0, 6)).containsExactly(
- "Start",
- "End",
- "Center",
- "SpaceEvenly",
- "SpaceBetween",
- "SpaceAround",
- )
- assertThat(lookupStrings.subList(6, 12)).containsExactly(
- "Absolute.Left",
- "Absolute.Center",
- "Absolute.Right",
- "Absolute.SpaceBetween",
- "Absolute.SpaceEvenly",
- "Absolute.SpaceAround",
+ assertThat(lookupStrings.subList(0, 6))
+ .containsExactly(
+ "Start",
+ "End",
+ "Center",
+ "SpaceEvenly",
+ "SpaceBetween",
+ "SpaceAround",
+ )
+ assertThat(lookupStrings.subList(6, 12))
+ .containsExactly(
+ "Absolute.Left",
+ "Absolute.Center",
+ "Absolute.Right",
+ "Absolute.SpaceBetween",
+ "Absolute.SpaceEvenly",
+ "Absolute.SpaceAround",
)
- val startLookupElement = myFixture.lookupElements?.find { it.lookupString == "Absolute.Center" }!!
+ val startLookupElement =
+ myFixture.lookupElements?.find { it.lookupString == "Absolute.Center" }!!
val presentation = LookupElementPresentation()
startLookupElement.renderElement(presentation)
@@ -620,7 +673,8 @@
fun HomeScreen() {
RowWithArrangement(Arrangement.Absolute.Center)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -629,7 +683,7 @@
fun horizontalArrangementCompletion_arrangementAbsoluteAlreadyCompleted() {
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -639,19 +693,17 @@
fun HomeScreen() {
RowWithArrangement(Arrangement.Absolute.<caret>)
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
- // Ordering: all Horizontal entries on `Arrangement` should come first. No entries from `Arrangement.Absolute` should be present.
+ // Ordering: all Horizontal entries on `Arrangement` should come first. No entries from
+ // `Arrangement.Absolute` should be present.
val lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.subList(0, 6)).containsExactly(
- "Left",
- "Center",
- "Right",
- "SpaceBetween",
- "SpaceEvenly",
- "SpaceAround")
+ assertThat(lookupStrings.subList(0, 6))
+ .containsExactly("Left", "Center", "Right", "SpaceBetween", "SpaceEvenly", "SpaceAround")
assertThat(lookupStrings).doesNotContain("Start")
assertThat(lookupStrings).doesNotContain("End")
@@ -676,7 +728,8 @@
fun HomeScreen() {
RowWithArrangement(Arrangement.Absolute.Center)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -685,7 +738,7 @@
fun verticalArrangementCompletion() {
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -695,19 +748,23 @@
fun HomeScreen() {
RowWithArrangement(verticalArrangement = <caret>)
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
- // Ordering: all Vertical entries on `Arrangement` should come at the top. There are no Vertical entries on `Arrangement.Absolute`.
+ // Ordering: all Vertical entries on `Arrangement` should come at the top. There are no Vertical
+ // entries on `Arrangement.Absolute`.
val lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.subList(0, 6)).containsExactly(
- "Arrangement.Top",
- "Arrangement.Bottom",
- "Arrangement.Center",
- "Arrangement.SpaceEvenly",
- "Arrangement.SpaceBetween",
- "Arrangement.SpaceAround",
- )
+ assertThat(lookupStrings.subList(0, 6))
+ .containsExactly(
+ "Arrangement.Top",
+ "Arrangement.Bottom",
+ "Arrangement.Center",
+ "Arrangement.SpaceEvenly",
+ "Arrangement.SpaceBetween",
+ "Arrangement.SpaceAround",
+ )
val topLookupElement = myFixture.lookupElements?.find { it.lookupString == "Arrangement.Top" }!!
@@ -729,7 +786,8 @@
fun HomeScreen() {
RowWithArrangement(verticalArrangement = Arrangement.Top)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -738,7 +796,7 @@
fun horizontalOrVerticalArrangementCompletion() {
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -748,20 +806,25 @@
fun HomeScreen() {
RowWithArrangement(horizontalOrVerticalArrangement = <caret>)
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.completeBasic()
- // Ordering: all HorizontalOrVertical entries on `Arrangement` should come at the top. There are no HorizontalOrVertical entries on
+ // Ordering: all HorizontalOrVertical entries on `Arrangement` should come at the top. There are
+ // no HorizontalOrVertical entries on
// `Arrangement.Absolute`.
val lookupStrings = myFixture.lookupElementStrings!!
- assertThat(lookupStrings.subList(0, 4)).containsExactly(
- "Arrangement.Center",
- "Arrangement.SpaceEvenly",
- "Arrangement.SpaceBetween",
- "Arrangement.SpaceAround",
- )
+ assertThat(lookupStrings.subList(0, 4))
+ .containsExactly(
+ "Arrangement.Center",
+ "Arrangement.SpaceEvenly",
+ "Arrangement.SpaceBetween",
+ "Arrangement.SpaceAround",
+ )
- val topLookupElement = myFixture.lookupElements?.find { it.lookupString == "Arrangement.SpaceEvenly" }!!
+ val topLookupElement =
+ myFixture.lookupElements?.find { it.lookupString == "Arrangement.SpaceEvenly" }!!
val presentation = LookupElementPresentation()
topLookupElement.renderElement(presentation)
@@ -781,7 +844,8 @@
fun HomeScreen() {
RowWithArrangement(horizontalOrVerticalArrangement = Arrangement.SpaceEvenly)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -790,7 +854,7 @@
fun duplicateLookupEntriesFromOtherContributorsHandled() {
myFixture.loadNewFile(
"src/com/example/Test.kt",
- //language=kotlin
+ // language=kotlin
"""
package com.example
@@ -803,11 +867,14 @@
RowWithArrangement(horizontalArrangement = Arrangement.A)
RowWithArrangement(horizontalArrangement = Arrangement.Absolute.L)
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.moveCaret("(horizontalArrangement = A|)")
myFixture.completeBasic()
- assertThat(myFixture.lookupElementStrings!!.filter { it.startsWith("Arrangement") }).containsNoDuplicates()
+ assertThat(myFixture.lookupElementStrings!!.filter { it.startsWith("Arrangement") })
+ .containsNoDuplicates()
myFixture.moveCaret("(horizontalArrangement = Arrangement.A|)")
myFixture.completeBasic()
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/constraintlayout/ConstraintLayoutJsonCompletionContributorTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/constraintlayout/ConstraintLayoutJsonCompletionContributorTest.kt
index 6cf3ae9..e822340 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/constraintlayout/ConstraintLayoutJsonCompletionContributorTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/code/completion/constraintlayout/ConstraintLayoutJsonCompletionContributorTest.kt
@@ -28,10 +28,10 @@
import org.junit.Test
internal class ConstraintLayoutJsonCompletionContributorTest {
- // TODO(b/207030860): Change test class to 'LightPlatformCodeInsightFixture4TestCase' once/if we remove the Compose requirement
+ // TODO(b/207030860): Change test class to 'LightPlatformCodeInsightFixture4TestCase' once/if we
+ // remove the Compose requirement
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory()
private val myFixture: CodeInsightTestFixture by lazy { projectRule.fixture }
@@ -42,7 +42,8 @@
@Test
fun completeConstraintSetFields() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
start: {
@@ -56,7 +57,9 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
val items = myFixture.lookupElementStrings!!
assertThat(items).hasSize(2)
assertThat((items[0])).isEqualTo("id2")
@@ -65,7 +68,8 @@
@Test
fun completeExtendsValue() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
start: {
@@ -76,14 +80,17 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
assertThat(myFixture.lookupElementStrings!!).hasSize(1)
assertThat(myFixture.lookupElementStrings!![0]).isEqualTo("start")
}
@Test
fun completeConstraintBlockFields() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
start: {
@@ -94,7 +101,9 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
val lookupElements = myFixture.lookupElementStrings!!
assertThat(lookupElements).hasSize(19)
assertThat(lookupElements).containsNoDuplicates()
@@ -106,7 +115,8 @@
@Test
fun completeDimensionBehaviors() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
start: {
@@ -116,7 +126,9 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
val lookupElements = myFixture.lookupElementStrings!!
assertThat(lookupElements).hasSize(4)
@@ -125,7 +137,8 @@
@Test
fun completeVisibilityModes() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
start: {
@@ -135,7 +148,9 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
val lookupElements = myFixture.lookupElementStrings!!
assertThat(lookupElements).hasSize(3)
@@ -144,7 +159,8 @@
@Test
fun completeConstraintIdsInArray() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
start: {
@@ -156,7 +172,9 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
val lookupElements = myFixture.lookupElementStrings!!
assertThat(lookupElements).hasSize(3)
@@ -165,7 +183,8 @@
@Test
fun completeConstraintIdsInSpecialAnchors() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
start: {
@@ -177,7 +196,9 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
val lookupElements = myFixture.lookupElementStrings!!
assertThat(lookupElements).hasSize(3)
@@ -186,7 +207,8 @@
@Test
fun completeAnchorsInConstraintArray() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
start: {
@@ -196,12 +218,15 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
val lookupElements1 = myFixture.lookupElementStrings!!
assertThat(lookupElements1).hasSize(4)
assertThat(lookupElements1).containsExactly("end", "left", "right", "start")
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
start: {
@@ -211,7 +236,9 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
val lookupElements2 = myFixture.lookupElementStrings!!
assertThat(lookupElements2).hasSize(3)
assertThat(lookupElements2).containsExactly("top", "bottom", "baseline")
@@ -219,7 +246,8 @@
@Test
fun constraintAnchorHandlerResult() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
start: {
@@ -229,7 +257,9 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.checkResult(
// language=JSON5
"""
@@ -242,13 +272,15 @@
}
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@Test
fun completionHandlerResult() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
start: {
@@ -259,9 +291,11 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.checkResult(
- //language=JSON5
+ // language=JSON5
"""{
ConstraintSets: {
start: {
@@ -273,9 +307,11 @@
}
}
}
-}""")
+}"""
+ )
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
start: {
@@ -283,7 +319,9 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
myFixture.checkResult(
// language=JSON5
"""{
@@ -292,12 +330,14 @@
Extends: '$caret',
}
}
-}""")
+}"""
+ )
}
@Test
fun completeTransitionFields() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -306,7 +346,9 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
val lookupElements = myFixture.lookupElementStrings!!
assertThat(lookupElements).containsExactly("to", "KeyFrames", "pathMotionArc", "onSwipe")
@@ -314,7 +356,8 @@
@Test
fun completeTransitionFromAndTo() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
a: {},
@@ -329,10 +372,13 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
assertThat(myFixture.lookupElementStrings!!).containsExactly("a", "b", "c", "d")
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
e: {},
@@ -347,13 +393,16 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
assertThat(myFixture.lookupElementStrings!!).containsExactly("e", "f", "g", "h")
}
@Test
fun completeClearField() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
a: {},
@@ -365,14 +414,17 @@
},
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// The repeated clear is to autocomplete with all options populated
assertThat(myFixture.lookupElementStrings!!).containsExactly("clear", "clear")
}
@Test
fun completeClearOptions() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
a: {},
@@ -384,10 +436,14 @@
},
}
}
- """.trimIndent())
- assertThat(myFixture.lookupElementStrings!!).containsExactly("constraints", "dimensions", "transforms")
+ """
+ .trimIndent()
+ )
+ assertThat(myFixture.lookupElementStrings!!)
+ .containsExactly("constraints", "dimensions", "transforms")
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
a: {},
@@ -399,14 +455,17 @@
},
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// 'constraints' options is already populated
assertThat(myFixture.lookupElementStrings!!).containsExactly("dimensions", "transforms")
}
@Test
fun completeOnSwipeFieldsAndValues() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -417,10 +476,13 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
assertThat(myFixture.lookupElementStrings!!).containsExactly("side", "direction", "mode")
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -430,8 +492,11 @@
}
}
}
- """.trimIndent())
- myFixture.checkResult("""
+ """
+ .trimIndent()
+ )
+ myFixture.checkResult(
+ """
{
Transitions: {
default: {
@@ -441,9 +506,12 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -453,8 +521,11 @@
}
}
}
- """.trimIndent())
- myFixture.checkResult("""
+ """
+ .trimIndent()
+ )
+ myFixture.checkResult(
+ """
{
Transitions: {
default: {
@@ -464,9 +535,12 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -476,8 +550,11 @@
}
}
}
- """.trimIndent())
- myFixture.checkResult("""
+ """
+ .trimIndent()
+ )
+ myFixture.checkResult(
+ """
{
Transitions: {
default: {
@@ -487,9 +564,12 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
ConstraintSets: {
start: {
@@ -511,13 +591,16 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
assertThat(myFixture.lookupElementStrings!!).containsExactly("parent", "a", "b", "c", "d", "e")
}
@Test
fun completeKeyFramesFields() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -527,13 +610,17 @@
}
}
}
- """.trimIndent())
- assertThat(myFixture.lookupElementStrings!!).containsExactly("KeyAttributes", "KeyPositions", "KeyCycles")
+ """
+ .trimIndent()
+ )
+ assertThat(myFixture.lookupElementStrings!!)
+ .containsExactly("KeyAttributes", "KeyPositions", "KeyCycles")
}
@Test
fun completeKeyAttributesFields() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -545,28 +632,32 @@
}
}
}
- """.trimIndent())
- assertThat(myFixture.lookupElementStrings!!).containsExactly(
- "target",
- "frames",
- "transitionEasing",
- "curveFit",
- // Attributes specific:
- "alpha",
- "scaleX",
- "scaleY",
- "rotationX",
- "rotationY",
- "rotationZ",
- "translationX",
- "translationY",
- "translationZ",
+ """
+ .trimIndent()
)
+ assertThat(myFixture.lookupElementStrings!!)
+ .containsExactly(
+ "target",
+ "frames",
+ "transitionEasing",
+ "curveFit",
+ // Attributes specific:
+ "alpha",
+ "scaleX",
+ "scaleY",
+ "rotationX",
+ "rotationY",
+ "rotationZ",
+ "translationX",
+ "translationY",
+ "translationZ",
+ )
}
@Test
fun completeKeyPositionsFields() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -578,25 +669,29 @@
}
}
}
- """.trimIndent())
- assertThat(myFixture.lookupElementStrings!!).containsExactly(
- "target",
- "frames",
- "transitionEasing",
- "curveFit",
- // Position specific:
- "percentX",
- "percentY",
- "percentWidth",
- "percentHeight",
- "pathMotionArc",
- "type"
+ """
+ .trimIndent()
)
+ assertThat(myFixture.lookupElementStrings!!)
+ .containsExactly(
+ "target",
+ "frames",
+ "transitionEasing",
+ "curveFit",
+ // Position specific:
+ "percentX",
+ "percentY",
+ "percentWidth",
+ "percentHeight",
+ "pathMotionArc",
+ "type"
+ )
}
@Test
fun completeKeyCyclesFields() {
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -608,33 +703,37 @@
}
}
}
- """.trimIndent())
- assertThat(myFixture.lookupElementStrings!!).containsExactly(
- "target",
- "frames",
- "transitionEasing",
- "curveFit",
- // Cycles specific:
- "period",
- "offset",
- "phase",
- // Shared with KeyAttributes:
- "alpha",
- "scaleX",
- "scaleY",
- "rotationX",
- "rotationY",
- "rotationZ",
- "translationX",
- "translationY",
- "translationZ",
+ """
+ .trimIndent()
)
+ assertThat(myFixture.lookupElementStrings!!)
+ .containsExactly(
+ "target",
+ "frames",
+ "transitionEasing",
+ "curveFit",
+ // Cycles specific:
+ "period",
+ "offset",
+ "phase",
+ // Shared with KeyAttributes:
+ "alpha",
+ "scaleX",
+ "scaleY",
+ "rotationX",
+ "rotationY",
+ "rotationZ",
+ "translationX",
+ "translationY",
+ "translationZ",
+ )
}
@Test
fun completeKeyFrameChildPropertyWithAtLeastOneElement() {
// `frames` with empty array
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -647,8 +746,11 @@
}
}
}
- """.trimIndent())
- myFixture.checkResult("""
+ """
+ .trimIndent()
+ )
+ myFixture.checkResult(
+ """
{
Transitions: {
default: {
@@ -661,10 +763,13 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// No `frames` property
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -676,8 +781,11 @@
}
}
}
- """.trimIndent())
- myFixture.checkResult("""
+ """
+ .trimIndent()
+ )
+ myFixture.checkResult(
+ """
{
Transitions: {
default: {
@@ -689,10 +797,13 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Completing `frames` for first time
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -704,8 +815,11 @@
}
}
}
- """.trimIndent())
- myFixture.checkResult("""
+ """
+ .trimIndent()
+ )
+ myFixture.checkResult(
+ """
{
Transitions: {
default: {
@@ -717,13 +831,17 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
fun completeKeyFrameChildPropertyWithArray() {
- // A completed number based property should be initialized with an array matching the same number of items as the `frames` property
- myFixture.completeJson5Text("""
+ // A completed number based property should be initialized with an array matching the same
+ // number of items as the `frames` property
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -736,8 +854,11 @@
}
}
}
- """.trimIndent())
- myFixture.checkResult("""
+ """
+ .trimIndent()
+ )
+ myFixture.checkResult(
+ """
{
Transitions: {
default: {
@@ -750,10 +871,14 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
- // Completed parameter should match `frames` size array regardless of the size of other parameters
- myFixture.completeJson5Text("""
+ // Completed parameter should match `frames` size array regardless of the size of other
+ // parameters
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -767,8 +892,11 @@
}
}
}
- """.trimIndent())
- myFixture.checkResult("""
+ """
+ .trimIndent()
+ )
+ myFixture.checkResult(
+ """
{
Transitions: {
default: {
@@ -782,10 +910,13 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Text based properties should not be initialized with an array
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -798,8 +929,11 @@
}
}
}
- """.trimIndent())
- myFixture.checkResult("""
+ """
+ .trimIndent()
+ )
+ myFixture.checkResult(
+ """
{
Transitions: {
default: {
@@ -812,13 +946,16 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
fun completionIsCaseSensitive() {
// Using wrong casing
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -826,9 +963,12 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Not changes in result
- myFixture.checkResult("""
+ myFixture.checkResult(
+ """
{
Transitions: {
default: {
@@ -836,10 +976,13 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// With correct casing
- myFixture.completeJson5Text("""
+ myFixture.completeJson5Text(
+ """
{
Transitions: {
default: {
@@ -847,9 +990,12 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Expression completed properly
- myFixture.checkResult("""
+ myFixture.checkResult(
+ """
{
Transitions: {
default: {
@@ -859,11 +1005,13 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
private fun CodeInsightTestFixture.completeJson5Text(@Language("JSON5") text: String) {
configureByText(Json5FileType.INSTANCE, text)
completeBasic()
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/ComposeDebuggerTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/ComposeDebuggerTest.kt
index 467bb28..760c235 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/ComposeDebuggerTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/ComposeDebuggerTest.kt
@@ -23,13 +23,12 @@
import com.intellij.ui.classFilter.DebuggerClassFilterProvider
import com.intellij.xdebugger.impl.settings.XDebuggerSettingManagerImpl
import com.intellij.xdebugger.settings.DebuggerSettingsCategory
+import javax.swing.JCheckBox
import org.junit.Rule
import org.junit.Test
-import javax.swing.JCheckBox
class ComposeDebuggerTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory()
private fun getClassFilterPatterns(): List<String> =
DebuggerClassFilterProvider.EP_NAME.extensionList.flatMap { it.filters }.map { it.pattern }
@@ -55,7 +54,9 @@
val element = serialize(settingsManager.state!!)
settings.filterComposeRuntimeClasses = true
- settingsManager.loadState(element!!.deserialize(XDebuggerSettingManagerImpl.SettingsState::class.java))
+ settingsManager.loadState(
+ element!!.deserialize(XDebuggerSettingManagerImpl.SettingsState::class.java)
+ )
assert(!settings.filterComposeRuntimeClasses)
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/ComposePositionManagerTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/ComposePositionManagerTest.kt
index edab98a..ef046ed 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/ComposePositionManagerTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/ComposePositionManagerTest.kt
@@ -24,15 +24,15 @@
import org.junit.Test
class ComposePositionManagerTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory()
private val project: Project
get() = projectRule.project
@Test
fun testComposeSingletonClasses() {
- val source = """
+ val source =
+ """
package a;
import androidx.compose.runtime.Composable
@@ -45,21 +45,23 @@
fun g(@Composable () -> Unit) {}
}
- """.trimIndent()
+ """
+ .trimIndent()
val file = projectRule.fixture.addFileToProject("src/a/test.kt", source)
- val debugProcess = mockDebugProcess(project, projectRule.testRootDisposable) {
- classType("a.A") {
- method("f", lines = listOf(4, 5, 6, 7, 8))
- method("g", lines = listOf(10))
- }
+ val debugProcess =
+ mockDebugProcess(project, projectRule.testRootDisposable) {
+ classType("a.A") {
+ method("f", lines = listOf(4, 5, 6, 7, 8))
+ method("g", lines = listOf(10))
+ }
- classType("a.ComposableSingletons\$TestKt")
+ classType("a.ComposableSingletons\$TestKt")
- classType("a.ComposableSingletons\$TestKt\$lambda-1") {
- method("invoke", lines = listOf(5, 6, 7))
+ classType("a.ComposableSingletons\$TestKt\$lambda-1") {
+ method("invoke", lines = listOf(5, 6, 7))
+ }
}
- }
val composePositionManager =
ComposePositionManagerFactory().createPositionManager(debugProcess) as ComposePositionManager
@@ -75,7 +77,8 @@
@Test
fun testComposeSingletonClassesJvmName() {
- val source = """
+ val source =
+ """
@file:JvmName("FileClass")
package a;
import androidx.compose.runtime.Composable
@@ -87,21 +90,23 @@
}
fun g(@Composable () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
val file = projectRule.fixture.addFileToProject("src/a/test2.kt", source)
- val debugProcess = mockDebugProcess(project, projectRule.testRootDisposable) {
- classType("a.FileClass") {
- method("f", lines = listOf(4, 5, 6, 7, 8))
- method("g", lines = listOf(10))
- }
+ val debugProcess =
+ mockDebugProcess(project, projectRule.testRootDisposable) {
+ classType("a.FileClass") {
+ method("f", lines = listOf(4, 5, 6, 7, 8))
+ method("g", lines = listOf(10))
+ }
- classType("a.ComposableSingletons\$Test2Kt")
+ classType("a.ComposableSingletons\$Test2Kt")
- classType("a.ComposableSingletons\$Test2Kt\$lambda-1") {
- method("invoke", lines = listOf(5, 6, 7))
+ classType("a.ComposableSingletons\$Test2Kt\$lambda-1") {
+ method("invoke", lines = listOf(5, 6, 7))
+ }
}
- }
val composePositionManager =
ComposePositionManagerFactory().createPositionManager(debugProcess) as ComposePositionManager
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/recomposition/ParamStateTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/recomposition/ParamStateTest.kt
index 1f3ac43..7cb33c3 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/recomposition/ParamStateTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/recomposition/ParamStateTest.kt
@@ -18,67 +18,72 @@
import com.google.common.truth.Truth.assertThat
import org.junit.Test
-/**
- * Tests for [ParamState]
- */
+/** Tests for [ParamState] */
class ParamStateTest {
@Test
fun oneFullValue() {
val states = ParamState.decode(listOf(0b1111101011000110100010000010101))
- assertThat(states.map { it.getDisplayName() }).containsExactly(
- "Changed",
- "Unchanged",
- "Evaluating",
- "Unchanged",
- "Changed",
- "Static",
- "Unknown",
- "Unknown",
- "Unknown",
- "Unknown",
- ).inOrder()
+ assertThat(states.map { it.getDisplayName() })
+ .containsExactly(
+ "Changed",
+ "Unchanged",
+ "Evaluating",
+ "Unchanged",
+ "Changed",
+ "Static",
+ "Unknown",
+ "Unknown",
+ "Unknown",
+ "Unknown",
+ )
+ .inOrder()
}
@Test
fun multipleValuea() {
- val states = ParamState.decode(listOf(
- 0b1111101011000110100010000010101,
- 0b1111101011000110100010000010100,
- 0b0010,
- ))
+ val states =
+ ParamState.decode(
+ listOf(
+ 0b1111101011000110100010000010101,
+ 0b1111101011000110100010000010100,
+ 0b0010,
+ )
+ )
- assertThat(states.map { it.getDisplayName() }).containsExactly(
- "Changed",
- "Unchanged",
- "Evaluating",
- "Unchanged",
- "Changed",
- "Static",
- "Unknown",
- "Unknown",
- "Unknown",
- "Unknown",
- "Changed",
- "Unchanged",
- "Evaluating",
- "Unchanged",
- "Changed",
- "Static",
- "Unknown",
- "Unknown",
- "Unknown",
- "Unknown",
- "Unchanged",
- "Evaluating",
- "Evaluating",
- "Evaluating",
- "Evaluating",
- "Evaluating",
- "Evaluating",
- "Evaluating",
- "Evaluating",
- "Evaluating",
- ).inOrder()
+ assertThat(states.map { it.getDisplayName() })
+ .containsExactly(
+ "Changed",
+ "Unchanged",
+ "Evaluating",
+ "Unchanged",
+ "Changed",
+ "Static",
+ "Unknown",
+ "Unknown",
+ "Unknown",
+ "Unknown",
+ "Changed",
+ "Unchanged",
+ "Evaluating",
+ "Unchanged",
+ "Changed",
+ "Static",
+ "Unknown",
+ "Unknown",
+ "Unknown",
+ "Unknown",
+ "Unchanged",
+ "Evaluating",
+ "Evaluating",
+ "Evaluating",
+ "Evaluating",
+ "Evaluating",
+ "Evaluating",
+ "Evaluating",
+ "Evaluating",
+ "Evaluating",
+ )
+ .inOrder()
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/render/ComposeStateObjectRendererTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/render/ComposeStateObjectRendererTest.kt
index 0e6fbe4..9a45e5b 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/render/ComposeStateObjectRendererTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/render/ComposeStateObjectRendererTest.kt
@@ -23,7 +23,6 @@
import com.android.tools.compose.debug.utils.invokeOnDebuggerManagerThread
import com.android.tools.compose.debug.utils.mockDebugProcess
import com.android.tools.compose.debug.utils.mockEvaluationContext
-
import com.android.tools.idea.testing.AndroidProjectRule
import com.google.common.truth.Truth.assertThat
import com.intellij.debugger.engine.DebugProcessImpl
@@ -35,50 +34,58 @@
import org.junit.Test
class ComposeStateObjectRendererTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory()
private val project
get() = projectRule.project
@Test
fun renderList() {
- val source = """
+ val source =
+ """
package androidx.compose.runtime.snapshots
class SnapshotStateList<T> {}
- """.trimIndent()
- projectRule.fixture.addFileToProject("src/androidx/compose/runtime/snapshots/SnapshotStateList.kt", source)
+ """
+ .trimIndent()
+ projectRule.fixture.addFileToProject(
+ "src/androidx/compose/runtime/snapshots/SnapshotStateList.kt",
+ source
+ )
// prepare
- val debugProcess: DebugProcessImpl = mockDebugProcess(project, projectRule.testRootDisposable) {
- val vm = this@mockDebugProcess.virtualMachineProxy.virtualMachine
+ val debugProcess: DebugProcessImpl =
+ mockDebugProcess(project, projectRule.testRootDisposable) {
+ val vm = this@mockDebugProcess.virtualMachineProxy.virtualMachine
- val listType = classType("java.util.List") {
- method("size", "()I") {
- value(MockIntegerValue(7, vm))
+ val listType =
+ classType("java.util.List") { method("size", "()I") { value(MockIntegerValue(7, vm)) } }
+
+ classType("androidx.compose.runtime.snapshots.SnapshotStateList") {
+ method("getDebuggerDisplayValue", "()Ljava/util/List;") {
+ value(MockClassObjectReference(listType, vm))
+ }
}
}
- classType("androidx.compose.runtime.snapshots.SnapshotStateList") {
- method("getDebuggerDisplayValue", "()Ljava/util/List;") {
- value(MockClassObjectReference(listType, vm))
- }
- }
- }
-
- val thisObjectType: ReferenceType = debugProcess.virtualMachineProxy
- .classesByName("androidx.compose.runtime.snapshots.SnapshotStateList")
- .first()
+ val thisObjectType: ReferenceType =
+ debugProcess.virtualMachineProxy
+ .classesByName("androidx.compose.runtime.snapshots.SnapshotStateList")
+ .first()
debugProcess.invokeOnDebuggerManagerThread {
// 1. check `Compose SnapshotStateList` is the first selected renderer by default.
- val renderer = NodeRendererSettings.getInstance().getAllRenderers(projectRule.project)
- .filter { it.isEnabled }
- .first { (it as? CompoundReferenceRenderer)?.isApplicableAsync(thisObjectType)?.get() == true }
+ val renderer =
+ NodeRendererSettings.getInstance()
+ .getAllRenderers(projectRule.project)
+ .filter { it.isEnabled }
+ .first {
+ (it as? CompoundReferenceRenderer)?.isApplicableAsync(thisObjectType)?.get() == true
+ }
assertThat(renderer.name).isEqualTo("Compose State Object")
- val thisObjectValue = MockClassObjectReference(thisObjectType, debugProcess.virtualMachineProxy.virtualMachine)
+ val thisObjectValue =
+ MockClassObjectReference(thisObjectType, debugProcess.virtualMachineProxy.virtualMachine)
val evaluationContext = mockEvaluationContext(debugProcess, thisObjectValue)
val thisValueDescriptor = MockValueDescriptor(project, thisObjectValue)
@@ -89,48 +96,56 @@
// 3. check if the children renderer is the same as the label renderer.
val childrenRenderer = (renderer as CompoundReferenceRenderer).childrenRenderer
- assertThat(childrenRenderer.uniqueId).isEqualTo("androidx.compose.runtime.snapshots.SnapshotStateList")
+ assertThat(childrenRenderer.uniqueId)
+ .isEqualTo("androidx.compose.runtime.snapshots.SnapshotStateList")
}
}
@Test
fun renderMap() {
- val source = """
+ val source =
+ """
package androidx.compose.runtime.snapshots
class SnapshotStateMap<K, V> {}
- """.trimIndent()
- projectRule.fixture.addFileToProject("src/androidx/compose/runtime/snapshots/SnapshotStateMap.kt", source)
+ """
+ .trimIndent()
+ projectRule.fixture.addFileToProject(
+ "src/androidx/compose/runtime/snapshots/SnapshotStateMap.kt",
+ source
+ )
// prepare
- val debugProcess: DebugProcessImpl = mockDebugProcess(project, projectRule.testRootDisposable) {
- val vm = this@mockDebugProcess.virtualMachineProxy.virtualMachine
+ val debugProcess: DebugProcessImpl =
+ mockDebugProcess(project, projectRule.testRootDisposable) {
+ val vm = this@mockDebugProcess.virtualMachineProxy.virtualMachine
- val mapType = classType("java.util.Map") {
- method("size", "()I") {
- value(MockIntegerValue(5, vm))
+ val mapType =
+ classType("java.util.Map") { method("size", "()I") { value(MockIntegerValue(5, vm)) } }
+
+ classType("androidx.compose.runtime.snapshots.SnapshotStateMap") {
+ method("getDebuggerDisplayValue") { value(MockClassObjectReference(mapType, vm)) }
}
}
- classType("androidx.compose.runtime.snapshots.SnapshotStateMap") {
- method("getDebuggerDisplayValue") {
- value(MockClassObjectReference(mapType, vm))
- }
- }
- }
-
- val thisObjectType: ReferenceType = debugProcess.virtualMachineProxy
- .classesByName("androidx.compose.runtime.snapshots.SnapshotStateMap")
- .first()
+ val thisObjectType: ReferenceType =
+ debugProcess.virtualMachineProxy
+ .classesByName("androidx.compose.runtime.snapshots.SnapshotStateMap")
+ .first()
debugProcess.invokeOnDebuggerManagerThread {
// 1. check `"Compose SnapshotStateList"` is the first selected renderer by default.
- val renderer = NodeRendererSettings.getInstance().getAllRenderers(projectRule.project)
- .filter { it.isEnabled }
- .first { (it as? CompoundReferenceRenderer)?.isApplicableAsync(thisObjectType)?.get() == true }
+ val renderer =
+ NodeRendererSettings.getInstance()
+ .getAllRenderers(projectRule.project)
+ .filter { it.isEnabled }
+ .first {
+ (it as? CompoundReferenceRenderer)?.isApplicableAsync(thisObjectType)?.get() == true
+ }
assertThat(renderer.name).isEqualTo("Compose State Object")
- val thisObjectValue = MockClassObjectReference(thisObjectType, debugProcess.virtualMachineProxy.virtualMachine)
+ val thisObjectValue =
+ MockClassObjectReference(thisObjectType, debugProcess.virtualMachineProxy.virtualMachine)
val evaluationContext = mockEvaluationContext(debugProcess, thisObjectValue)
val thisValueDescriptor = MockValueDescriptor(project, thisObjectValue)
@@ -141,151 +156,190 @@
// 3. check if the children renderer is the same as the label renderer.
val childrenRenderer = (renderer as CompoundReferenceRenderer).childrenRenderer
- assertThat(childrenRenderer.uniqueId).isEqualTo("androidx.compose.runtime.snapshots.SnapshotStateMap")
+ assertThat(childrenRenderer.uniqueId)
+ .isEqualTo("androidx.compose.runtime.snapshots.SnapshotStateMap")
}
}
@Test
fun renderInteger() {
- val source = """
+ val source =
+ """
package androidx.compose.runtime
open class SnapshotMutableStateImpl<T> {}
class ParcelableSnapshotMutableState : SnapshotMutableStateImpl<T>
- """.trimIndent()
- projectRule.fixture.addFileToProject("src/androidx/compose/runtime/SnapshotMutableStateImpl.kt", source)
+ """
+ .trimIndent()
+ projectRule.fixture.addFileToProject(
+ "src/androidx/compose/runtime/SnapshotMutableStateImpl.kt",
+ source
+ )
- val debugProcess: DebugProcessImpl = mockDebugProcess(project, projectRule.testRootDisposable) {
- val vm = this@mockDebugProcess.virtualMachineProxy.virtualMachine
+ val debugProcess: DebugProcessImpl =
+ mockDebugProcess(project, projectRule.testRootDisposable) {
+ val vm = this@mockDebugProcess.virtualMachineProxy.virtualMachine
- val snapshotMutableStateImplType = classType("androidx.compose.runtime.SnapshotMutableStateImpl") {
- method("getDebuggerDisplayValue") {
- value(MockIntegerValue(1, vm))
+ val snapshotMutableStateImplType =
+ classType("androidx.compose.runtime.SnapshotMutableStateImpl") {
+ method("getDebuggerDisplayValue") { value(MockIntegerValue(1, vm)) }
+ }
+
+ classType(
+ "androidx.compose.runtime.ParcelableSnapshotMutableState",
+ snapshotMutableStateImplType as ClassType
+ ) {
+ method("getDebuggerDisplayValue") { value(MockIntegerValue(2, vm)) }
}
}
- classType("androidx.compose.runtime.ParcelableSnapshotMutableState", snapshotMutableStateImplType as ClassType) {
- method("getDebuggerDisplayValue") {
- value(MockIntegerValue(2, vm))
- }
- }
- }
-
- val thisObjectType: ReferenceType = debugProcess.virtualMachineProxy
- .classesByName("androidx.compose.runtime.ParcelableSnapshotMutableState")
- .first()
+ val thisObjectType: ReferenceType =
+ debugProcess.virtualMachineProxy
+ .classesByName("androidx.compose.runtime.ParcelableSnapshotMutableState")
+ .first()
debugProcess.invokeOnDebuggerManagerThread {
// check `Compose SnapshotState` is the first selected renderer by default.
- val renderer = NodeRendererSettings.getInstance().getAllRenderers(project)
- .filter { it.isEnabled }
- .first { (it as? CompoundReferenceRenderer)?.isApplicableAsync(thisObjectType)?.get() == true }
+ val renderer =
+ NodeRendererSettings.getInstance()
+ .getAllRenderers(project)
+ .filter { it.isEnabled }
+ .first {
+ (it as? CompoundReferenceRenderer)?.isApplicableAsync(thisObjectType)?.get() == true
+ }
assertThat(renderer.name).isEqualTo("Compose State Object")
- val thisObjectValue = MockClassObjectReference(thisObjectType, debugProcess.virtualMachineProxy.virtualMachine)
+ val thisObjectValue =
+ MockClassObjectReference(thisObjectType, debugProcess.virtualMachineProxy.virtualMachine)
val thisValueDescriptor = MockValueDescriptor(project, thisObjectValue)
val evaluationContext = mockEvaluationContext(debugProcess, thisObjectValue)
- // check if the label is eventually properly rendered - it should be the label calculated for the underlying value.
+ // check if the label is eventually properly rendered - it should be the label calculated for
+ // the underlying value.
renderer.calcLabel(thisValueDescriptor, evaluationContext, MockitoKt.mock())
debugProcess.managerThread.processRemaining()
assertThat(thisValueDescriptor.valueText).isEqualTo("2")
// check if the children renderer is the same as the label renderer.
val childrenRenderer = (renderer as CompoundReferenceRenderer).childrenRenderer
- assertThat(childrenRenderer.uniqueId).isEqualTo("androidx.compose.runtime.SnapshotMutableStateImpl")
+ assertThat(childrenRenderer.uniqueId)
+ .isEqualTo("androidx.compose.runtime.SnapshotMutableStateImpl")
}
}
@Test
fun renderString() {
- val source = """
+ val source =
+ """
package androidx.compose.runtime
private class DerivedSnapshotState<T> {}
- """.trimIndent()
- projectRule.fixture.addFileToProject("src/androidx/compose/runtime/DerivedSnapshotState.kt", source)
+ """
+ .trimIndent()
+ projectRule.fixture.addFileToProject(
+ "src/androidx/compose/runtime/DerivedSnapshotState.kt",
+ source
+ )
- val debugProcess: DebugProcessImpl = mockDebugProcess(project, projectRule.testRootDisposable) {
- val vm = this@mockDebugProcess.virtualMachineProxy.virtualMachine
+ val debugProcess: DebugProcessImpl =
+ mockDebugProcess(project, projectRule.testRootDisposable) {
+ val vm = this@mockDebugProcess.virtualMachineProxy.virtualMachine
- val stringType = classType("java.lang.String")
+ val stringType = classType("java.lang.String")
- classType("androidx.compose.runtime.DerivedSnapshotState") {
- method("getDebuggerDisplayValue") {
- value(MockStringReference("This is fake string value.", stringType, vm))
+ classType("androidx.compose.runtime.DerivedSnapshotState") {
+ method("getDebuggerDisplayValue") {
+ value(MockStringReference("This is fake string value.", stringType, vm))
+ }
}
}
- }
- val thisObjectType: ReferenceType = debugProcess.virtualMachineProxy
- .classesByName("androidx.compose.runtime.DerivedSnapshotState")
- .first()
+ val thisObjectType: ReferenceType =
+ debugProcess.virtualMachineProxy
+ .classesByName("androidx.compose.runtime.DerivedSnapshotState")
+ .first()
debugProcess.invokeOnDebuggerManagerThread {
// check `Compose SnapshotState` is the first selected renderer by default.
- val renderer = NodeRendererSettings.getInstance().getAllRenderers(projectRule.project)
- .filter { it.isEnabled }
- .first { (it as? CompoundReferenceRenderer)?.isApplicableAsync(thisObjectType)?.get() == true }
+ val renderer =
+ NodeRendererSettings.getInstance()
+ .getAllRenderers(projectRule.project)
+ .filter { it.isEnabled }
+ .first {
+ (it as? CompoundReferenceRenderer)?.isApplicableAsync(thisObjectType)?.get() == true
+ }
assertThat(renderer.name).isEqualTo("Compose State Object")
- val thisObjectValue = MockClassObjectReference(thisObjectType, debugProcess.virtualMachineProxy.virtualMachine)
+ val thisObjectValue =
+ MockClassObjectReference(thisObjectType, debugProcess.virtualMachineProxy.virtualMachine)
val evaluationContext = mockEvaluationContext(debugProcess, thisObjectValue)
val thisValueDescriptor = MockValueDescriptor(project, thisObjectValue)
- // check if the label is eventually properly rendered - it should be the label calculated for the underlying value.
+ // check if the label is eventually properly rendered - it should be the label calculated for
+ // the underlying value.
renderer.calcLabel(thisValueDescriptor, evaluationContext, MockitoKt.mock())
debugProcess.managerThread.processRemaining()
assertThat(thisValueDescriptor.valueText).isEqualTo("This is fake string value.")
// check if the children renderer is the same as the label renderer.
val childrenRenderer = (renderer as CompoundReferenceRenderer).childrenRenderer
- assertThat(childrenRenderer.uniqueId).isEqualTo("androidx.compose.runtime.DerivedSnapshotState")
+ assertThat(childrenRenderer.uniqueId)
+ .isEqualTo("androidx.compose.runtime.DerivedSnapshotState")
}
}
@Test
fun checkApplicable_NoValidMethod() {
- val source = """
+ val source =
+ """
package androidx.compose.runtime.snapshots
class SnapshotStateList<T> {}
- """.trimIndent()
- projectRule.fixture.addFileToProject("src/androidx/compose/runtime/snapshots/SnapshotStateList.kt", source)
+ """
+ .trimIndent()
+ projectRule.fixture.addFileToProject(
+ "src/androidx/compose/runtime/snapshots/SnapshotStateList.kt",
+ source
+ )
// prepare
- val debugProcess: DebugProcessImpl = mockDebugProcess(project, projectRule.testRootDisposable) {
- val vm = this@mockDebugProcess.virtualMachineProxy.virtualMachine
- classType("java.lang.Object") {
- method("toString", "()Ljava/lang/String;")
- }
+ val debugProcess: DebugProcessImpl =
+ mockDebugProcess(project, projectRule.testRootDisposable) {
+ val vm = this@mockDebugProcess.virtualMachineProxy.virtualMachine
+ classType("java.lang.Object") { method("toString", "()Ljava/lang/String;") }
- val stringType = classType("java.lang.String")
+ val stringType = classType("java.lang.String")
- classType("androidx.compose.runtime.snapshots.SnapshotStateList") {
- // Please note: method `getDebuggerDisplayValue` is not declared.
+ classType("androidx.compose.runtime.snapshots.SnapshotStateList") {
+ // Please note: method `getDebuggerDisplayValue` is not declared.
- method("toString", "()Ljava/lang/String;") {
- value(MockStringReference("SnapshotStateList@1234", stringType, vm))
+ method("toString", "()Ljava/lang/String;") {
+ value(MockStringReference("SnapshotStateList@1234", stringType, vm))
+ }
}
}
- }
- val thisObjectType: ReferenceType = debugProcess.virtualMachineProxy
- .classesByName("androidx.compose.runtime.snapshots.SnapshotStateList")
- .first()
+ val thisObjectType: ReferenceType =
+ debugProcess.virtualMachineProxy
+ .classesByName("androidx.compose.runtime.snapshots.SnapshotStateList")
+ .first()
debugProcess.invokeOnDebuggerManagerThread {
// 1. Check if `Compose State Object` is the first selected renderer by default since
// `getDebuggerDisplayValue` method is not found.
- val renderer = NodeRendererSettings.getInstance().getAllRenderers(projectRule.project)
- .filter { it.isEnabled }
- .first { (it as? CompoundReferenceRenderer)?.isApplicableAsync(thisObjectType)?.get() == true }
+ val renderer =
+ NodeRendererSettings.getInstance()
+ .getAllRenderers(projectRule.project)
+ .filter { it.isEnabled }
+ .first {
+ (it as? CompoundReferenceRenderer)?.isApplicableAsync(thisObjectType)?.get() == true
+ }
assertThat(renderer.name).isEqualTo("Compose State Object")
- val thisObjectValue = MockClassObjectReference(thisObjectType, debugProcess.virtualMachineProxy.virtualMachine)
+ val thisObjectValue =
+ MockClassObjectReference(thisObjectType, debugProcess.virtualMachineProxy.virtualMachine)
val evaluationContext = mockEvaluationContext(debugProcess, thisObjectValue)
val thisValueDescriptor = MockValueDescriptor(project, thisObjectValue)
@@ -296,4 +350,4 @@
assertThat(thisValueDescriptor.valueText).isEqualTo("SnapshotStateList@1234")
}
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/render/KotlinMapEntryRendererTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/render/KotlinMapEntryRendererTest.kt
index 689508f..113af09 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/render/KotlinMapEntryRendererTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/render/KotlinMapEntryRendererTest.kt
@@ -33,8 +33,7 @@
import org.junit.Test
class KotlinMapEntryRendererTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory()
private val project
get() = projectRule.project
@@ -42,34 +41,39 @@
@Test
fun checkRenderer() {
// prepare
- val debugProcess: DebugProcessImpl = mockDebugProcess(project, projectRule.testRootDisposable) {
- val vm = this@mockDebugProcess.virtualMachineProxy.virtualMachine
+ val debugProcess: DebugProcessImpl =
+ mockDebugProcess(project, projectRule.testRootDisposable) {
+ val vm = this@mockDebugProcess.virtualMachineProxy.virtualMachine
- val stringType = classType("java.lang.String")
+ val stringType = classType("java.lang.String")
- classType("java.util.Map\$Entry") {
- method("getKey", "()Ljava/lang/Object;") {
- value(MockStringReference("key1", stringType, vm))
- }
+ classType("java.util.Map\$Entry") {
+ method("getKey", "()Ljava/lang/Object;") {
+ value(MockStringReference("key1", stringType, vm))
+ }
- method("getValue", "()Ljava/lang/Object;") {
- value(MockStringReference("value1", stringType, vm))
+ method("getValue", "()Ljava/lang/Object;") {
+ value(MockStringReference("value1", stringType, vm))
+ }
}
}
- }
- val thisObjectType: ReferenceType = debugProcess.virtualMachineProxy
- .classesByName("java.util.Map\$Entry")
- .first()
+ val thisObjectType: ReferenceType =
+ debugProcess.virtualMachineProxy.classesByName("java.util.Map\$Entry").first()
debugProcess.invokeOnDebuggerManagerThread {
// 1. check `Kotlin MapEntry` is the first selected renderer by default.
- val renderer = NodeRendererSettings.getInstance().getAllRenderers(projectRule.project)
- .filter { it.isEnabled }
- .first { (it as? CompoundReferenceRenderer)?.isApplicableAsync(thisObjectType)?.get() == true }
+ val renderer =
+ NodeRendererSettings.getInstance()
+ .getAllRenderers(projectRule.project)
+ .filter { it.isEnabled }
+ .first {
+ (it as? CompoundReferenceRenderer)?.isApplicableAsync(thisObjectType)?.get() == true
+ }
assertThat(renderer.name).isEqualTo("Kotlin MapEntry")
- val thisObjectValue = MockClassObjectReference(thisObjectType, debugProcess.virtualMachineProxy.virtualMachine)
+ val thisObjectValue =
+ MockClassObjectReference(thisObjectType, debugProcess.virtualMachineProxy.virtualMachine)
val evaluationContext = mockEvaluationContext(debugProcess, thisObjectValue)
val thisValueDescriptor = MockValueDescriptor(project, thisObjectValue)
@@ -78,8 +82,9 @@
assertThat(label).isEqualTo("key1 -> value1")
// 3. check if `EnumerationChildrenRenderer` is the children renderer.
- val childrenRenderer = (renderer as CompoundReferenceRenderer).childrenRenderer as EnumerationChildrenRenderer
+ val childrenRenderer =
+ (renderer as CompoundReferenceRenderer).childrenRenderer as EnumerationChildrenRenderer
assertThat(childrenRenderer.children.map { it.myName }).containsExactly("key", "value")
}
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/DebugProcessTestUtils.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/DebugProcessTestUtils.kt
index dfb4059..414c549 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/DebugProcessTestUtils.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/DebugProcessTestUtils.kt
@@ -28,8 +28,7 @@
override fun action() {
try {
future.complete(f())
- }
- catch (t: Throwable) {
+ } catch (t: Throwable) {
future.completeExceptionally(t)
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockDebugProcessUtils.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockDebugProcessUtils.kt
index 711fe1e..eacca10 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockDebugProcessUtils.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockDebugProcessUtils.kt
@@ -58,7 +58,11 @@
fun value(value: Value)
}
-fun mockDebugProcess(project: Project, disposable: Disposable, block: MockDebugProcessScope.() -> Unit): MockDebugProcessImpl {
+fun mockDebugProcess(
+ project: Project,
+ disposable: Disposable,
+ block: MockDebugProcessScope.() -> Unit
+): MockDebugProcessImpl {
val debugProcess = MockDebugProcessImpl(project)
Disposer.register(disposable) {
// Stop and dispose the debugger process in order to avoid leaking the project via
@@ -68,38 +72,43 @@
debugProcess.dispose()
}
object : MockDebugProcessScope {
- override val virtualMachineProxy: VirtualMachineProxyImpl
- get() = debugProcess.virtualMachineProxy
+ override val virtualMachineProxy: VirtualMachineProxyImpl
+ get() = debugProcess.virtualMachineProxy
- override fun classType(
- signature: String,
- superClass: ClassType?,
- interfaces: List<InterfaceType>,
- block: MockReferenceTypeScope.() -> Unit
- ): ClassType {
- val classType = debugProcess.addClassType(signature, superClass, interfaces) as MockClassType
- object : MockReferenceTypeScope {
- override fun method(
- name: String,
- signature: String?,
- argumentTypeNames: List<String>,
- lines: List<Int>,
- block: MockValueScope.() -> Unit
- ) {
- val method = MockMethod(name, signature, argumentTypeNames, lines, classType, debugProcess)
- classType.addMethod(method)
+ override fun classType(
+ signature: String,
+ superClass: ClassType?,
+ interfaces: List<InterfaceType>,
+ block: MockReferenceTypeScope.() -> Unit
+ ): ClassType {
+ val classType =
+ debugProcess.addClassType(signature, superClass, interfaces) as MockClassType
+ object : MockReferenceTypeScope {
+ override fun method(
+ name: String,
+ signature: String?,
+ argumentTypeNames: List<String>,
+ lines: List<Int>,
+ block: MockValueScope.() -> Unit
+ ) {
+ val method =
+ MockMethod(name, signature, argumentTypeNames, lines, classType, debugProcess)
+ classType.addMethod(method)
- object : MockValueScope {
- override fun value(value: Value) {
- classType.setValue(value, method)
+ object : MockValueScope {
+ override fun value(value: Value) {
+ classType.setValue(value, method)
+ }
+ }
+ .block()
}
- }.block()
- }
- }.block()
+ }
+ .block()
- return classType
+ return classType
+ }
}
- }.block()
+ .block()
return debugProcess
}
@@ -108,12 +117,16 @@
private val mockVirtualMachineProxy = MockVirtualMachineProxy(this, referencesByName)
val prepareRequestPatterns = mutableListOf<String>()
- private val mockRequestManager = object : RequestManagerImpl(this) {
- override fun createClassPrepareRequest(requestor: ClassPrepareRequestor, pattern: String): ClassPrepareRequest? {
- prepareRequestPatterns.add(pattern)
- return MockitoKt.mock()
+ private val mockRequestManager =
+ object : RequestManagerImpl(this) {
+ override fun createClassPrepareRequest(
+ requestor: ClassPrepareRequestor,
+ pattern: String
+ ): ClassPrepareRequest? {
+ prepareRequestPatterns.add(pattern)
+ return MockitoKt.mock()
+ }
}
- }
override fun getVirtualMachineProxy(): VirtualMachineProxyImpl = mockVirtualMachineProxy
override fun getSearchScope(): GlobalSearchScope = GlobalSearchScope.allScope(project)
@@ -125,8 +138,9 @@
method: Method,
args: List<Value>
): Value {
- val referenceType: ReferenceType = referencesByName[objRef.type().name()]
- ?: error("Reference type \"${objRef.type()}\" is not available when asked.")
+ val referenceType: ReferenceType =
+ referencesByName[objRef.type().name()]
+ ?: error("Reference type \"${objRef.type()}\" is not available when asked.")
return when (referenceType) {
is ClassType -> referenceType.invokeMethod(objRef.owningThread(), method, args, 0)
@@ -149,8 +163,6 @@
superClass: ClassType?,
interfaces: List<InterfaceType>
): ClassType {
- return MockClassType(this, name, superClass, interfaces).apply {
- referencesByName[name] = this
- }
+ return MockClassType(this, name, superClass, interfaces).apply { referencesByName[name] = this }
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockEvaluationContextUtils.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockEvaluationContextUtils.kt
index 0db63e4..01461cf 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockEvaluationContextUtils.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockEvaluationContextUtils.kt
@@ -23,7 +23,10 @@
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.sun.jdi.ObjectReference
-internal fun mockEvaluationContext(debugProcess: DebugProcessImpl, objectReference: ObjectReference): EvaluationContextImpl {
+internal fun mockEvaluationContext(
+ debugProcess: DebugProcessImpl,
+ objectReference: ObjectReference
+): EvaluationContextImpl {
val mockSuspendContext = MockitoKt.mock<SuspendContextImpl>()
whenever(mockSuspendContext.debugProcess).thenReturn(debugProcess)
@@ -31,4 +34,4 @@
whenever(mockFrameProxyImpl.thisObject()).thenReturn(objectReference)
return EvaluationContextImpl(mockSuspendContext, mockFrameProxyImpl)
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockIntegerValue.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockIntegerValue.kt
index a50ec45..f5bb2bb 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockIntegerValue.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockIntegerValue.kt
@@ -21,11 +21,12 @@
import com.sun.jdi.Type
import com.sun.jdi.VirtualMachine
-class MockIntegerValue(private val value: Int, private val virtualMachine: VirtualMachine) : IntegerValue by MockitoKt.mock() {
+class MockIntegerValue(private val value: Int, private val virtualMachine: VirtualMachine) :
+ IntegerValue by MockitoKt.mock() {
override fun toString(): String = value.toString()
override fun virtualMachine(): VirtualMachine = virtualMachine
override fun type(): Type = MockitoKt.mock<IntegerType>()
override fun intValue(): Int = value
override fun longValue(): Long = value.toLong()
override fun value(): Int = value
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockMethod.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockMethod.kt
index 92d093e..afcce5b 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockMethod.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockMethod.kt
@@ -32,7 +32,8 @@
) : Method by MockitoKt.mock() {
override fun name() = name
override fun declaringType(): ReferenceType = declaringType
- override fun allLineLocations(): List<Location> = lines.map { GeneratedLocation(declaringType, name, it) }
+ override fun allLineLocations(): List<Location> =
+ lines.map { GeneratedLocation(declaringType, name, it) }
override fun argumentTypeNames(): List<String> = argumentTypeNames
override fun signature(): String? = signature
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockObjectReference.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockObjectReference.kt
index 121bad9..2d6d1bd 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockObjectReference.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockObjectReference.kt
@@ -43,8 +43,14 @@
override fun reflectedType(): ReferenceType = referenceType
override fun toString(): String {
- return "instance of " + referenceType().name() +
- "(reflected class=" + reflectedType().name() + ", " + "id=" + "@fakeUniqueId" + ")"
+ return "instance of " +
+ referenceType().name() +
+ "(reflected class=" +
+ reflectedType().name() +
+ ", " +
+ "id=" +
+ "@fakeUniqueId" +
+ ")"
}
}
@@ -55,4 +61,4 @@
) : StringReference, MockObjectReference(referenceType, vm) {
override fun value(): String = value
override fun toString(): String = "\"$value\""
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockReferenceType.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockReferenceType.kt
index 21ffd7d..d1f15b6 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockReferenceType.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockReferenceType.kt
@@ -38,9 +38,8 @@
override fun name() = name
override fun signature(): String = "L$name;"
override fun isPrepared(): Boolean = true
- override fun nestedTypes(): List<ReferenceType> = debugProcess.virtualMachineProxy.allClasses().filter {
- it.name().startsWith("${name()}\$")
- }
+ override fun nestedTypes(): List<ReferenceType> =
+ debugProcess.virtualMachineProxy.allClasses().filter { it.name().startsWith("${name()}\$") }
override fun allLineLocations(): List<Location> = methods().flatMap { it.allLineLocations() }
override fun allMethods(): List<Method> = methods()
@@ -68,7 +67,12 @@
methodToValue[name] = value
}
- override fun invokeMethod(thread: ThreadReference?, method: Method, arguments: List<Value>, options: Int): Value {
+ override fun invokeMethod(
+ thread: ThreadReference?,
+ method: Method,
+ arguments: List<Value>,
+ options: Int
+ ): Value {
val name = method.name() ?: error("Name of method \"$method\" is null.")
return methodToValue[name] ?: error("Fake value is not set for method \"$name\" when asked.")
}
@@ -79,9 +83,14 @@
override fun subclasses(): List<ClassType> = emptyList()
override fun isEnum() = false
override fun setValue(field: Field?, value: Value?) {}
- override fun newInstance(thread: ThreadReference?, method: Method, arguments: List<Value>, options: Int): ObjectReference {
+ override fun newInstance(
+ thread: ThreadReference?,
+ method: Method,
+ arguments: List<Value>,
+ options: Int
+ ): ObjectReference {
throw UnsupportedOperationException()
}
override fun concreteMethodByName(name: String, signature: String?): Method? = null
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockValueDescriptor.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockValueDescriptor.kt
index 0502344..e26cd52 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockValueDescriptor.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockValueDescriptor.kt
@@ -34,4 +34,4 @@
override fun setValueLabel(label: String) {
myValueText = label
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockVirtualMachineProxy.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockVirtualMachineProxy.kt
index ced2eee..249555a 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockVirtualMachineProxy.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/debug/utils/MockVirtualMachineProxy.kt
@@ -30,11 +30,10 @@
override fun classesByName(s: String): List<ReferenceType> = virtualMachine.classesByName(s)
}
-private class MockVirtualMachine(
- private val referencesByName: Map<String, ReferenceType>
-) : VirtualMachine by MockitoKt.mock() {
+private class MockVirtualMachine(private val referencesByName: Map<String, ReferenceType>) :
+ VirtualMachine by MockitoKt.mock() {
override fun name(): String = "MockDalvik"
override fun allClasses(): List<ReferenceType> = referencesByName.values.toList()
override fun classesByName(s: String): List<ReferenceType> = listOfNotNull(referencesByName[s])
override fun topLevelThreadGroups(): List<ThreadGroupReference> = emptyList()
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/formatting/ComposePostFormatProcessorTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/formatting/ComposePostFormatProcessorTest.kt
index b8918a0..e49608e 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/formatting/ComposePostFormatProcessorTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/formatting/ComposePostFormatProcessorTest.kt
@@ -31,12 +31,9 @@
import org.junit.Rule
import org.junit.Test
-/**
- * Test for [ComposePostFormatProcessor].
- */
+/** Test for [ComposePostFormatProcessor]. */
class ComposePostFormatProcessorTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory()
private val myFixture: CodeInsightTestFixture by lazy { projectRule.fixture }
@@ -58,10 +55,12 @@
fun adjust():Modifier {}
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val settings = CodeStyle.getSettings(project).getCustomSettings(KotlinCodeStyleSettings::class.java)
+ val settings =
+ CodeStyle.getSettings(project).getCustomSettings(KotlinCodeStyleSettings::class.java)
settings.CONTINUATION_INDENT_FOR_CHAINED_CALLS = false
}
@@ -79,11 +78,13 @@
fun HomeScreen() {
val m = Modifier.adjust().adjust()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
WriteCommandAction.writeCommandAction(project).run<RuntimeException> {
- CodeStyleManager.getInstance(project).reformatText(myFixture.file, listOf(myFixture.file.textRange))
+ CodeStyleManager.getInstance(project)
+ .reformatText(myFixture.file, listOf(myFixture.file.textRange))
}
myFixture.checkResult(
@@ -99,7 +100,8 @@
.adjust()
.adjust()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -117,11 +119,13 @@
fun HomeScreen() {
val m = Modifier.adjust()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
WriteCommandAction.writeCommandAction(project).run<RuntimeException> {
- CodeStyleManager.getInstance(project).reformatText(myFixture.file, listOf(myFixture.file.textRange))
+ CodeStyleManager.getInstance(project)
+ .reformatText(myFixture.file, listOf(myFixture.file.textRange))
}
myFixture.checkResult(
@@ -135,8 +139,8 @@
fun HomeScreen() {
val m = Modifier.adjust()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
-
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/AddComposableToFunctionQuickFixTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/AddComposableToFunctionQuickFixTest.kt
index 672c78b..3aba4f3 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/AddComposableToFunctionQuickFixTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/AddComposableToFunctionQuickFixTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.tools.compose.intentions
+import com.android.tools.compose.analysis.setUpCompilerArgumentsForComposeCompilerPlugin
import com.android.tools.idea.testing.AndroidProjectRule
import com.android.tools.idea.testing.loadNewFile
import com.android.tools.idea.testing.moveCaret
@@ -32,8 +33,7 @@
@RunWith(JUnit4::class)
class AddComposableToFunctionQuickFixTest {
- @get:Rule
- val projectRule = AndroidProjectRule.inMemory()
+ @get:Rule val projectRule = AndroidProjectRule.inMemory()
private val myFixture by lazy { projectRule.fixture }
private val myProject by lazy { projectRule.project }
@@ -41,6 +41,7 @@
@Before
fun setUp() {
myFixture.stubComposableAnnotation()
+ setUpCompilerArgumentsForComposeCompilerPlugin(myProject)
}
@Test
@@ -59,7 +60,8 @@
fun NonComposable<caret>Function() {
ComposableFunction()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
invokeQuickFix()
@@ -77,7 +79,9 @@
fun NonComposableFunction() {
ComposableFunction()
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
@@ -96,7 +100,8 @@
fun NonComposableFunction() {
Composable<caret>Function()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
invokeQuickFix()
@@ -114,7 +119,9 @@
fun NonComposableFunction() {
ComposableFunction()
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
@@ -129,7 +136,8 @@
@Composable
fun ComposableFunction() {}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.loadNewFile(
@@ -141,7 +149,8 @@
fun NonComposable<caret>Function() {
ComposableFunction()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
invokeQuickFix()
@@ -156,7 +165,9 @@
fun NonComposableFunction() {
ComposableFunction()
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
@@ -181,12 +192,15 @@
ComposableFunction() // invocation
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
assertQuickFixNotAvailable("Composable|Function() // invocation")
- ApplicationManager.getApplication().invokeAndWait { myFixture.moveCaret("fun NonComposable|Function") }
+ ApplicationManager.getApplication().invokeAndWait {
+ myFixture.moveCaret("fun NonComposable|Function")
+ }
invokeQuickFix()
myFixture.checkResult(
@@ -208,7 +222,9 @@
ComposableFunction() // invocation
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
@@ -231,11 +247,14 @@
ComposableFunction() // invocation
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
- // Adding @Composable to `NonComposableFunction` isn't correct here. To fix the build error, @Composable should be added to the
- // `content` parameter of `functionThatTakesALambda`. That's currently out of scope for this quick fix (although could be added in the
+ // Adding @Composable to `NonComposableFunction` isn't correct here. To fix the build error,
+ // @Composable should be added to the
+ // `content` parameter of `functionThatTakesALambda`. That's currently out of scope for this
+ // quick fix (although could be added in the
// future), so for now we just assert that the quick fix isn't available.
assertQuickFixNotAvailable("fun NonComposable|Function() {")
assertQuickFixNotAvailable("functionThatTake|sALambda {")
@@ -263,7 +282,8 @@
}
return MyClass()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
assertQuickFixNotAvailable("fun getMy|Class(): Any")
@@ -293,13 +313,18 @@
}
return MyClass()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
- // The compiler reports this error on the property, even though it's the getter function that needs to have @Composable added.
- // Furthermore, it does the same when the setter function calls a @Composable function, but that scenario can't be handled because
- // @Composable doesn't apply to setters. It's not trivial to determine from the compiler error on the property which accessor it should
- // apply to, so we just don't show the fix. There will be a separate error on the getter() in this case that shows the fix anyway, which
+ // The compiler reports this error on the property, even though it's the getter function that
+ // needs to have @Composable added.
+ // Furthermore, it does the same when the setter function calls a @Composable function, but that
+ // scenario can't be handled because
+ // @Composable doesn't apply to setters. It's not trivial to determine from the compiler error
+ // on the property which accessor it should
+ // apply to, so we just don't show the fix. There will be a separate error on the getter() in
+ // this case that shows the fix anyway, which
// suffices for the user to be able to quickly fix the error.
assertQuickFixNotAvailable("val pro|perty: String")
@@ -328,7 +353,9 @@
}
return MyClass()
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
@@ -356,7 +383,8 @@
}
return MyClass()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
// @Composable is not allowed on setters, so we shouldn't suggest adding it.
@@ -388,7 +416,8 @@
}
return MyClass()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
assertQuickFixNotAvailable("fun getMy|Class(): Any")
@@ -400,17 +429,16 @@
val fixFilter: (IntentionAction) -> Boolean =
if (expectedFunctionName != null) {
{ action -> action.text == "Add '@Composable' to function '$expectedFunctionName'" }
- }
- else {
+ } else {
{ action -> action.text.startsWith("Add '@Composable' to function '") }
}
val action = myFixture.availableIntentions.singleOrNull(fixFilter)
if (action == null) {
- val intentionTexts = myFixture.availableIntentions.joinToString(transform = IntentionAction::getText)
+ val intentionTexts =
+ myFixture.availableIntentions.joinToString(transform = IntentionAction::getText)
fail("Could not find expected quick fix. Available intentions: $intentionTexts")
- }
- else {
+ } else {
WriteCommandAction.runWriteCommandAction(myProject) {
action.invoke(myProject, myFixture.editor, myFixture.file)
}
@@ -418,7 +446,12 @@
}
private fun assertQuickFixNotAvailable(window: String) {
- ApplicationManager.getApplication().invokeAndWait { myFixture.moveCaret (window) }
- assertThat(myFixture.availableIntentions.filter { it.text.startsWith("Add '@Composable' to function '") }).isEmpty()
+ ApplicationManager.getApplication().invokeAndWait { myFixture.moveCaret(window) }
+ assertThat(
+ myFixture.availableIntentions.filter {
+ it.text.startsWith("Add '@Composable' to function '")
+ }
+ )
+ .isEmpty()
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeCreatePreviewActionTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeCreatePreviewActionTest.kt
index fffa4c3..48a6c61 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeCreatePreviewActionTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeCreatePreviewActionTest.kt
@@ -24,9 +24,7 @@
import org.jetbrains.android.compose.stubComposableAnnotation
import org.jetbrains.android.compose.stubPreviewAnnotation
-/**
- * Test for [ComposeCreatePreviewAction]
- */
+/** Test for [ComposeCreatePreviewAction] */
class ComposeCreatePreviewActionTest : JavaCodeInsightFixtureAdtTestCase() {
override fun setUp() {
super.setUp()
@@ -49,17 +47,22 @@
Text("Davenport, California")
Text("December 2018")
}
- """.trimIndent()
+ """
+ .trimIndent()
)
val action = myFixture.availableIntentions.find { it.text == "Create Preview" }
assertThat(action).isNotNull()
- WriteCommandAction.runWriteCommandAction(myFixture.project, Runnable {
- // Within unit tests ListPopupImpl.showInBestPositionFor doesn't open popup and acts like fist item was selected.
- // In our case wrap in Container will be selected.
- action!!.invoke(myFixture.project, myFixture.editor, myFixture.file)
- })
+ WriteCommandAction.runWriteCommandAction(
+ myFixture.project,
+ Runnable {
+ // Within unit tests ListPopupImpl.showInBestPositionFor doesn't open popup and acts like
+ // fist item was selected.
+ // In our case wrap in Container will be selected.
+ action!!.invoke(myFixture.project, myFixture.editor, myFixture.file)
+ }
+ )
myFixture.checkResult(
// language=kotlin
@@ -76,7 +79,8 @@
Text("Davenport, California")
Text("December 2018")
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -97,17 +101,22 @@
}
</selection>
- """.trimIndent()
+ """
+ .trimIndent()
)
var action = myFixture.availableIntentions.find { it.text == "Create Preview" }
assertThat(action).isNotNull()
- WriteCommandAction.runWriteCommandAction(myFixture.project, Runnable {
- // Within unit tests ListPopupImpl.showInBestPositionFor doesn't open popup and acts like fist item was selected.
- // In our case wrap in Container will be selected.
- action!!.invoke(myFixture.project, myFixture.editor, myFixture.file)
- })
+ WriteCommandAction.runWriteCommandAction(
+ myFixture.project,
+ Runnable {
+ // Within unit tests ListPopupImpl.showInBestPositionFor doesn't open popup and acts like
+ // fist item was selected.
+ // In our case wrap in Container will be selected.
+ action!!.invoke(myFixture.project, myFixture.editor, myFixture.file)
+ }
+ )
myFixture.checkResult(
// language=kotlin
@@ -126,10 +135,10 @@
}
- """.trimIndent()
+ """
+ .trimIndent()
)
-
myFixture.loadNewFile(
"src/com/example/Test2.kt",
// language=kotlin
@@ -148,17 +157,22 @@
}
</selection>
- """.trimIndent()
+ """
+ .trimIndent()
)
action = myFixture.availableIntentions.find { it.text == "Create Preview" }
assertThat(action).isNotNull()
- WriteCommandAction.runWriteCommandAction(myFixture.project, Runnable {
- // Within unit tests ListPopupImpl.showInBestPositionFor doesn't open popup and acts like fist item was selected.
- // In our case wrap in Container will be selected.
- action!!.invoke(myFixture.project, myFixture.editor, myFixture.file)
- })
+ WriteCommandAction.runWriteCommandAction(
+ myFixture.project,
+ Runnable {
+ // Within unit tests ListPopupImpl.showInBestPositionFor doesn't open popup and acts like
+ // fist item was selected.
+ // In our case wrap in Container will be selected.
+ action!!.invoke(myFixture.project, myFixture.editor, myFixture.file)
+ }
+ )
myFixture.checkResult(
// language=kotlin
@@ -178,7 +192,8 @@
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeSurroundWithWidgetActionTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeSurroundWithWidgetActionTest.kt
index 6f1c3b6..b370073 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeSurroundWithWidgetActionTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeSurroundWithWidgetActionTest.kt
@@ -24,9 +24,7 @@
import org.jetbrains.android.JavaCodeInsightFixtureAdtTestCase
import org.jetbrains.android.compose.stubComposableAnnotation
-/**
- * Test for [ComposeSurroundWithWidgetActionGroup] and [ComposeSurroundWithWidgetAction]
- */
+/** Test for [ComposeSurroundWithWidgetActionGroup] and [ComposeSurroundWithWidgetAction] */
class ComposeSurroundWithWidgetActionTest : JavaCodeInsightFixtureAdtTestCase() {
public override fun setUp() {
super.setUp()
@@ -43,7 +41,8 @@
inline fun Row(content: @Composable () -> Unit) {}
inline fun Column(content: @Composable () -> Unit) {}
inline fun Box(content: @Composable () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -55,7 +54,8 @@
myFixture.loadNewFile("src/com/example/Test.kt", inputFileContent)
val action = actionProvider()
WriteCommandAction.runWriteCommandAction(myFixture.project) {
- // Within unit tests ListPopupImpl.showInBestPositionFor doesn't open popup and acts like fist item was selected.
+ // Within unit tests ListPopupImpl.showInBestPositionFor doesn't open popup and acts like fist
+ // item was selected.
// In our case wrap in Box will be selected.
action.invoke(myFixture.project, myFixture.editor, myFixture.file)
}
@@ -68,11 +68,15 @@
inputFileContent: String,
expectedResult: String
) {
- invokeActionAndAssertResult({
- val action = myFixture.availableIntentions.find { it.text == actionName }
- assertThat(action).isNotNull()
- action!!
- }, inputFileContent, expectedResult)
+ invokeActionAndAssertResult(
+ {
+ val action = myFixture.availableIntentions.find { it.text == actionName }
+ assertThat(action).isNotNull()
+ action!!
+ },
+ inputFileContent,
+ expectedResult
+ )
}
private fun invokeActionAndAssertResult(
@@ -102,7 +106,8 @@
</selection><caret>
}
- """.trimIndent(),
+ """
+ .trimIndent(),
// language=kotlin
"""
package com.example
@@ -122,7 +127,8 @@
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -141,7 +147,8 @@
Text("Davenport, California")
Text("December 2018")
}
- """.trimIndent(),
+ """
+ .trimIndent(),
// language=kotlin
"""
package com.example
@@ -157,7 +164,9 @@
Text("Davenport, California")
Text("December 2018")
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
invokeActionAndAssertResult(
"Surround with widget",
@@ -173,7 +182,8 @@
<caret>Text("Davenport, California")
Text("December 2018")
}
- """.trimIndent(),
+ """
+ .trimIndent(),
// language=kotlin
"""
package com.example
@@ -189,7 +199,9 @@
}
Text("December 2018")
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
invokeActionAndAssertResult(
"Surround with widget",
@@ -205,7 +217,8 @@
Text("Davenport, California")
Text("December 2018")<caret>
}
- """.trimIndent(),
+ """
+ .trimIndent(),
// language=kotlin
"""
package com.example
@@ -221,7 +234,9 @@
Text("December 2018")
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
invokeActionAndAssertResult(
"Surround with widget",
@@ -237,7 +252,8 @@
Text("Davenport, California")
}
}
- """.trimIndent(),
+ """
+ .trimIndent(),
// language=kotlin
"""
package com.example
@@ -253,16 +269,17 @@
}
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
- /**
- * Checks the cases where the intention should not be available.
- */
+ /** Checks the cases where the intention should not be available. */
fun testSurroundWithWidgetWithoutSelectionNotAvailable() {
- val cases = listOf(
- // language=kotlin
- """
+ val cases =
+ listOf(
+ // language=kotlin
+ """
package com.example
import androidx.compose.runtime.Composable
@@ -276,9 +293,10 @@
Text("December 2018")
}
}
- """.trimIndent(),
- // language=kotlin
- """
+ """
+ .trimIndent(),
+ // language=kotlin
+ """
package com.example
import $COMPOSABLE_ANNOTATION_FQ_NAME
@@ -292,9 +310,10 @@
Text("December 2018")
}
}
- """.trimIndent(),
- // language=kotlin
- """
+ """
+ .trimIndent(),
+ // language=kotlin
+ """
package com.example
import $COMPOSABLE_ANNOTATION_FQ_NAME
@@ -310,9 +329,10 @@
Text("December 2018")
}
}
- """.trimIndent(),
- // language=kotlin
- """
+ """
+ .trimIndent(),
+ // language=kotlin
+ """
package com.example
import $COMPOSABLE_ANNOTATION_FQ_NAME
@@ -322,8 +342,9 @@
fun NewsStory() {<caret>
Text("A day in Shark Fin Cove")
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
cases.forEachIndexed { index, content ->
myFixture.loadNewFile("src/com/example/Test${index}.kt", content)
@@ -333,8 +354,8 @@
}
/**
- * Checks surround with widget when the selection starts and/or stops in the middle or an element and not
- * in empty space.
+ * Checks surround with widget when the selection starts and/or stops in the middle or an element
+ * and not in empty space.
*/
fun testSurroundWithWidgetWithPartialSelection() {
invokeActionAndAssertResult(
@@ -351,7 +372,8 @@
Text("Davenport, Cali</selection>fornia")
Text("December 2018")
}
- """.trimIndent(),
+ """
+ .trimIndent(),
// language=kotlin
"""
package com.example
@@ -367,7 +389,9 @@
}
Text("December 2018")
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
invokeActionAndAssertResult(
"Surround with widget",
@@ -385,7 +409,8 @@
// A comment
</selection>
}
- """.trimIndent(),
+ """
+ .trimIndent(),
// language=kotlin
"""
package com.example
@@ -403,7 +428,9 @@
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
invokeActionAndAssertResult(
"Surround with widget",
@@ -423,7 +450,8 @@
Text("December 2018")</selection>
}
}
- """.trimIndent(),
+ """
+ .trimIndent(),
// language=kotlin
"""
package com.example
@@ -442,7 +470,8 @@
}
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
invokeActionAndAssertResult(
@@ -464,7 +493,8 @@
Text("December 2018")
}</selection>
}
- """.trimIndent(),
+ """
+ .trimIndent(),
// language=kotlin
"""
package com.example
@@ -484,17 +514,17 @@
}
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
- /**
- * Checks the cases where the intention should not be available.
- */
+ /** Checks the cases where the intention should not be available. */
fun testSurroundWithWidgetWithPartialSelectionNotAvailable() {
- val cases = listOf(
- // language=kotlin
- """
+ val cases =
+ listOf(
+ // language=kotlin
+ """
package com.example
import $COMPOSABLE_ANNOTATION_FQ_NAME
@@ -508,9 +538,10 @@
Text("December 2018")
}
}
- """.trimIndent(),
- // language=kotlin
- """
+ """
+ .trimIndent(),
+ // language=kotlin
+ """
package com.example
import $COMPOSABLE_ANNOTATION_FQ_NAME
@@ -533,9 +564,10 @@
Text("December 2018")
}
}
- """.trimIndent(),
- // language=kotlin
- """
+ """
+ .trimIndent(),
+ // language=kotlin
+ """
package com.example
import $COMPOSABLE_ANNOTATION_FQ_NAME
@@ -560,9 +592,10 @@
}
}
</selection>
- """.trimIndent(),
- // language=kotlin
- """
+ """
+ .trimIndent(),
+ // language=kotlin
+ """
package com.example
import $COMPOSABLE_ANNOTATION_FQ_NAME
@@ -578,8 +611,9 @@
Text("December 2018")
}
}
- """.trimIndent()
- )
+ """
+ .trimIndent()
+ )
cases.forEachIndexed { index, content ->
myFixture.loadNewFile("src/com/example/Test${index}.kt", content)
@@ -604,7 +638,8 @@
Text("Davenport, California")
Text("December 2018")</selection><caret>
}
- """.trimIndent(),
+ """
+ .trimIndent(),
// language=kotlin
"""
package com.example
@@ -620,7 +655,8 @@
Text("December 2018")
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -639,7 +675,8 @@
Text("Davenport, California")
Text("December 2018")</selection><caret>
}
- """.trimIndent(),
+ """
+ .trimIndent(),
// language=kotlin
"""
package com.example
@@ -655,7 +692,8 @@
Text("December 2018")
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -674,7 +712,8 @@
Text("Davenport, California")
Text("December 2018")</selection><caret>
}
- """.trimIndent(),
+ """
+ .trimIndent(),
// language=kotlin
"""
package com.example
@@ -690,7 +729,8 @@
Text("December 2018")
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeUnresolvedFunctionFixContributorTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeUnresolvedFunctionFixContributorTest.kt
index 75c5f5a..49bbe45 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeUnresolvedFunctionFixContributorTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeUnresolvedFunctionFixContributorTest.kt
@@ -32,8 +32,7 @@
@RunWith(JUnit4::class)
class ComposeUnresolvedFunctionFixContributorTest {
- @get:Rule
- val projectRule = AndroidProjectRule.onDisk()
+ @get:Rule val projectRule = AndroidProjectRule.onDisk()
private lateinit var myFixture: CodeInsightTestFixture
@@ -57,10 +56,14 @@
fun NewsStory() {
<caret>UnresolvedFunction()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val action = myFixture.availableIntentions.find { it.text == "Create @Composable function 'UnresolvedFunction'" }
+ val action =
+ myFixture.availableIntentions.find {
+ it.text == "Create @Composable function 'UnresolvedFunction'"
+ }
assertThat(action).isNotNull()
WriteCommandAction.runWriteCommandAction(myFixture.project) {
@@ -68,7 +71,8 @@
}
// language=kotlin
- val expectedText = """
+ val expectedText =
+ """
package com.example
import $COMPOSABLE_ANNOTATION_FQ_NAME
@@ -104,10 +108,14 @@
fun NewsStory() {
<caret>UnresolvedFunction<Int>(45, f = { 43 }, "OK".length)
}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val action = myFixture.availableIntentions.find { it.text == "Create @Composable function 'UnresolvedFunction'" }
+ val action =
+ myFixture.availableIntentions.find {
+ it.text == "Create @Composable function 'UnresolvedFunction'"
+ }
assertThat(action).isNotNull()
WriteCommandAction.runWriteCommandAction(myFixture.project) {
@@ -132,7 +140,8 @@
fun <T0> UnresolvedFunction(x0: Int, f: () -> Int, x2: Int) {
TODO("Not yet implemented")
}
- """.trimIndent()
+ """
+ .trimIndent()
)
} else {
myFixture.checkResult(
@@ -152,7 +161,8 @@
TODO("Not yet implemented")
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
}
@@ -171,10 +181,14 @@
fun NewsStory() {
val k:Int = <caret>unresolvedFunction()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val action = myFixture.availableIntentions.find { it.text == "Create @Composable function 'unresolvedFunction'" }
+ val action =
+ myFixture.availableIntentions.find {
+ it.text == "Create @Composable function 'unresolvedFunction'"
+ }
assertThat(action).isNull()
}
@@ -192,10 +206,14 @@
fun NewsStory() {
<caret>unresolvedFunction()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val action = myFixture.availableIntentions.find { it.text == "Create @Composable function 'unresolvedFunction'" }
+ val action =
+ myFixture.availableIntentions.find {
+ it.text == "Create @Composable function 'unresolvedFunction'"
+ }
assertThat(action).isNull()
}
@@ -213,10 +231,14 @@
fun NewsStory() {
<caret>UnresolvedFunction {}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val action = myFixture.availableIntentions.find { it.text == "Create @Composable function 'UnresolvedFunction'" }
+ val action =
+ myFixture.availableIntentions.find {
+ it.text == "Create @Composable function 'UnresolvedFunction'"
+ }
assertThat(action).isNotNull()
WriteCommandAction.runWriteCommandAction(myFixture.project) {
@@ -241,7 +263,8 @@
fun UnresolvedFunction(x0: () -> Unit) {
TODO("Not yet implemented")
}
- """.trimIndent()
+ """
+ .trimIndent()
)
} else {
myFixture.checkResult(
@@ -261,7 +284,8 @@
TODO("Not yet implemented")
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeUnwrapActionTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeUnwrapActionTest.kt
index 8f5b7e1..c4fbc8d 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeUnwrapActionTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeUnwrapActionTest.kt
@@ -15,15 +15,12 @@
*/
package com.android.tools.compose.intentions
-
import com.google.common.truth.Truth
import com.intellij.openapi.command.WriteCommandAction
import org.jetbrains.android.JavaCodeInsightFixtureAdtTestCase
import org.jetbrains.android.compose.stubComposableAnnotation
-/**
- * Test for [ComposeUnwrapAction].
- */
+/** Test for [ComposeUnwrapAction]. */
internal class ComposeUnwrapActionTest : JavaCodeInsightFixtureAdtTestCase() {
public override fun setUp() {
@@ -41,7 +38,8 @@
inline fun Row(content: @Composable () -> Unit) {}
inline fun Column(content: @Composable () -> Unit) {}
inline fun Box(content: @Composable () -> Unit) {}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -63,7 +61,8 @@
Text("December 2018")
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
val action = myFixture.availableIntentions.find { it.text == "Remove wrapper" }
@@ -87,7 +86,8 @@
Text("Davenport, California")
Text("December 2018")
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeWrapModifiersActionTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeWrapModifiersActionTest.kt
index 5182352..44f2cbb1 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeWrapModifiersActionTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/intentions/ComposeWrapModifiersActionTest.kt
@@ -25,9 +25,7 @@
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
-/**
- * Test for [ComposeWrapModifiersAction].
- */
+/** Test for [ComposeWrapModifiersAction]. */
class ComposeWrapModifiersActionTest : JavaCodeInsightFixtureAdtTestCase() {
override fun setUp() {
super.setUp()
@@ -46,10 +44,12 @@
}
fun Modifier.extentionFunction():Modifier { return this}
- """.trimIndent()
+ """
+ .trimIndent()
)
- val settings = CodeStyle.getSettings(project).getCustomSettings(KotlinCodeStyleSettings::class.java)
+ val settings =
+ CodeStyle.getSettings(project).getCustomSettings(KotlinCodeStyleSettings::class.java)
settings.CONTINUATION_INDENT_FOR_CHAINED_CALLS = false
}
@@ -68,7 +68,8 @@
val m2 = Modifier.adjust().adjust()
val m3 = Modifier.adjust().adjust()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
var action = myFixture.availableIntentions.find { it.text == "Wrap modifiers" }
@@ -92,7 +93,8 @@
val m2 = Modifier.adjust().adjust()
val m3 = Modifier.adjust().adjust()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.moveCaret("val m2 = Modifier.adj|ust().adjust()")
@@ -109,7 +111,6 @@
action!!.invoke(project, myFixture.editor, myFixture.file)
}
-
myFixture.checkResult(
"""
package com.example
@@ -129,7 +130,8 @@
.adjust()
.adjust()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -148,10 +150,11 @@
.ad<caret>just()
.adjust()
}
- """.trimIndent()
+ """
+ .trimIndent()
)
val action = myFixture.availableIntentions.find { it.text == "Wrap modifiers" }
Truth.assertThat(action).isNull()
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/compose/templates/AndroidComposeTest.kt b/compose-ide-plugin/testSrc/com/android/tools/compose/templates/AndroidComposeTest.kt
index 478d207..1dcd8d6 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/compose/templates/AndroidComposeTest.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/compose/templates/AndroidComposeTest.kt
@@ -36,7 +36,8 @@
class Row
class Column
class Box
- """.trimIndent()
+ """
+ .trimIndent()
)
LiveTemplateCompletionContributor.setShowTemplatesInTests(true, myFixture.testRootDisposable)
TemplateManagerImpl.setTemplateTesting(myFixture.testRootDisposable)
@@ -55,7 +56,8 @@
fun NewsStory() {
W<caret>
}
- """.trimIndent()
+ """
+ .trimIndent()
)
myFixture.type("\t")
@@ -73,7 +75,8 @@
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -92,7 +95,8 @@
Text("Davenport, California")
Text("December 2018")</selection><caret>
}
- """.trimIndent()
+ """
+ .trimIndent()
)
val template = TemplateSettings.getInstance().getTemplate("WR", "AndroidCompose")
@@ -113,7 +117,8 @@
Text("December 2018")
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -132,7 +137,8 @@
Text("Davenport, California")
Text("December 2018")</selection><caret>
}
- """.trimIndent()
+ """
+ .trimIndent()
)
val template = TemplateSettings.getInstance().getTemplate("WC", "AndroidCompose")
@@ -153,7 +159,8 @@
Text("December 2018")
}
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -173,7 +180,8 @@
"A day in Shark Fin Cove")
Text("Davenport, California")
}
- """.trimIndent()
+ """
+ .trimIndent()
)
val template = TemplateSettings.getInstance().getTemplate("paddp", "AndroidCompose")
@@ -193,7 +201,8 @@
"A day in Shark Fin Cove")
Text("Davenport, California")
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
@@ -213,7 +222,8 @@
"A day in Shark Fin Cove")
Text("Davenport, California")
}
- """.trimIndent()
+ """
+ .trimIndent()
)
val template = TemplateSettings.getInstance().getTemplate("weight", "AndroidCompose")
@@ -233,7 +243,8 @@
"A day in Shark Fin Cove")
Text("Davenport, California")
}
- """.trimIndent()
+ """
+ .trimIndent()
)
}
-}
\ No newline at end of file
+}
diff --git a/compose-ide-plugin/testSrc/com/android/tools/tests/ComposeTestSuite.kt b/compose-ide-plugin/testSrc/com/android/tools/tests/ComposeTestSuite.kt
index ef59597..90706c8 100644
--- a/compose-ide-plugin/testSrc/com/android/tools/tests/ComposeTestSuite.kt
+++ b/compose-ide-plugin/testSrc/com/android/tools/tests/ComposeTestSuite.kt
@@ -23,8 +23,12 @@
companion object {
init {
unzipIntoOfflineMavenRepo("tools/base/build-system/android_gradle_plugin.zip")
- linkIntoOfflineMavenRepo("tools/base/build-system/android_gradle_plugin_runtime_dependencies.manifest")
- linkIntoOfflineMavenRepo("tools/base/build-system/integration-test/kotlin_gradle_plugin_prebuilts.manifest")
+ linkIntoOfflineMavenRepo(
+ "tools/base/build-system/android_gradle_plugin_runtime_dependencies.manifest"
+ )
+ linkIntoOfflineMavenRepo(
+ "tools/base/build-system/integration-test/kotlin_gradle_plugin_prebuilts.manifest"
+ )
}
}
-}
\ No newline at end of file
+}
diff --git a/connection-assistant/src/com/android/tools/idea/connection/assistant/OpenConnectionAssistantSidePanelAction.java b/connection-assistant/src/com/android/tools/idea/connection/assistant/OpenConnectionAssistantSidePanelAction.java
index 05662e0..21cd7b1 100644
--- a/connection-assistant/src/com/android/tools/idea/connection/assistant/OpenConnectionAssistantSidePanelAction.java
+++ b/connection-assistant/src/com/android/tools/idea/connection/assistant/OpenConnectionAssistantSidePanelAction.java
@@ -19,6 +19,7 @@
import com.android.tools.idea.assistant.OpenAssistSidePanelAction;
import com.google.wireless.android.sdk.stats.AndroidStudioEvent;
import com.google.wireless.android.sdk.stats.ConnectionAssistantEvent;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import org.jetbrains.annotations.NotNull;
@@ -29,6 +30,11 @@
}
@Override
+ public @NotNull ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
+ @Override
public void actionPerformed(@NotNull AnActionEvent event) {
super.actionPerformed(event);
diff --git a/databinding/src/com/android/tools/idea/databinding/analytics/LayoutBindingTracker.kt b/databinding/src/com/android/tools/idea/databinding/analytics/LayoutBindingTracker.kt
index f0ce57d..cd6cd81 100644
--- a/databinding/src/com/android/tools/idea/databinding/analytics/LayoutBindingTracker.kt
+++ b/databinding/src/com/android/tools/idea/databinding/analytics/LayoutBindingTracker.kt
@@ -95,7 +95,6 @@
FileTypeIndex
.getFiles(XmlFileType.INSTANCE, GlobalSearchScope.projectScope(project))
- .filter { BindingXmlIndex.acceptsFile(it) }
.mapNotNull { BindingXmlIndex.getDataForFile(project, it) }
.forEach { layoutInfo ->
if (layoutInfo.layoutType == DATA_BINDING_LAYOUT) {
diff --git a/databinding/testData/projects/projectWithSamePackageModules/lib1/src/main/res/layout/activity_lib.xml b/databinding/testData/projects/projectWithSamePackageModules/lib1/src/main/res/layout/activity_lib.xml
index dc8b103..c4e7ac8 100644
--- a/databinding/testData/projects/projectWithSamePackageModules/lib1/src/main/res/layout/activity_lib.xml
+++ b/databinding/testData/projects/projectWithSamePackageModules/lib1/src/main/res/layout/activity_lib.xml
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout />
\ No newline at end of file
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" />
\ No newline at end of file
diff --git a/databinding/testData/projects/projectWithSamePackageModules/lib2/src/main/res/layout/activity_lib.xml b/databinding/testData/projects/projectWithSamePackageModules/lib2/src/main/res/layout/activity_lib.xml
index dc8b103..c4e7ac8 100644
--- a/databinding/testData/projects/projectWithSamePackageModules/lib2/src/main/res/layout/activity_lib.xml
+++ b/databinding/testData/projects/projectWithSamePackageModules/lib2/src/main/res/layout/activity_lib.xml
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout />
\ No newline at end of file
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" />
\ No newline at end of file
diff --git a/databinding/testSrc/com/android/tools/idea/databinding/DataBindingNavigationTests.kt b/databinding/testSrc/com/android/tools/idea/databinding/DataBindingNavigationTests.kt
index d11a3b2..857b067 100644
--- a/databinding/testSrc/com/android/tools/idea/databinding/DataBindingNavigationTests.kt
+++ b/databinding/testSrc/com/android/tools/idea/databinding/DataBindingNavigationTests.kt
@@ -150,7 +150,7 @@
// language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
- <layout>
+ <layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type='java.util.Map' alias='MyMap'/>
<variable name='sample' type='MyMap.En${caret}try'/>
@@ -169,7 +169,7 @@
// language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
- <layout>
+ <layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type='java.util.M${caret}ap' />
</data>
@@ -187,7 +187,7 @@
// language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
- <layout>
+ <layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name='sample' type='Int${caret}eger'/>
</data>
@@ -212,7 +212,7 @@
// language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
- <layout>
+ <layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name='sample' type='a.b.c.Samp${caret}le'/>
</data>
diff --git a/databinding/testSrc/com/android/tools/idea/databinding/viewbinding/LightViewBindingClassTest.kt b/databinding/testSrc/com/android/tools/idea/databinding/viewbinding/LightViewBindingClassTest.kt
index d02118a..42d8db5 100644
--- a/databinding/testSrc/com/android/tools/idea/databinding/viewbinding/LightViewBindingClassTest.kt
+++ b/databinding/testSrc/com/android/tools/idea/databinding/viewbinding/LightViewBindingClassTest.kt
@@ -19,7 +19,6 @@
import com.android.tools.idea.databinding.DataBindingMode
import com.android.tools.idea.databinding.module.LayoutBindingModuleCache
import com.android.tools.idea.databinding.psiclass.LightBindingClass
-import com.android.tools.idea.databinding.util.isViewBindingEnabled
import com.android.tools.idea.databinding.utils.assertExpected
import com.android.tools.idea.gradle.model.impl.IdeViewBindingOptionsImpl
import com.android.tools.idea.testing.AndroidProjectBuilder
@@ -34,7 +33,6 @@
import com.intellij.testFramework.RunsInEdt
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import org.jetbrains.android.facet.AndroidFacet
-import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
@@ -351,7 +349,7 @@
fun methodsAreAnnotatedNonNullAndNullableCorrectly_regularLayouts() {
fixture.addFileToProject("src/main/res/layout/activity_main.xml", """
<?xml version="1.0" encoding="utf-8"?>
- <LinearLayout />
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" />
""".trimIndent())
val project = fixture.project
@@ -386,7 +384,7 @@
fun methodsAreAnnotatedNonNullAndNullableCorrectly_mergeLayouts() {
fixture.addFileToProject("src/main/res/layout/activity_main.xml", """
<?xml version="1.0" encoding="utf-8"?>
- <merge />
+ <merge xmlns:android="http://schemas.android.com/apk/res/android" />
""".trimIndent())
val project = fixture.project
diff --git a/deploy/src/com/android/tools/idea/run/ui/DeployAction.java b/deploy/src/com/android/tools/idea/run/ui/DeployAction.java
index 8da8079..0c7a9af 100644
--- a/deploy/src/com/android/tools/idea/run/ui/DeployAction.java
+++ b/deploy/src/com/android/tools/idea/run/ui/DeployAction.java
@@ -29,6 +29,7 @@
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.runners.ExecutionEnvironmentBuilder;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
@@ -51,6 +52,12 @@
myEnabledDescription = description;
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
Presentation presentation = e.getPresentation();
diff --git a/designer/lint_baseline.xml b/designer/lint_baseline.xml
index 364f27c..6e8b8c7 100644
--- a/designer/lint_baseline.xml
+++ b/designer/lint_baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 8.2.0-dev">
+<issues format="5" by="lint 8.3.0-dev">
<issue
id="DefaultLocale"
@@ -30,7 +30,7 @@
message="Avoid `by lazy` for simple lazy initialization">
<location
file="src/com/android/tools/idea/common/scene/target/CommonDragTarget.kt"
- line="304"/>
+ line="302"/>
</issue>
<issue
@@ -38,7 +38,7 @@
message="Avoid `by lazy` for simple lazy initialization">
<location
file="src/com/android/tools/idea/common/scene/target/CommonDragTarget.kt"
- line="305"/>
+ line="303"/>
</issue>
<issue
@@ -118,7 +118,7 @@
message="Avoid `by lazy` for simple lazy initialization">
<location
file="src/com/android/tools/idea/uibuilder/visual/VisualizationToolWindowFactory.kt"
- line="171"/>
+ line="173"/>
</issue>
<issue
@@ -398,7 +398,7 @@
message="Do not store `JBUI.scale` scaled results in fields; this will not work correctly on dynamic theme or font size changes">
<location
file="src/com/android/tools/idea/uibuilder/handlers/constraint/WidgetConstraintPanel.java"
- line="277"/>
+ line="282"/>
</issue>
<issue
@@ -406,7 +406,7 @@
message="Do not store `JBUI.scale` scaled results in fields; this will not work correctly on dynamic theme or font size changes">
<location
file="src/com/android/tools/idea/uibuilder/handlers/constraint/WidgetConstraintPanel.java"
- line="278"/>
+ line="283"/>
</issue>
<issue
@@ -414,7 +414,15 @@
message="Do not store `JBUI.scale` scaled results in fields; this will not work correctly on dynamic theme or font size changes">
<location
file="src/com/android/tools/idea/uibuilder/handlers/constraint/WidgetConstraintPanel.java"
- line="279"/>
+ line="284"/>
+ </issue>
+
+ <issue
+ id="VisibleForTests"
+ message="This class should only be accessed from tests or within private scope">
+ <location
+ file="src/com/android/tools/idea/common/analytics/CommonUsageTracker.kt"
+ line="84"/>
</issue>
<issue
@@ -422,7 +430,7 @@
message="This method should only be accessed from tests or within package private scope">
<location
file="src/com/android/tools/idea/uibuilder/editor/DesignFilesPreviewEditor.kt"
- line="109"/>
+ line="108"/>
</issue>
<issue
@@ -430,7 +438,7 @@
message="This method should only be accessed from tests or within package private scope">
<location
file="src/com/android/tools/idea/uibuilder/editor/DesignFilesPreviewEditor.kt"
- line="109"/>
+ line="108"/>
</issue>
<issue
@@ -438,7 +446,7 @@
message="This method should only be accessed from tests or within package private scope">
<location
file="src/com/android/tools/idea/uibuilder/editor/DesignFilesPreviewEditor.kt"
- line="112"/>
+ line="111"/>
</issue>
<issue
@@ -446,7 +454,7 @@
message="This method should only be accessed from tests or within package private scope">
<location
file="src/com/android/tools/idea/uibuilder/editor/DesignFilesPreviewEditor.kt"
- line="112"/>
+ line="111"/>
</issue>
<issue
@@ -454,7 +462,7 @@
message="This method should only be accessed from tests or within protected scope">
<location
file="src/com/android/tools/idea/common/surface/notifications/DesignSurfaceNotificationProvider.kt"
- line="40"/>
+ line="39"/>
</issue>
<issue
@@ -462,7 +470,7 @@
message="This method should only be accessed from tests or within protected scope">
<location
file="src/com/android/tools/idea/common/surface/notifications/DesignSurfaceNotificationProvider.kt"
- line="40"/>
+ line="39"/>
</issue>
<issue
@@ -526,7 +534,7 @@
message="This method should only be accessed from tests or within protected scope">
<location
file="src/com/android/tools/idea/uibuilder/surface/NlDesignSurface.java"
- line="724"/>
+ line="725"/>
</issue>
<issue
@@ -550,7 +558,7 @@
message="This method should only be accessed from tests or within protected scope">
<location
file="src/com/android/tools/idea/uibuilder/visual/VisualizationForm.kt"
- line="613"/>
+ line="580"/>
</issue>
<issue
@@ -558,7 +566,7 @@
message="This method should only be accessed from tests or within protected scope">
<location
file="src/com/android/tools/idea/uibuilder/visual/VisualizationForm.kt"
- line="613"/>
+ line="580"/>
</issue>
<issue
diff --git a/designer/resources/layout_editor_help_assistance_bundle.xml b/designer/resources/layout_editor_help_assistance_bundle.xml
index fc51ec2..41a37cc 100644
--- a/designer/resources/layout_editor_help_assistance_bundle.xml
+++ b/designer/resources/layout_editor_help_assistance_bundle.xml
@@ -114,23 +114,23 @@
</td>
<td>Ctrl –
</td>
- <td>Command–
+ <td>Command –
</td>
</tr>
<tr>
<td>Zoom to 100%
</td>
- <td>Ctrl /
+ <td>Ctrl .
</td>
- <td>Command /
+ <td>Command .
</td>
</tr>
<tr>
<td>Zoom to fit
</td>
- <td>Ctrl 0
+ <td>Ctrl /
</td>
- <td>Command 0
+ <td>Command /
</td>
</tr>
<tr>
@@ -228,17 +228,17 @@
<tr>
<td>Zoom to 100%
</td>
- <td>Ctrl /
+ <td>Ctrl .
</td>
- <td>Command /
+ <td>Command .
</td>
</tr>
<tr>
<td>Zoom to fit
</td>
- <td>Ctrl 0
+ <td>Ctrl /
</td>
- <td>Command 0
+ <td>Command /
</td>
</tr>
<tr>
diff --git a/designer/src/com/android/tools/idea/common/actions/RestoreDefaultWindowLayoutAction.java b/designer/src/com/android/tools/idea/common/actions/RestoreDefaultWindowLayoutAction.java
index 63f2b21..f8e2a44 100644
--- a/designer/src/com/android/tools/idea/common/actions/RestoreDefaultWindowLayoutAction.java
+++ b/designer/src/com/android/tools/idea/common/actions/RestoreDefaultWindowLayoutAction.java
@@ -18,6 +18,7 @@
import com.android.tools.adtui.workbench.DetachedToolWindowManager;
import com.android.tools.adtui.workbench.WorkBenchManager;
import com.intellij.ide.actions.RestoreDefaultLayoutAction;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.DumbAware;
@@ -50,6 +51,12 @@
floatingToolWindowManager.restoreDefaultLayout();
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@Override
public void update(@NotNull AnActionEvent event){
myDelegate.update(event);
diff --git a/designer/src/com/android/tools/idea/common/actions/StoreDefaultWindowLayoutAction.java b/designer/src/com/android/tools/idea/common/actions/StoreDefaultWindowLayoutAction.java
index 746a2c6..caf69b8 100644
--- a/designer/src/com/android/tools/idea/common/actions/StoreDefaultWindowLayoutAction.java
+++ b/designer/src/com/android/tools/idea/common/actions/StoreDefaultWindowLayoutAction.java
@@ -17,6 +17,7 @@
import com.android.tools.adtui.workbench.WorkBenchManager;
import com.intellij.ide.actions.StoreDefaultLayoutAction;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.DumbAware;
@@ -41,6 +42,12 @@
workBenchManager.storeDefaultLayout();
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@Override
public void update(@NotNull AnActionEvent event){
myDelegate.update(event);
diff --git a/designer/src/com/android/tools/idea/common/editor/ActionManager.java b/designer/src/com/android/tools/idea/common/editor/ActionManager.java
index 3c5e17d..b47a6f2 100644
--- a/designer/src/com/android/tools/idea/common/editor/ActionManager.java
+++ b/designer/src/com/android/tools/idea/common/editor/ActionManager.java
@@ -18,6 +18,8 @@
import com.android.tools.adtui.stdui.KeyBindingKt;
import com.android.tools.idea.common.model.NlComponent;
import com.android.tools.idea.common.surface.DesignSurface;
+import com.android.tools.idea.common.surface.LabelPanel;
+import com.android.tools.idea.common.surface.LayoutData;
import com.android.tools.idea.common.surface.SceneView;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
@@ -127,6 +129,14 @@
}
/**
+ * Creates a {@link LabelPanel} with a label for a {@link SceneView}.
+ */
+ @NotNull
+ public LabelPanel createSceneViewLabel(@NotNull SceneView sceneView) {
+ return new LabelPanel(LayoutData.Companion.fromSceneView(sceneView));
+ }
+
+ /**
* Returns the bottom bar for a {@link SceneView}. This is similar to {@link #getSceneViewContextToolbar(SceneView)} but this bar is at
* the bottom of the {@link SceneView} while context toolbar is at the top.
*/
diff --git a/designer/src/com/android/tools/idea/common/error/CopyIssueDescriptionAction.kt b/designer/src/com/android/tools/idea/common/error/CopyIssueDescriptionAction.kt
index a27b87d..069decd 100644
--- a/designer/src/com/android/tools/idea/common/error/CopyIssueDescriptionAction.kt
+++ b/designer/src/com/android/tools/idea/common/error/CopyIssueDescriptionAction.kt
@@ -15,6 +15,7 @@
*/
package com.android.tools.idea.common.error
+import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
import com.intellij.openapi.ide.CopyPasteManager
@@ -28,6 +29,8 @@
event.presentation.isEnabledAndVisible = description != null
}
+ override fun getActionUpdateThread() = ActionUpdateThread.EDT
+
override fun actionPerformed(event: AnActionEvent) {
val description = getSelectedNode(event)?.getDescription() ?: return
CopyPasteManager.getInstance().setContents(StringSelection(description))
diff --git a/designer/src/com/android/tools/idea/common/error/DesignerCommonIssuePanel.kt b/designer/src/com/android/tools/idea/common/error/DesignerCommonIssuePanel.kt
index 1d76e5e..87ef985 100644
--- a/designer/src/com/android/tools/idea/common/error/DesignerCommonIssuePanel.kt
+++ b/designer/src/com/android/tools/idea/common/error/DesignerCommonIssuePanel.kt
@@ -63,14 +63,20 @@
private const val POPUP_HANDLER_ACTION_ID = "Android.Designer.IssuePanel.TreePopup"
private val KEY_DETAIL_VISIBLE = DesignerCommonIssuePanel::class.java.name + "_detail_visibility"
-/** The issue panel to load the issues from Layout Editor and Layout Validation Tool. */
+/**
+ * The issue panel to load the issues from Layout Editor and Layout Validation Tool.
+ *
+ * @param additionalDataProvider A [DataProvider] used to pass information from the creator of this
+ * panel, that will allow to link the panel with the previews from which it displays the errors.
+ */
class DesignerCommonIssuePanel(
parentDisposable: Disposable,
private val project: Project,
private val treeModel: DesignerCommonIssueModel,
nodeFactoryProvider: () -> NodeFactory,
val issueProvider: DesignerCommonIssueProvider<Any>,
- private val emptyMessageProvider: () -> String
+ private val emptyMessageProvider: () -> String,
+ private val additionalDataProvider: DataProvider? = null
) : Disposable {
var sidePanelVisible =
@@ -90,7 +96,7 @@
}
override fun getData(dataId: String): Any? {
- val node = getSelectedNode() ?: return null
+ val node = getSelectedNode() ?: return additionalDataProvider?.getData(dataId)
if (PlatformCoreDataKeys.BGT_DATA_PROVIDER.`is`(dataId)) {
return DataProvider { getDataInBackground(it, node) }
}
@@ -109,7 +115,7 @@
else -> emptyList()
}
}
- return null
+ return additionalDataProvider?.getData(dataId)
}
}
diff --git a/designer/src/com/android/tools/idea/common/error/IssuePanelService.kt b/designer/src/com/android/tools/idea/common/error/IssuePanelService.kt
index 427eac4..0537924 100644
--- a/designer/src/com/android/tools/idea/common/error/IssuePanelService.kt
+++ b/designer/src/com/android/tools/idea/common/error/IssuePanelService.kt
@@ -35,6 +35,7 @@
import com.intellij.ide.DataManager
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.DataKey
+import com.intellij.openapi.actionSystem.DataProvider
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.components.Service
@@ -547,7 +548,9 @@
parentDisposable: Disposable,
name: String,
displayName: String,
- surface: NlDesignSurface
+ surface: NlDesignSurface,
+ postIssueUpdateListener: () -> Unit,
+ additionalDataProvider: DataProvider
) {
val contentManager =
ToolWindowManager.getInstance(project).getToolWindow(ProblemsView.ID)?.contentManager
@@ -555,16 +558,19 @@
var uiCheckIssuePanel = nameToTabMap[name]?.first
if (uiCheckIssuePanel == null) {
+ val issueProvider =
+ DesignToolsIssueProvider(parentDisposable, project, NotSuppressedFilter, name)
uiCheckIssuePanel =
DesignerCommonIssuePanel(
parentDisposable,
project,
DesignerCommonIssuePanelModelProvider.getInstance(project).createModel(),
{ UICheckNodeFactory },
- DesignToolsIssueProvider(parentDisposable, project, NotSuppressedFilter, name)
- ) {
- "UI Check did not find any issues to report"
- }
+ issueProvider,
+ { "UI Check did not find any issues to report" },
+ additionalDataProvider
+ )
+ issueProvider.registerUpdateListener(postIssueUpdateListener)
val tab =
contentManager.factory
@@ -659,7 +665,7 @@
* @param show whether to show or hide the issue panel.
* @param runnable optional task to execute after the visibility of issue panel is changed.
*
- * TODO(b/298229332): Revisit this function to see if we can remove the DesignSurface dependency.
+ * TODO(b/300646581): Revisit this function to see if we can remove the DesignSurface dependency.
*/
fun DesignSurface<*>.setIssuePanelVisibility(show: Boolean, runnable: Runnable? = null) {
analyticsManager.trackShowIssuePanel()
diff --git a/designer/src/com/android/tools/idea/common/surface/InteractiveLabelPanel.kt b/designer/src/com/android/tools/idea/common/surface/InteractiveLabelPanel.kt
new file mode 100644
index 0000000..bb5e01f
--- /dev/null
+++ b/designer/src/com/android/tools/idea/common/surface/InteractiveLabelPanel.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.common.surface
+
+import com.android.tools.idea.concurrency.AndroidCoroutineScope
+import com.intellij.openapi.Disposable
+import com.intellij.ui.JBColor
+import java.awt.event.MouseAdapter
+import java.awt.event.MouseEvent
+import kotlinx.coroutines.launch
+
+/** This label displays the [SceneView] model label. */
+class InteractiveLabelPanel(
+ layoutData: LayoutData,
+ disposable: Disposable,
+ private val onLabelClicked: (suspend () -> Boolean)
+) : LabelPanel(layoutData) {
+
+ private val scope = AndroidCoroutineScope(disposable)
+
+ init {
+ addMouseListener(
+ object : MouseAdapter() {
+ override fun mouseEntered(e: MouseEvent?) {
+ foreground = labelHoverColor
+ }
+
+ override fun mouseExited(e: MouseEvent?) {
+ foreground = labelDefaultColor
+ }
+
+ override fun mouseClicked(e: MouseEvent?) {
+ scope.launch { onLabelClicked() }
+ }
+ },
+ )
+ }
+
+ companion object {
+ val labelHoverColor = JBColor(0x5a5d6b, 0xf0f1f2)
+ }
+}
diff --git a/designer/src/com/android/tools/idea/common/surface/LabelPanel.kt b/designer/src/com/android/tools/idea/common/surface/LabelPanel.kt
new file mode 100644
index 0000000..c920fb0
--- /dev/null
+++ b/designer/src/com/android/tools/idea/common/surface/LabelPanel.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.common.surface
+
+import com.intellij.ui.JBColor
+import com.intellij.ui.components.JBLabel
+import java.awt.Dimension
+
+/** This label displays the [SceneView] model label. */
+open class LabelPanel(var layoutData: LayoutData) : JBLabel() {
+ init {
+ maximumSize = Dimension(Int.MAX_VALUE, Int.MAX_VALUE)
+ foreground = labelDefaultColor
+ }
+
+ final override fun doLayout() {
+ super.doLayout()
+ // If there is a model name, we manually assign the content of the modelNameLabel and position
+ // it here.
+ // Once this panel gets more functionality, we will need the use of a layout manager. For now,
+ // we just lay out the component manually.
+ if (layoutData.modelName == null) {
+ text = ""
+ toolTipText = ""
+ isVisible = false
+ } else {
+ text = layoutData.modelName
+ // Use modelName for tooltip if none has been specified.
+ toolTipText = layoutData.modelTooltip ?: layoutData.modelName
+ isVisible = true
+ }
+ }
+
+ companion object {
+ val labelDefaultColor = JBColor(0x6c707e, 0xdfe1e5)
+ }
+}
diff --git a/designer/src/com/android/tools/idea/common/surface/SceneViewPanel.kt b/designer/src/com/android/tools/idea/common/surface/SceneViewPanel.kt
index a1b370d..c142b29 100644
--- a/designer/src/com/android/tools/idea/common/surface/SceneViewPanel.kt
+++ b/designer/src/com/android/tools/idea/common/surface/SceneViewPanel.kt
@@ -85,9 +85,6 @@
invalidate()
}
- /** Invoked when label in [SceneViewPeerPanel] is clicked. */
- var onLabelClicked: (suspend (SceneView, Boolean) -> Boolean) = { _, _ -> true }
-
@UiThread
private fun revalidateSceneViews() {
// Check if the SceneViews are still valid
@@ -112,17 +109,18 @@
if (shouldRenderErrorsPanel()) SceneViewErrorsPanel { sceneView.hasRenderErrors() }
else null
+ val labelPanel = actionManagerProvider().createSceneViewLabel(sceneView)
+
add(
SceneViewPeerPanel(
sceneView,
- disposable,
+ labelPanel,
statusIcon,
toolbar,
bottomBar,
leftBar,
rightBar,
errorsPanel,
- onLabelClicked
)
.also { it.alignmentX = sceneViewAlignment }
)
diff --git a/designer/src/com/android/tools/idea/common/surface/SceneViewPeerPanel.kt b/designer/src/com/android/tools/idea/common/surface/SceneViewPeerPanel.kt
index e003464..df7ce35 100644
--- a/designer/src/com/android/tools/idea/common/surface/SceneViewPeerPanel.kt
+++ b/designer/src/com/android/tools/idea/common/surface/SceneViewPeerPanel.kt
@@ -17,7 +17,6 @@
import com.android.tools.adtui.common.SwingCoordinate
import com.android.tools.idea.common.model.scaleBy
-import com.android.tools.idea.concurrency.AndroidCoroutineScope
import com.android.tools.idea.uibuilder.scene.hasRenderErrors
import com.android.tools.idea.uibuilder.surface.layout.PositionableContent
import com.android.tools.idea.uibuilder.surface.layout.getScaledContentSize
@@ -25,9 +24,6 @@
import com.android.tools.idea.uibuilder.surface.layout.margin
import com.android.tools.idea.uibuilder.surface.layout.scaledContentSize
import com.google.common.annotations.VisibleForTesting
-import com.intellij.openapi.Disposable
-import com.intellij.ui.JBColor
-import com.intellij.ui.components.JBLabel
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import java.awt.BorderLayout
@@ -39,7 +35,6 @@
import javax.swing.JComponent
import javax.swing.JPanel
import javax.swing.SwingUtilities
-import kotlinx.coroutines.launch
/** Distance between the bottom bound of model name and top bound of SceneView. */
@SwingCoordinate private const val TOP_BAR_BOTTOM_MARGIN = 3
@@ -53,8 +48,7 @@
/** Minimum allowed width for the model name label. */
@SwingCoordinate private const val MODEL_NAME_LABEL_MIN_WIDTH = 20
-private data class LayoutData
-private constructor(
+data class LayoutData(
val scale: Double,
val modelName: String?,
val modelTooltip: String?,
@@ -89,9 +83,6 @@
}
}
-private val nameLabelDefaultColor = JBColor(0x6c707e, 0xdfe1e5)
-private val nameLabelHoverColor = JBColor(0x5a5d6b, 0xf0f1f2)
-
/**
* A Swing component associated to the given [SceneView]. There will be one of this components in
* the [DesignSurface] per every [SceneView] available. This panel will be positioned on the
@@ -99,18 +90,15 @@
*/
class SceneViewPeerPanel(
val sceneView: SceneView,
- disposable: Disposable,
+ private val labelPanel: LabelPanel,
private val sceneViewStatusIcon: JComponent?,
private val sceneViewToolbar: JComponent?,
private val sceneViewBottomBar: JComponent?,
private val sceneViewLeftBar: JComponent?,
private val sceneViewRightBar: JComponent?,
private val sceneViewErrorsPanel: JComponent?,
- private val onLabelClicked: (suspend (SceneView, Boolean) -> Boolean)
) : JPanel() {
- private val scope = AndroidCoroutineScope(disposable)
-
/**
* Contains cached layout data that can be used by this panel to verify when it's been invalidated
* without having to explicitly call [revalidate]
@@ -121,28 +109,6 @@
private val cachedScaledContentSize = Dimension()
private val cachedPreferredSize = Dimension()
- /** This label displays the [SceneView] model if there is any */
- private val modelNameLabel =
- JBLabel().apply {
- maximumSize = Dimension(Int.MAX_VALUE, Int.MAX_VALUE)
- foreground = nameLabelDefaultColor
- addMouseListener(
- object : MouseAdapter() {
- override fun mouseEntered(e: MouseEvent?) {
- foreground = nameLabelHoverColor
- }
-
- override fun mouseExited(e: MouseEvent?) {
- foreground = nameLabelDefaultColor
- }
-
- override fun mouseClicked(e: MouseEvent?) {
- scope.launch { onLabelClicked(sceneView, false) }
- }
- }
- )
- }
-
val positionableAdapter =
object : PositionableContent {
override val groupId: String?
@@ -240,7 +206,7 @@
add(sceneViewStatusIcon, BorderLayout.LINE_START)
sceneViewStatusIcon.isVisible = true
}
- add(modelNameLabel, BorderLayout.CENTER)
+ add(labelPanel, BorderLayout.CENTER)
if (sceneViewToolbar != null) {
add(sceneViewToolbar, BorderLayout.LINE_END)
// Initialize the toolbar as invisible. Its visibility will be controlled by hovering the
@@ -348,7 +314,7 @@
}
addMouseListener(hoverTopPanelMouseListener)
- modelNameLabel.addMouseListener(hoverTopPanelMouseListener)
+ labelPanel.addMouseListener(hoverTopPanelMouseListener)
}
private val sceneViewBottomPanel =
@@ -387,6 +353,7 @@
override fun doLayout() {
layoutData = LayoutData.fromSceneView(sceneView)
+ labelPanel.layoutData = layoutData
// SceneViewPeerPanel layout:
//
@@ -405,28 +372,14 @@
// ←-------→ ←--------→
// preferredWidth preferredWidth
- // If there is a model name, we manually assign the content of the modelNameLabel and position
- // it here.
- // Once this panel gets more functionality, we will need the use of a layout manager. For now,
- // we just lay out the component manually.
- if (layoutData.modelName == null) {
- modelNameLabel.text = ""
- modelNameLabel.toolTipText = ""
- sceneViewTopPanel.isVisible = false
- } else {
- modelNameLabel.text = layoutData.modelName
- // Use modelName for tooltip if none has been specified.
- modelNameLabel.toolTipText = layoutData.modelTooltip ?: layoutData.modelName
- // We layout the top panel. We make the width to match the SceneViewPanel width and we let it
- // choose its own
- // height.
+ sceneViewTopPanel.isVisible = labelPanel.isVisible
+ if (labelPanel.isVisible) {
sceneViewTopPanel.setBounds(
0,
0,
width + insets.horizontal,
sceneViewTopPanel.preferredSize.height
)
- sceneViewTopPanel.isVisible = true
}
val leftSectionWidth = sceneViewLeftPanel.preferredSize.width
val centerPanelHeight =
diff --git a/designer/src/com/android/tools/idea/uibuilder/actions/GenerateLayoutTestSkeletonAction.java b/designer/src/com/android/tools/idea/uibuilder/actions/GenerateLayoutTestSkeletonAction.java
index ce2a272..6d8a788 100644
--- a/designer/src/com/android/tools/idea/uibuilder/actions/GenerateLayoutTestSkeletonAction.java
+++ b/designer/src/com/android/tools/idea/uibuilder/actions/GenerateLayoutTestSkeletonAction.java
@@ -36,8 +36,10 @@
import com.google.common.base.CaseFormat;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
@@ -64,6 +66,11 @@
}
@Override
+ public @NotNull ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.EDT;
+ }
+
+ @Override
public void update(@NotNull AnActionEvent event) {
event.getPresentation().setEnabled(getModel(event.getProject()) != null);
}
diff --git a/designer/src/com/android/tools/idea/uibuilder/surface/NlDesignSurface.java b/designer/src/com/android/tools/idea/uibuilder/surface/NlDesignSurface.java
index 830b999..aa7a6de 100644
--- a/designer/src/com/android/tools/idea/uibuilder/surface/NlDesignSurface.java
+++ b/designer/src/com/android/tools/idea/uibuilder/surface/NlDesignSurface.java
@@ -132,7 +132,6 @@
private BiFunction<NlDesignSurface, NlModel, LayoutlibSceneManager> mySceneManagerProvider =
NlDesignSurface::defaultSceneManagerProvider;
private SurfaceLayoutManager myLayoutManager;
- private NavigationHandler myNavigationHandler;
@SurfaceScale private double myMinScale = DEFAULT_MIN_SCALE;
@SurfaceScale private double myMaxScale = DEFAULT_MAX_SCALE;
/**
@@ -231,15 +230,6 @@
return this;
}
- /**
- * When the surface is clicked, it can delegate navigation related task to the given handler.
- * @param navigationHandler handles the navigation when the surface is clicked.
- */
- @NotNull
- public Builder setNavigationHandler(NavigationHandler navigationHandler) {
- myNavigationHandler = navigationHandler;
- return this;
- }
/**
* Restrict the minimum zoom level to the given value. The default value is {@link #DEFAULT_MIN_SCALE}.
@@ -357,7 +347,6 @@
myActionManagerProvider,
myInteractableProvider,
myInteractionHandlerProvider,
- myNavigationHandler,
myMinScale,
myMaxScale,
myActionHandlerProvider,
@@ -388,9 +377,6 @@
private final BiFunction<NlDesignSurface, NlModel, LayoutlibSceneManager> mySceneManagerProvider;
@NotNull private SurfaceLayoutManager myLayoutManager;
-
- @Nullable private final NavigationHandler myNavigationHandler;
-
@SurfaceScale private final double myMinScale;
@SurfaceScale private final double myMaxScale;
@@ -417,7 +403,6 @@
@NotNull Function<DesignSurface<LayoutlibSceneManager>, ActionManager<? extends DesignSurface<LayoutlibSceneManager>>> actionManagerProvider,
@NotNull Function<DesignSurface<LayoutlibSceneManager>, Interactable> interactableProvider,
@NotNull Function<DesignSurface<LayoutlibSceneManager>, InteractionHandler> interactionHandlerProvider,
- @Nullable NavigationHandler navigationHandler,
@SurfaceScale double minScale,
@SurfaceScale double maxScale,
@NotNull Function<DesignSurface<LayoutlibSceneManager>, DesignSurfaceActionHandler> actionHandlerProvider,
@@ -437,16 +422,9 @@
myAccessoryPanel.setSurface(this);
myLayoutManager = defaultLayoutManager;
mySceneManagerProvider = sceneManagerProvider;
- myNavigationHandler = navigationHandler;
mySupportedActions = supportedActions;
myShouldRenderErrorsPanel = shouldRenderErrorsPanel;
myVisualLintIssueProvider = new VisualLintIssueProvider(this);
-
- if (myNavigationHandler != null) {
- Disposer.register(this, myNavigationHandler);
- mySceneViewPanel.setOnLabelClicked(myNavigationHandler::handleNavigate);
- }
-
myMinScale = minScale;
myMaxScale = maxScale;
@@ -595,11 +573,6 @@
revalidateScrollArea();
}
- @Nullable
- public NavigationHandler getNavigationHandler() {
- return myNavigationHandler;
- }
-
@Override
public boolean shouldRenderErrorsPanel() {
return myShouldRenderErrorsPanel;
diff --git a/designer/src/com/android/tools/idea/uibuilder/surface/NlLayoutScannerMetricTracker.kt b/designer/src/com/android/tools/idea/uibuilder/surface/NlLayoutScannerMetricTracker.kt
index b4fa3a1..b0871e3 100644
--- a/designer/src/com/android/tools/idea/uibuilder/surface/NlLayoutScannerMetricTracker.kt
+++ b/designer/src/com/android/tools/idea/uibuilder/surface/NlLayoutScannerMetricTracker.kt
@@ -25,7 +25,6 @@
import com.google.wireless.android.sdk.stats.AtfResultDetail
import com.google.wireless.android.sdk.stats.IgnoreAtfResultEvent
import com.google.wireless.android.sdk.stats.LayoutEditorEvent
-import com.intellij.lang.annotation.HighlightSeverity
/** Metric tracker for results from accessibility testing framework */
class NlLayoutScannerMetricTracker(private val surface: NlDesignSurface) {
@@ -33,55 +32,6 @@
/** Track expanded issues so we don't log them multiple times */
@VisibleForTesting val expanded = HashSet<Issue>()
- /**
- * Tracks all issues created by atf.
- *
- * TODO(b/298229332): Remove this, or call it somewhere other than tests.
- */
- fun trackIssues(issues: Set<Issue>, renderMetric: RenderResultMetricData) {
- val atfIssues =
- issues.filterIsInstance<NlAtfIssue>().filter {
- it.severity == HighlightSeverity.ERROR || it.severity == HighlightSeverity.WARNING
- }
- if (atfIssues.isEmpty()) {
- return
- }
-
- CommonUsageTracker.getInstance(surface).logStudioEvent(
- LayoutEditorEvent.LayoutEditorEventType.ATF_AUDIT_RESULT
- ) { event ->
- val atfResultBuilder = AtfAuditResult.newBuilder()
- atfIssues.forEach { issue -> atfResultBuilder.addCounts(atfResultCountBuilder(issue)) }
- atfResultBuilder
- .setAuditDurationMs(renderMetric.scanMs)
- .setTotalRenderTimeMs(renderMetric.renderMs)
- .setComponentCount(renderMetric.componentCount)
- .setRenderResult(renderMetric.isRenderResultSuccess)
- event.setAtfAuditResult(atfResultBuilder)
- }
- }
-
- /**
- * Track the first time the issue is expanded by user.
- *
- * TODO(b/298229332): Remove this, or call it somewhere other than tests.
- */
- fun trackFirstExpanded(issue: Issue) {
- if (issue !is NlAtfIssue || expanded.contains(issue)) {
- return
- }
-
- expanded.add(issue)
-
- CommonUsageTracker.getInstance(surface).logStudioEvent(
- LayoutEditorEvent.LayoutEditorEventType.ATF_AUDIT_RESULT
- ) { event ->
- val atfResultBuilder = AtfAuditResult.newBuilder()
- atfResultBuilder.addCounts(atfResultCountBuilder(issue).setErrorExpanded(true))
- event.setAtfAuditResult(atfResultBuilder)
- }
- }
-
/** Track the ignore button is clicked by user. */
fun trackIgnoreButtonClicked(issue: ValidatorData.Issue) {
CommonUsageTracker.getInstance(surface).logStudioEvent(
@@ -132,21 +82,6 @@
else -> AtfFixDetail.AtfFixType.UNKNOWN
}
}
-
- private fun atfResultCountBuilder(issue: NlAtfIssue): AtfAuditResult.AtfResultCount.Builder {
- val atfResultCountBuilder =
- AtfAuditResult.AtfResultCount.newBuilder().setCheckName(issue.srcClass)
- issue.result.mFix?.let { atfResultCountBuilder.addFixes(atfFixDetailBuilder(it)) }
- atfResultCountBuilder.setResultType(
- when (issue.result.mLevel) {
- ValidatorData.Level.ERROR -> AtfAuditResult.AtfResultCount.CheckResultType.ERROR
- ValidatorData.Level.WARNING -> AtfAuditResult.AtfResultCount.CheckResultType.WARNING
- ValidatorData.Level.INFO -> AtfAuditResult.AtfResultCount.CheckResultType.INFO
- else -> AtfAuditResult.AtfResultCount.CheckResultType.UNKNOWN // Should not be triggered.
- }
- )
- return atfResultCountBuilder
- }
}
/** Metric metadata related to render results. */
diff --git a/designer/src/com/android/tools/idea/uibuilder/surface/layout/GroupedGridSurfaceLayoutManager.kt b/designer/src/com/android/tools/idea/uibuilder/surface/layout/GroupedGridSurfaceLayoutManager.kt
index 23c25ac..55597c2 100644
--- a/designer/src/com/android/tools/idea/uibuilder/surface/layout/GroupedGridSurfaceLayoutManager.kt
+++ b/designer/src/com/android/tools/idea/uibuilder/surface/layout/GroupedGridSurfaceLayoutManager.kt
@@ -264,7 +264,7 @@
val visibleContents = content.filter { it.isVisible }
if (visibleContents.size == 1) {
- val singleContent = content.single()
+ val singleContent = visibleContents.single()
// When there is only one visible preview, centralize it as a special case.
val point = getSingleContentPosition(singleContent, availableWidth, availableHeight)
diff --git a/designer/testSrc/com/android/tools/idea/common/error/DesignerCommonIssuePanelTest.kt b/designer/testSrc/com/android/tools/idea/common/error/DesignerCommonIssuePanelTest.kt
index 3ef230e..f67a511 100644
--- a/designer/testSrc/com/android/tools/idea/common/error/DesignerCommonIssuePanelTest.kt
+++ b/designer/testSrc/com/android/tools/idea/common/error/DesignerCommonIssuePanelTest.kt
@@ -63,10 +63,9 @@
rule.project,
model,
{ LayoutValidationNodeFactory },
- provider
- ) {
- ""
- }
+ provider,
+ { "" }
+ )
// Make sure the Tree is added into DesignerCommonIssuePanel.
IdeEventQueue.getInstance().flushQueue()
val tree = UIUtil.findComponentOfType(panel.getComponent(), Tree::class.java)!!
@@ -132,10 +131,9 @@
rule.project,
model,
{ LayoutValidationNodeFactory },
- provider
- ) {
- ""
- }
+ provider,
+ { "" }
+ )
// Make sure the Tree is added into DesignerCommonIssuePanel.
IdeEventQueue.getInstance().flushQueue()
val tree = UIUtil.findComponentOfType(panel.getComponent(), Tree::class.java)!!
@@ -198,10 +196,9 @@
rule.project,
model,
{ LayoutValidationNodeFactory },
- provider
- ) {
- ""
- }
+ provider,
+ { "" }
+ )
// Make sure the Tree is added into DesignerCommonIssuePanel.
IdeEventQueue.getInstance().flushQueue()
val tree = UIUtil.findComponentOfType(panel.getComponent(), Tree::class.java)!!
diff --git a/designer/testSrc/com/android/tools/idea/common/error/IssueNodeVisitorTest.kt b/designer/testSrc/com/android/tools/idea/common/error/IssueNodeVisitorTest.kt
index 96d4e03..ee9f738 100644
--- a/designer/testSrc/com/android/tools/idea/common/error/IssueNodeVisitorTest.kt
+++ b/designer/testSrc/com/android/tools/idea/common/error/IssueNodeVisitorTest.kt
@@ -46,10 +46,9 @@
rule.project,
model,
{ LayoutValidationNodeFactory },
- provider
- ) {
- ""
- }
+ provider,
+ { "" }
+ )
IdeEventQueue.getInstance().flushQueue()
val tree = UIUtil.findComponentOfType(panel.getComponent(), Tree::class.java)!!
diff --git a/designer/testSrc/com/android/tools/idea/common/error/SceneViewIssueNodeVisitorTest.kt b/designer/testSrc/com/android/tools/idea/common/error/SceneViewIssueNodeVisitorTest.kt
index 5c1136b..e2034617 100644
--- a/designer/testSrc/com/android/tools/idea/common/error/SceneViewIssueNodeVisitorTest.kt
+++ b/designer/testSrc/com/android/tools/idea/common/error/SceneViewIssueNodeVisitorTest.kt
@@ -64,10 +64,9 @@
rule.project,
model,
{ LayoutValidationNodeFactory },
- provider
- ) {
- ""
- }
+ provider,
+ { "" }
+ )
IdeEventQueue.getInstance().flushQueue()
val tree = UIUtil.findComponentOfType(panel.getComponent(), Tree::class.java)!!
@@ -105,10 +104,9 @@
rule.project,
model,
{ LayoutValidationNodeFactory },
- provider
- ) {
- ""
- }
+ provider,
+ { "" }
+ )
IdeEventQueue.getInstance().flushQueue()
val tree = UIUtil.findComponentOfType(panel.getComponent(), Tree::class.java)!!
@@ -146,10 +144,9 @@
rule.project,
model,
{ LayoutValidationNodeFactory },
- provider
- ) {
- ""
- }
+ provider,
+ { "" }
+ )
IdeEventQueue.getInstance().flushQueue()
val tree = UIUtil.findComponentOfType(panel.getComponent(), Tree::class.java)!!
@@ -188,10 +185,9 @@
rule.project,
model,
{ LayoutValidationNodeFactory },
- provider
- ) {
- ""
- }
+ provider,
+ { "" }
+ )
IdeEventQueue.getInstance().flushQueue()
val tree = UIUtil.findComponentOfType(panel.getComponent(), Tree::class.java)!!
diff --git a/designer/testSrc/com/android/tools/idea/common/surface/InteractiveLabelPanelTest.kt b/designer/testSrc/com/android/tools/idea/common/surface/InteractiveLabelPanelTest.kt
new file mode 100644
index 0000000..fc3bfcf
--- /dev/null
+++ b/designer/testSrc/com/android/tools/idea/common/surface/InteractiveLabelPanelTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.common.surface
+
+import com.android.tools.adtui.swing.FakeUi
+import com.android.tools.idea.testing.AndroidProjectRule
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.util.Disposer
+import java.awt.Dimension
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertEquals
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+class InteractiveLabelPanelTest {
+
+ @get:Rule val projectRule = AndroidProjectRule.inMemory()
+ private lateinit var parentDisposable: Disposable
+
+ @Before
+ fun setUp() {
+ parentDisposable = Disposer.newDisposable()
+ }
+
+ @After
+ fun tearDown() {
+ Disposer.dispose(parentDisposable)
+ }
+
+ @Test
+ @Ignore("b/289994157")
+ fun `click label`() {
+ runBlocking {
+ var clickCount = 0
+ fun labelClicked(): Boolean {
+ clickCount++
+ return false
+ }
+
+ val layoutData = LayoutData(1.0, "Name", "Tooltip", 0, 0, Dimension(10, 10))
+ val label =
+ InteractiveLabelPanel(layoutData, parentDisposable, ::labelClicked).apply {
+ size = Dimension(250, 50)
+ }
+ FakeUi(label).also { it.clickOn(label) }
+ withTimeout(TimeUnit.SECONDS.toMillis(1)) { assertEquals(1, clickCount) }
+ }
+ }
+}
diff --git a/designer/testSrc/com/android/tools/idea/common/surface/LabelPanelTest.kt b/designer/testSrc/com/android/tools/idea/common/surface/LabelPanelTest.kt
new file mode 100644
index 0000000..33b23b5
--- /dev/null
+++ b/designer/testSrc/com/android/tools/idea/common/surface/LabelPanelTest.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.common.surface
+
+import com.android.tools.adtui.swing.FakeUi
+import java.awt.Dimension
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class LabelPanelTest {
+
+ @Test
+ fun `label is visible`() {
+ val layoutData = LayoutData(1.0, "Name", "Tooltip", 0, 0, Dimension(10, 10))
+ val label = LabelPanel(layoutData).apply { size = Dimension(250, 50) }
+ FakeUi(label).also { it.layout() }
+ assertTrue(label.isVisible)
+ }
+
+ @Test
+ fun `label is not visible`() {
+ val layoutData = LayoutData(1.0, null, null, 0, 0, Dimension(10, 10))
+ val label = LabelPanel(layoutData).apply { size = Dimension(250, 50) }
+ FakeUi(label).also { it.layout() }
+ assertFalse(label.isVisible)
+ }
+}
diff --git a/designer/testSrc/com/android/tools/idea/uibuilder/surface/NlLayoutScannerMetricTrackerTest.kt b/designer/testSrc/com/android/tools/idea/uibuilder/surface/NlLayoutScannerMetricTrackerTest.kt
index 4e6023b..c98c586 100644
--- a/designer/testSrc/com/android/tools/idea/uibuilder/surface/NlLayoutScannerMetricTrackerTest.kt
+++ b/designer/testSrc/com/android/tools/idea/uibuilder/surface/NlLayoutScannerMetricTrackerTest.kt
@@ -18,13 +18,11 @@
import com.android.SdkConstants
import com.android.tools.idea.common.analytics.CommonNopTracker
import com.android.tools.idea.common.analytics.CommonUsageTracker
-import com.android.tools.idea.common.error.Issue
import com.android.tools.idea.common.error.IssueSource
import com.android.tools.idea.common.model.NlModel
import com.android.tools.idea.uibuilder.LayoutTestCase
import com.android.tools.idea.validator.ValidatorData
import com.google.wireless.android.sdk.stats.LayoutEditorEvent
-import com.intellij.lang.annotation.HighlightSeverity
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,80 +43,6 @@
}
@Test
- fun trackIssueNoIssues() {
- val tracker = NlLayoutScannerMetricTracker(mockSurface)
- val nlAtfIssues = setOf<Issue>()
- val renderResultMetricData = RenderResultMetricData(1, 1, 1)
-
- tracker.trackIssues(nlAtfIssues, renderResultMetricData)
-
- val usageTracker = CommonUsageTracker.getInstance(mockSurface) as CommonNopTracker
- assertNull(usageTracker.lastTrackedEvent)
- }
-
- @Test
- fun trackIssueNoNlAtfIssues() {
- val tracker = NlLayoutScannerMetricTracker(mockSurface)
- val nlAtfIssues = setOf<Issue>(TestIssue())
- val renderResultMetricData = RenderResultMetricData(1, 1, 1)
-
- tracker.trackIssues(nlAtfIssues, renderResultMetricData)
-
- val usageTracker = CommonUsageTracker.getInstance(mockSurface) as CommonNopTracker
- assertNull(usageTracker.lastTrackedEvent)
- }
-
- @Test
- fun trackIssues() {
- val tracker = NlLayoutScannerMetricTracker(mockSurface)
- val nlAtfIssues = setOf(createTestNlAtfIssue())
- val renderResultMetricData = RenderResultMetricData(1, 1, 1)
-
- tracker.trackIssues(nlAtfIssues, renderResultMetricData)
-
- val usageTracker = CommonUsageTracker.getInstance(mockSurface) as CommonNopTracker
- assertEquals(
- LayoutEditorEvent.LayoutEditorEventType.ATF_AUDIT_RESULT,
- usageTracker.lastTrackedEvent
- )
- }
-
- @Test
- fun trackExpandedNotAtfIssue() {
- val tracker = NlLayoutScannerMetricTracker(mockSurface)
- tracker.trackFirstExpanded(TestIssue())
-
- val usageTracker = CommonUsageTracker.getInstance(mockSurface) as CommonNopTracker
- assertNull(usageTracker.lastTrackedEvent)
- }
-
- @Test
- fun trackExpandedAlreadyContained() {
- val tracker = NlLayoutScannerMetricTracker(mockSurface)
- val issue = createTestNlAtfIssue()
- tracker.expanded.add(issue)
-
- tracker.trackFirstExpanded(issue)
-
- val usageTracker = CommonUsageTracker.getInstance(mockSurface) as CommonNopTracker
- assertNull(usageTracker.lastTrackedEvent)
- }
-
- @Test
- fun trackExpanded() {
- val tracker = NlLayoutScannerMetricTracker(mockSurface)
- val issue = createTestNlAtfIssue()
- tracker.trackFirstExpanded(issue)
-
- val usageTracker = CommonUsageTracker.getInstance(mockSurface) as CommonNopTracker
- assertEquals(
- LayoutEditorEvent.LayoutEditorEventType.ATF_AUDIT_RESULT,
- usageTracker.lastTrackedEvent
- )
- assertTrue(tracker.expanded.contains(issue))
- }
-
- @Test
fun trackIgnoreButtonClicked() {
val tracker = NlLayoutScannerMetricTracker(mockSurface)
val issue = ScannerTestHelper.createTestIssueBuilder().build()
@@ -163,17 +87,4 @@
val issue: ValidatorData.Issue = ScannerTestHelper.createTestIssueBuilder().build()
return NlAtfIssue(issue, IssueSource.NONE, mockModel)
}
-
- private class TestIssue : Issue() {
- override val summary: String
- get() = ""
- override val description: String
- get() = ""
- override val severity: HighlightSeverity
- get() = HighlightSeverity.ERROR
- override val source: IssueSource
- get() = IssueSource.NONE
- override val category: String
- get() = ""
- }
}
diff --git a/device-explorer-files/src/com/android/tools/idea/device/explorer/files/actions/GotoDatabaseFolderAction.java b/device-explorer-files/src/com/android/tools/idea/device/explorer/files/actions/GotoDatabaseFolderAction.java
deleted file mode 100644
index 801c583..0000000
--- a/device-explorer-files/src/com/android/tools/idea/device/explorer/files/actions/GotoDatabaseFolderAction.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2022 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.device.explorer.files.actions;
-
-import com.android.tools.idea.device.explorer.files.DeviceFileExplorerControllerImpl;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.project.DumbAwareAction;
-import org.jetbrains.annotations.NotNull;
-
-public class GotoDatabaseFolderAction extends DumbAwareAction {
- @Override
- public void update(@NotNull AnActionEvent e) {
- DeviceFileExplorerControllerImpl controller = DeviceFileExplorerControllerImpl.getProjectController(e.getProject());
- e.getPresentation().setEnabled(controller != null && controller.hasActiveDevice());
- }
-
- @Override
- public void actionPerformed(@NotNull AnActionEvent e) {
- }
-}
diff --git a/device-explorer-files/src/com/android/tools/idea/device/explorer/files/actions/GotoSdcardFolderAction.java b/device-explorer-files/src/com/android/tools/idea/device/explorer/files/actions/GotoSdcardFolderAction.java
deleted file mode 100644
index ff74f39..0000000
--- a/device-explorer-files/src/com/android/tools/idea/device/explorer/files/actions/GotoSdcardFolderAction.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2022 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.device.explorer.files.actions;
-
-import com.android.tools.idea.device.explorer.files.DeviceFileExplorerControllerImpl;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.project.DumbAwareAction;
-import org.jetbrains.annotations.NotNull;
-
-public class GotoSdcardFolderAction extends DumbAwareAction {
- @Override
- public void update(@NotNull AnActionEvent e) {
- DeviceFileExplorerControllerImpl controller = DeviceFileExplorerControllerImpl.getProjectController(e.getProject());
- e.getPresentation().setEnabled(controller != null && controller.hasActiveDevice());
- }
-
- @Override
- public void actionPerformed(@NotNull AnActionEvent e) {
- }
-}
diff --git a/device-explorer-files/src/com/android/tools/idea/device/explorer/files/actions/GotoSharedPrefsFolderAction.java b/device-explorer-files/src/com/android/tools/idea/device/explorer/files/actions/GotoSharedPrefsFolderAction.java
deleted file mode 100644
index 0a90ce8..0000000
--- a/device-explorer-files/src/com/android/tools/idea/device/explorer/files/actions/GotoSharedPrefsFolderAction.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2022 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.device.explorer.files.actions;
-
-import com.android.tools.idea.device.explorer.files.DeviceFileExplorerControllerImpl;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.project.DumbAwareAction;
-import org.jetbrains.annotations.NotNull;
-
-public class GotoSharedPrefsFolderAction extends DumbAwareAction {
- @Override
- public void update(@NotNull AnActionEvent e) {
- DeviceFileExplorerControllerImpl controller = DeviceFileExplorerControllerImpl.getProjectController(e.getProject());
- e.getPresentation().setEnabled(controller != null && controller.hasActiveDevice());
- }
-
- @Override
- public void actionPerformed(@NotNull AnActionEvent e) {
- }
-}
diff --git a/device-explorer-files/src/com/android/tools/idea/device/explorer/files/actions/HelpAction.java b/device-explorer-files/src/com/android/tools/idea/device/explorer/files/actions/HelpAction.java
deleted file mode 100644
index 53e66347..0000000
--- a/device-explorer-files/src/com/android/tools/idea/device/explorer/files/actions/HelpAction.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2022 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.device.explorer.files.actions;
-
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.project.DumbAwareAction;
-import org.jetbrains.annotations.NotNull;
-
-public class HelpAction extends DumbAwareAction {
- @Override
- public void update(@NotNull AnActionEvent e) {
- e.getPresentation().setEnabled(false);
- }
-
- @Override
- public void actionPerformed(@NotNull AnActionEvent e) {
- }
-}
\ No newline at end of file
diff --git a/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/GotoDatabaseFolderAction.java b/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/GotoDatabaseFolderAction.java
index 2782df9..add6f53 100644
--- a/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/GotoDatabaseFolderAction.java
+++ b/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/GotoDatabaseFolderAction.java
@@ -16,11 +16,19 @@
package com.android.tools.idea.file.explorer.toolwindow.actions;
import com.android.tools.idea.file.explorer.toolwindow.DeviceExplorerController;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.DumbAwareAction;
import org.jetbrains.annotations.NotNull;
public class GotoDatabaseFolderAction extends DumbAwareAction {
+
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.EDT;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
DeviceExplorerController controller = DeviceExplorerController.getProjectController(e.getProject());
diff --git a/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/GotoSdcardFolderAction.java b/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/GotoSdcardFolderAction.java
index 2529965..92a6910 100644
--- a/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/GotoSdcardFolderAction.java
+++ b/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/GotoSdcardFolderAction.java
@@ -16,11 +16,19 @@
package com.android.tools.idea.file.explorer.toolwindow.actions;
import com.android.tools.idea.file.explorer.toolwindow.DeviceExplorerController;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.DumbAwareAction;
import org.jetbrains.annotations.NotNull;
public class GotoSdcardFolderAction extends DumbAwareAction {
+
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.EDT;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
DeviceExplorerController controller = DeviceExplorerController.getProjectController(e.getProject());
diff --git a/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/GotoSharedPrefsFolderAction.java b/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/GotoSharedPrefsFolderAction.java
index 555ec14..7f0f974 100644
--- a/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/GotoSharedPrefsFolderAction.java
+++ b/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/GotoSharedPrefsFolderAction.java
@@ -16,11 +16,19 @@
package com.android.tools.idea.file.explorer.toolwindow.actions;
import com.android.tools.idea.file.explorer.toolwindow.DeviceExplorerController;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.DumbAwareAction;
import org.jetbrains.annotations.NotNull;
public class GotoSharedPrefsFolderAction extends DumbAwareAction {
+
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.EDT;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
DeviceExplorerController controller = DeviceExplorerController.getProjectController(e.getProject());
diff --git a/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/HelpAction.java b/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/HelpAction.java
index 8882865..ea7eb09 100644
--- a/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/HelpAction.java
+++ b/device-file-explorer-toolwindow/src/com/android/tools/idea/file/explorer/toolwindow/actions/HelpAction.java
@@ -15,11 +15,19 @@
*/
package com.android.tools.idea.file.explorer.toolwindow.actions;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.DumbAwareAction;
import org.jetbrains.annotations.NotNull;
public class HelpAction extends DumbAwareAction {
+
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
e.getPresentation().setEnabled(false);
diff --git a/device-manager/src/com/android/tools/idea/devicemanager/DeviceManagerWelcomeScreenAction.java b/device-manager/src/com/android/tools/idea/devicemanager/DeviceManagerWelcomeScreenAction.java
index 28b4965..b856df7 100644
--- a/device-manager/src/com/android/tools/idea/devicemanager/DeviceManagerWelcomeScreenAction.java
+++ b/device-manager/src/com/android/tools/idea/devicemanager/DeviceManagerWelcomeScreenAction.java
@@ -17,6 +17,7 @@
import com.android.tools.idea.avdmanager.HardwareAccelerationCheck;
import com.android.tools.idea.flags.StudioFlags;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.project.DumbAwareAction;
@@ -47,6 +48,11 @@
}
@Override
+ public @NotNull ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
+ @Override
public void update(@NotNull AnActionEvent event) {
Presentation presentation = event.getPresentation();
diff --git a/device-manager/testSrc/com/android/tools/idea/devicemanager/physicaltab/PhysicalDeviceChangeListenerTest.java b/device-manager/testSrc/com/android/tools/idea/devicemanager/physicaltab/PhysicalDeviceChangeListenerTest.java
index 999ae6a..87ae9a2 100644
--- a/device-manager/testSrc/com/android/tools/idea/devicemanager/physicaltab/PhysicalDeviceChangeListenerTest.java
+++ b/device-manager/testSrc/com/android/tools/idea/devicemanager/physicaltab/PhysicalDeviceChangeListenerTest.java
@@ -17,8 +17,8 @@
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.ddmlib.IDevice;
-import com.android.tools.idea.devicemanager.CountDownLatchAssert;
-import com.android.tools.idea.devicemanager.CountDownLatchFutureCallback;
+import com.android.tools.idea.concurrency.CountDownLatchAssert;
+import com.android.tools.idea.concurrency.CountDownLatchFutureCallback;
import com.android.tools.idea.devicemanager.DeviceManagerAndroidDebugBridge;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
diff --git a/device-manager/testSrc/com/android/tools/idea/devicemanager/physicaltab/PhysicalDeviceDetailsPanelTest.java b/device-manager/testSrc/com/android/tools/idea/devicemanager/physicaltab/PhysicalDeviceDetailsPanelTest.java
index 5bd8e14..8347020 100644
--- a/device-manager/testSrc/com/android/tools/idea/devicemanager/physicaltab/PhysicalDeviceDetailsPanelTest.java
+++ b/device-manager/testSrc/com/android/tools/idea/devicemanager/physicaltab/PhysicalDeviceDetailsPanelTest.java
@@ -19,8 +19,8 @@
import com.android.sdklib.AndroidVersion;
import com.android.tools.idea.device.Resolution;
-import com.android.tools.idea.devicemanager.CountDownLatchAssert;
-import com.android.tools.idea.devicemanager.CountDownLatchFutureCallback;
+import com.android.tools.idea.concurrency.CountDownLatchAssert;
+import com.android.tools.idea.concurrency.CountDownLatchFutureCallback;
import com.android.tools.idea.devicemanager.DetailsPanel;
import com.android.tools.idea.devicemanager.SerialNumber;
import com.android.tools.idea.devicemanager.physicaltab.PhysicalDeviceDetailsPanel.SummarySection;
diff --git a/device-manager/testSrc/com/android/tools/idea/devicemanager/physicaltab/PhysicalDevicePanelTest.java b/device-manager/testSrc/com/android/tools/idea/devicemanager/physicaltab/PhysicalDevicePanelTest.java
index d3517b4..6ebb685 100644
--- a/device-manager/testSrc/com/android/tools/idea/devicemanager/physicaltab/PhysicalDevicePanelTest.java
+++ b/device-manager/testSrc/com/android/tools/idea/devicemanager/physicaltab/PhysicalDevicePanelTest.java
@@ -24,8 +24,8 @@
import com.android.tools.idea.adb.wireless.WiFiPairingController;
import com.android.tools.idea.devicemanager.ActivateDeviceFileExplorerWindowValue;
import com.android.tools.idea.devicemanager.ConnectionType;
-import com.android.tools.idea.devicemanager.CountDownLatchAssert;
-import com.android.tools.idea.devicemanager.CountDownLatchFutureCallback;
+import com.android.tools.idea.concurrency.CountDownLatchAssert;
+import com.android.tools.idea.concurrency.CountDownLatchFutureCallback;
import com.android.tools.idea.devicemanager.DetailsPanel;
import com.android.tools.idea.devicemanager.DeviceType;
import com.android.tools.idea.devicemanager.PopUpMenuValue;
diff --git a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/ProcessManagerTest.java b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/ProcessManagerTest.java
index c0cf49a..c8b3e79 100644
--- a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/ProcessManagerTest.java
+++ b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/ProcessManagerTest.java
@@ -21,8 +21,8 @@
import com.android.ddmlib.IDevice.DeviceState;
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.tools.idea.avdmanager.AvdManagerConnection;
-import com.android.tools.idea.devicemanager.CountDownLatchAssert;
-import com.android.tools.idea.devicemanager.CountDownLatchFutureCallback;
+import com.android.tools.idea.concurrency.CountDownLatchAssert;
+import com.android.tools.idea.concurrency.CountDownLatchFutureCallback;
import com.android.tools.idea.devicemanager.Key;
import com.android.tools.idea.devicemanager.virtualtab.ProcessManager.State;
import com.google.common.util.concurrent.FutureCallback;
diff --git a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceDetailsPanelTest.java b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceDetailsPanelTest.java
index a8ba6e5..f8c092d 100644
--- a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceDetailsPanelTest.java
+++ b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceDetailsPanelTest.java
@@ -20,8 +20,8 @@
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.sdklib.internal.avd.AvdInfo.AvdStatus;
import com.android.tools.idea.device.Resolution;
-import com.android.tools.idea.devicemanager.CountDownLatchAssert;
-import com.android.tools.idea.devicemanager.CountDownLatchFutureCallback;
+import com.android.tools.idea.concurrency.CountDownLatchAssert;
+import com.android.tools.idea.concurrency.CountDownLatchFutureCallback;
import com.android.tools.idea.devicemanager.Device;
import com.android.tools.idea.devicemanager.InfoSection;
import com.android.tools.idea.devicemanager.StorageDevice;
diff --git a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModelTest.java b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModelTest.java
index 885ca66..9bae7b5 100644
--- a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModelTest.java
+++ b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableModelTest.java
@@ -25,8 +25,8 @@
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.tools.idea.avdmanager.AvdLaunchListener.RequestType;
import com.android.tools.idea.avdmanager.AvdManagerConnection;
-import com.android.tools.idea.devicemanager.CountDownLatchAssert;
-import com.android.tools.idea.devicemanager.CountDownLatchFutureCallback;
+import com.android.tools.idea.concurrency.CountDownLatchAssert;
+import com.android.tools.idea.concurrency.CountDownLatchFutureCallback;
import com.android.tools.idea.devicemanager.DeviceManagerAndroidDebugBridge;
import com.android.tools.idea.devicemanager.Key;
import com.android.tools.idea.devicemanager.virtualtab.VirtualDeviceTableModel.SetAllOnline;
diff --git a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableTest.java b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableTest.java
index 5aaa097..5277359 100644
--- a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableTest.java
+++ b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceTableTest.java
@@ -19,8 +19,8 @@
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.sdklib.repository.targets.SystemImage;
-import com.android.tools.idea.devicemanager.CountDownLatchAssert;
-import com.android.tools.idea.devicemanager.CountDownLatchFutureCallback;
+import com.android.tools.idea.concurrency.CountDownLatchAssert;
+import com.android.tools.idea.concurrency.CountDownLatchFutureCallback;
import com.android.tools.idea.wearpairing.WearPairingManager;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
diff --git a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceWatcherTest.java b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceWatcherTest.java
index 5c7c25d..56a9634 100644
--- a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceWatcherTest.java
+++ b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/VirtualDeviceWatcherTest.java
@@ -17,7 +17,7 @@
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.sdklib.internal.avd.AvdManager;
-import com.android.tools.idea.devicemanager.CountDownLatchAssert;
+import com.android.tools.idea.concurrency.CountDownLatchAssert;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.testFramework.ApplicationRule;
import java.util.List;
diff --git a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/WipeDataItemTest.kt b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/WipeDataItemTest.kt
index e02d554..5b8f450 100644
--- a/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/WipeDataItemTest.kt
+++ b/device-manager/testSrc/com/android/tools/idea/devicemanager/virtualtab/WipeDataItemTest.kt
@@ -19,8 +19,8 @@
import com.android.testutils.MockitoKt.mock
import com.android.testutils.MockitoKt.whenever
import com.android.tools.idea.avdmanager.AvdManagerConnection
-import com.android.tools.idea.devicemanager.CountDownLatchAssert
-import com.android.tools.idea.devicemanager.CountDownLatchFutureCallback
+import com.android.tools.idea.concurrency.CountDownLatchAssert
+import com.android.tools.idea.concurrency.CountDownLatchFutureCallback
import com.android.tools.idea.devicemanager.Key
import com.google.common.util.concurrent.FutureCallback
import com.google.common.util.concurrent.Futures.immediateFuture
diff --git a/execution/common/BUILD b/execution/common/BUILD
index a3d16f5..d480792 100644
--- a/execution/common/BUILD
+++ b/execution/common/BUILD
@@ -1,4 +1,5 @@
load("//tools/base/bazel:bazel.bzl", "iml_module")
+load("//tools/base/bazel:maven.bzl", "maven_repository")
# managed by go/iml_to_build
iml_module(
@@ -36,9 +37,16 @@
test_class = "com.android.tools.idea.execution.common.AndroidExecutionCommonTestSuite",
# keep sorted
test_data = [
+ ":test_deps",
+ "//prebuilts/studio/sdk:build-tools/latest",
"//prebuilts/studio/sdk:platform-tools",
"//prebuilts/studio/sdk:platforms/latest",
+ "//tools/adt/idea/android/annotations",
"//tools/adt/idea/android/testData",
+ "//tools/base/build-system:android_gradle_plugin.zip",
+ "//tools/base/build-system:android_gradle_plugin_runtime_dependencies",
+ "//tools/base/build-system:gradle-distrib",
+ "//tools/base/build-system/integration-test:kotlin_gradle_plugin_prebuilts",
],
test_srcs = ["testSrc"],
visibility = ["//visibility:public"],
@@ -69,3 +77,15 @@
"//tools/base/adblib:studio.android.sdktools.adblib[module, test]",
],
)
+
+maven_repository(
+ name = "test_deps",
+ # keep sorted: for buildifier
+ artifacts = [
+ "@maven//:com.android.support.appcompat-v7_28.0.0",
+ "@maven//:com.android.support.constraint.constraint-layout_1.0.2",
+ "@maven//:com.android.support.test.espresso.espresso-core_3.0.2",
+ "@maven//:com.android.support.test.runner_1.0.2",
+ "@maven//:com.google.guava.guava_19.0",
+ ],
+)
diff --git a/execution/common/src/com/android/tools/idea/execution/common/applychanges/ApplyChangesAction.java b/execution/common/src/com/android/tools/idea/execution/common/applychanges/ApplyChangesAction.java
index 8b2f079..28ea4e7 100644
--- a/execution/common/src/com/android/tools/idea/execution/common/applychanges/ApplyChangesAction.java
+++ b/execution/common/src/com/android/tools/idea/execution/common/applychanges/ApplyChangesAction.java
@@ -17,7 +17,9 @@
import static icons.StudioIcons.Shell.Toolbar.APPLY_ALL_CHANGES;
+import com.android.ddmlib.IDevice;
import com.android.tools.idea.execution.common.AndroidExecutionTarget;
+import com.android.tools.idea.execution.common.AndroidSessionInfo;
import com.android.tools.idea.execution.common.UtilsKt;
import com.android.tools.idea.run.util.SwapInfo;
import com.intellij.execution.ExecutionTargetManager;
@@ -32,7 +34,11 @@
import com.intellij.openapi.actionSystem.Shortcut;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.SystemInfo;
+import com.intellij.xdebugger.XDebugSession;
+import com.intellij.xdebugger.XDebuggerManager;
+import java.util.Arrays;
import java.util.List;
+import java.util.stream.Stream;
import javax.swing.KeyStroke;
import org.jetbrains.annotations.NotNull;
@@ -73,19 +79,22 @@
return;
}
- // Disable "Apply Changes" for any kind of test project.
- RunnerAndConfigurationSettings runConfig = RunManager.getInstance(project).getSelectedConfiguration();
AndroidExecutionTarget selectedExecutionTarget = (AndroidExecutionTarget)ExecutionTargetManager.getActiveTarget(project);
- final List<ProcessHandler> runningProcessHandlers =
- UtilsKt.getProcessHandlersForDevices(runConfig, project, selectedExecutionTarget.getRunningDevices().stream().toList());
+ final List<IDevice> devices = selectedExecutionTarget.getRunningDevices().stream().toList();
- final List<Executor> executors = getRunningExecutorsOfDifferentType(project, runningProcessHandlers);
+ final List<ProcessHandler> debugProcessHandlers =
+ Arrays.stream(XDebuggerManager.getInstance(project).getDebugSessions()).map(x -> x.getDebugProcess().getProcessHandler()).toList();
- // otherwise should be disabled by [BaseAction].
- assert executors.size() <= 1;
+ final boolean debuggerConnected = debugProcessHandlers.stream().anyMatch(processHandler -> {
+ final AndroidSessionInfo sessionInfo = AndroidSessionInfo.Companion.from(processHandler);
+ if (sessionInfo == null) {
+ return false;
+ }
+ return devices.stream().anyMatch(device -> sessionInfo.getDevices().contains(device));
+ });
- if (!executors.isEmpty() && executors.get(0) == DefaultDebugExecutor.getDebugExecutorInstance()) {
+ if (debuggerConnected) {
disableAction(e.getPresentation(), new DisableMessage(DisableMessage.DisableMode.DISABLED, "debug execution",
"it is currently not allowed during debugging"));
}
diff --git a/execution/common/testSrc/com/android/tools/idea/execution/common/AndroidExecutionCommonTestSuite.java b/execution/common/testSrc/com/android/tools/idea/execution/common/AndroidExecutionCommonTestSuite.java
index ef32ebc..aa308e7 100644
--- a/execution/common/testSrc/com/android/tools/idea/execution/common/AndroidExecutionCommonTestSuite.java
+++ b/execution/common/testSrc/com/android/tools/idea/execution/common/AndroidExecutionCommonTestSuite.java
@@ -16,10 +16,28 @@
package com.android.tools.idea.execution.common;
import com.android.testutils.JarTestSuiteRunner;
+import com.android.tools.tests.GradleDaemonsRule;
import com.android.tools.tests.IdeaTestSuiteBase;
+import com.intellij.ui.IconManager;
+import com.intellij.ui.icons.CoreIconManager;
+import org.junit.ClassRule;
import org.junit.runner.RunWith;
@RunWith(JarTestSuiteRunner.class)
public class AndroidExecutionCommonTestSuite extends IdeaTestSuiteBase {
+ @ClassRule public static GradleDaemonsRule gradle = new GradleDaemonsRule();
+ static {
+ unzipIntoOfflineMavenRepo("tools/base/build-system/android_gradle_plugin.zip");
+ linkIntoOfflineMavenRepo("tools/base/build-system/android_gradle_plugin_runtime_dependencies.manifest");
+ linkIntoOfflineMavenRepo("tools/adt/idea/execution/common/test_deps.manifest");
+ linkIntoOfflineMavenRepo("tools/base/build-system/integration-test/kotlin_gradle_plugin_prebuilts.manifest");
+ // Avoid depending on the execution order and initializing icons with dummies.
+ try {
+ IconManager.Companion.activate(new CoreIconManager());
+ }
+ catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
}
diff --git a/execution/common/testSrc/com/android/tools/idea/execution/common/applychanges/ApplyChangesActionTest.kt b/execution/common/testSrc/com/android/tools/idea/execution/common/applychanges/ApplyChangesActionTest.kt
new file mode 100644
index 0000000..6c9229a
--- /dev/null
+++ b/execution/common/testSrc/com/android/tools/idea/execution/common/applychanges/ApplyChangesActionTest.kt
@@ -0,0 +1,121 @@
+/*
+ * 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.execution.common.applychanges
+
+import com.android.ddmlib.Client
+import com.android.ddmlib.IDevice
+import com.android.sdklib.AndroidVersion
+import com.android.testutils.MockitoKt.mock
+import com.android.testutils.MockitoKt.whenever
+import com.android.tools.idea.execution.common.AndroidExecutionTarget
+import com.android.tools.idea.execution.common.AndroidSessionInfo
+import com.android.tools.idea.execution.common.debug.createFakeExecutionEnvironment
+import com.android.tools.idea.execution.common.processhandler.AndroidRemoteDebugProcessHandler
+import com.android.tools.idea.gradle.project.sync.snapshots.AndroidCoreTestProject
+import com.android.tools.idea.run.DeploymentApplicationService
+import com.android.tools.idea.testing.AndroidProjectRule
+import com.android.tools.idea.testing.onEdt
+import com.google.common.truth.Truth.assertThat
+import com.intellij.execution.ExecutionTargetManager
+import com.intellij.execution.RunManager
+import com.intellij.execution.configurations.RunConfiguration
+import com.intellij.openapi.actionSystem.CommonDataKeys
+import com.intellij.openapi.actionSystem.impl.SimpleDataContext
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.testFramework.RunsInEdt
+import com.intellij.testFramework.TestActionEvent
+import com.intellij.testFramework.replaceService
+import com.intellij.xdebugger.XDebugProcess
+import com.intellij.xdebugger.XDebugProcessStarter
+import com.intellij.xdebugger.XDebugSession
+import com.intellij.xdebugger.XDebuggerManager
+import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider
+import org.junit.Rule
+import org.junit.Test
+
+
+class ApplyChangesActionTest {
+ @get:Rule
+ val projectRule = AndroidProjectRule.testProject(AndroidCoreTestProject.SIMPLE_APPLICATION).onEdt()
+
+ private val APPLICATION_ID = "google.simpleapplication"
+
+ @Test
+ @RunsInEdt
+ fun disabledDuringDebugSession() {
+ // Set up
+ val device = createMockDevice()
+ val service = mock<DeploymentApplicationService>().also {
+ whenever(it.findClient(device, APPLICATION_ID)).thenReturn(listOf(mock<Client>()))
+ }
+ ApplicationManager.getApplication()
+ .replaceService(DeploymentApplicationService::class.java, service, projectRule.testRootDisposable)
+ setTarget(device, RunManager.getInstance(projectRule.project).selectedConfiguration!!.configuration)
+
+ val client = mock<Client>()
+ whenever(client.device).thenReturn(device)
+
+ val debugProcessHandler = AndroidRemoteDebugProcessHandler(projectRule.project, client, false)
+ AndroidSessionInfo.create(debugProcessHandler, listOf(device), APPLICATION_ID)
+
+ val executionEnvironment = createFakeExecutionEnvironment(projectRule.project, "fake env")
+
+ // Start debug session
+ XDebuggerManager.getInstance(projectRule.project).startSession(executionEnvironment, object : XDebugProcessStarter() {
+ override fun start(session: XDebugSession) = object : XDebugProcess(session) {
+ override fun getEditorsProvider() = mock<XDebuggerEditorsProvider>()
+ override fun doGetProcessHandler() = debugProcessHandler
+ }
+ })
+
+ // Update
+ val action = ApplyChangesAction()
+ val event = TestActionEvent.createTestEvent(SimpleDataContext.getSimpleContext(CommonDataKeys.PROJECT, projectRule.project))
+ action.update(event)
+
+ // Assert
+ assertThat(event.presentation.isEnabled).isFalse()
+ assertThat(event.presentation.description).isEqualTo("Apply Changes and Restart Activity is disabled for this device because it is currently not allowed during debugging.")
+ }
+
+ private fun setTarget(device: IDevice, config: RunConfiguration) {
+ val executionTarget = object : AndroidExecutionTarget() {
+ override fun getId() = "Test Target"
+
+ override fun getDisplayName() = "Test Target"
+
+ override fun getIcon() = null
+
+ override fun getAvailableDeviceCount() = 1
+
+ override fun getRunningDevices() = listOf(device)
+ }
+
+ val executionTargetManager = mock<ExecutionTargetManager>()
+ .also {
+ whenever(it.activeTarget).thenReturn(executionTarget)
+ whenever(it.getTargetsFor(config)).thenReturn(listOf(executionTarget))
+ }
+ projectRule.project.replaceService(ExecutionTargetManager::class.java, executionTargetManager, projectRule.testRootDisposable)
+ }
+
+ private fun createMockDevice(): IDevice {
+ val mockDevice = mock<IDevice>()
+ whenever(mockDevice.isOnline).thenReturn(true)
+ whenever(mockDevice.version).thenReturn(AndroidVersion(33))
+ return mockDevice
+ }
+}
\ No newline at end of file
diff --git a/execution/common/testSrc/stats/RunStatsUtilsTest.kt b/execution/common/testSrc/stats/RunStatsUtilsTest.kt
index 991465f..55b0c00 100644
--- a/execution/common/testSrc/stats/RunStatsUtilsTest.kt
+++ b/execution/common/testSrc/stats/RunStatsUtilsTest.kt
@@ -54,7 +54,7 @@
isRemote = true
isVirtual = false
icon = StudioDefaultDeviceIcons.handheld
- populateDeviceInfoProto("TestPlugin", "localhost:12345", emptyMap())
+ populateDeviceInfoProto("TestPlugin", "localhost:12345", emptyMap(), "connectionId")
}
private val deviceHandle = object : DeviceHandle {
override val scope: CoroutineScope
diff --git a/gradle-tooling/studio-gradle-tooling-impl/src/com/android/ide/gradle/model/artifacts/builder/AdditionalClassifierArtifactsModelBuilder.kt b/gradle-tooling/studio-gradle-tooling-impl/src/com/android/ide/gradle/model/artifacts/builder/AdditionalClassifierArtifactsModelBuilder.kt
index b9c70e3..bcf94b6 100644
--- a/gradle-tooling/studio-gradle-tooling-impl/src/com/android/ide/gradle/model/artifacts/builder/AdditionalClassifierArtifactsModelBuilder.kt
+++ b/gradle-tooling/studio-gradle-tooling-impl/src/com/android/ide/gradle/model/artifacts/builder/AdditionalClassifierArtifactsModelBuilder.kt
@@ -16,23 +16,24 @@
package com.android.ide.gradle.model.artifacts.builder
import com.android.ide.gradle.model.AdditionalClassifierArtifactsModelParameter
+import com.android.ide.gradle.model.ArtifactIdentifier
import com.android.ide.gradle.model.ArtifactIdentifierImpl
-import com.android.ide.gradle.model.artifacts.AdditionalClassifierArtifacts
import com.android.ide.gradle.model.artifacts.AdditionalClassifierArtifactsModel
import com.android.ide.gradle.model.artifacts.AdditionalClassifierArtifactsModel.SAMPLE_SOURCE_CLASSIFIER
import com.android.ide.gradle.model.artifacts.impl.AdditionalClassifierArtifactsImpl
import com.android.ide.gradle.model.artifacts.impl.AdditionalClassifierArtifactsModelImpl
import org.gradle.api.Project
+import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ModuleIdentifier
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.result.ComponentArtifactsResult
import org.gradle.api.artifacts.result.ResolvedArtifactResult
+import org.gradle.api.artifacts.type.ArtifactTypeDefinition
+import org.gradle.api.attributes.AttributeContainer
+import org.gradle.api.attributes.Category
import org.gradle.api.attributes.DocsType
import org.gradle.api.component.Artifact
import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
-import org.gradle.jvm.JvmLibrary
-import org.gradle.language.base.artifact.SourcesArtifact
-import org.gradle.language.java.artifact.JavadocArtifact
import org.gradle.maven.MavenModule
import org.gradle.maven.MavenPomArtifact
import org.gradle.tooling.provider.model.ParameterizedToolingModelBuilder
@@ -59,8 +60,38 @@
}
override fun buildAll(modelName: String, parameter: AdditionalClassifierArtifactsModelParameter, project: Project): Any {
- // Collect the components to download Sources and Javadoc for. DefaultModuleComponentIdentifier is the only supported type.
- // See DefaultArtifactResolutionQuery::validateComponentIdentifier.
+ if (parameter.artifactIdentifiers.isEmpty()) {
+ return AdditionalClassifierArtifactsModelImpl(emptyList(), null)
+ }
+
+ try {
+ val idToPomFile = getPomFiles(parameter, project)
+ val idToJavadoc = getArtifacts(project, DocsType.JAVADOC, parameter.artifactIdentifiers)
+ val idToSources = getArtifacts(project, DocsType.SOURCES, parameter.artifactIdentifiers)
+ val idToSampleLocation = getSampleSources(parameter, project)
+
+ val artifacts = parameter.artifactIdentifiers.map {
+ val artifactId = ArtifactIdentifierImpl(it.groupId, it.artifactId, it.version)
+ AdditionalClassifierArtifactsImpl(
+ id = artifactId,
+ javadoc = idToJavadoc[artifactId],
+ sources = idToSources[artifactId],
+ mavenPom = idToPomFile[artifactId],
+ sampleSources = idToSampleLocation[artifactId],
+ )
+ }
+ return AdditionalClassifierArtifactsModelImpl(artifacts, null)
+ }
+ catch (t: Throwable) {
+ return AdditionalClassifierArtifactsModelImpl(emptyList(), "Unable to download sources/javadoc: " + t.message)
+ }
+ }
+
+ private fun getPomFiles(
+ parameter: AdditionalClassifierArtifactsModelParameter,
+ project: Project
+ ): Map<ArtifactIdentifierImpl, File?> {
+ // Create query for Maven Pom File.
val ids = parameter.artifactIdentifiers.map {
DefaultModuleComponentIdentifier(
object : ModuleIdentifier {
@@ -70,82 +101,56 @@
)
}
- var artifacts = emptyList<AdditionalClassifierArtifacts>()
- var message: String? = null
+ val pomQuery = project.dependencies.createArtifactResolutionQuery()
+ .forComponents(ids)
+ .withArtifacts(MavenModule::class.java, MavenPomArtifact::class.java)
- if (ids.isEmpty()) {
- return AdditionalClassifierArtifactsModelImpl(artifacts, message)
+ fun getFile(result: ComponentArtifactsResult, clazz: Class<out Artifact>): File? {
+ return result.getArtifacts(clazz)
+ .filterIsInstance(ResolvedArtifactResult::class.java).firstOrNull()
+ ?.file
}
- try {
- // Create query for Maven Pom File.
- val pomQuery = project.dependencies.createArtifactResolutionQuery()
- .forComponents(ids)
- .withArtifacts(MavenModule::class.java, MavenPomArtifact::class.java)
-
- // Map from component id to Pom File.
- val idToPomFile = pomQuery.execute().resolvedComponents.map {
- it.id.displayName to getFile(it, MavenPomArtifact::class.java)
- }.toMap()
-
- // Create map from component id to location of sample sources file.
- val idToSampleLocation: Map<String, File?> =
- if (parameter.downloadAndroidxUISamplesSources) {
- getSampleSources(parameter, project)
- }
- else {
- emptyMap()
- }
-
- // Create query for Javadoc and Sources.
- val docQuery = project.dependencies.createArtifactResolutionQuery()
- .forComponents(ids)
- .withArtifacts(JvmLibrary::class.java, SourcesArtifact::class.java, JavadocArtifact::class.java)
-
- artifacts = docQuery.execute().resolvedComponents.filter { it.id is ModuleComponentIdentifier }.map {
- val id = it.id as ModuleComponentIdentifier
- AdditionalClassifierArtifactsImpl(
- ArtifactIdentifierImpl(id.group, id.module, id.version),
- getFile(it, SourcesArtifact::class.java),
- getFile(it, JavadocArtifact::class.java),
- idToPomFile[it.id.displayName],
- idToSampleLocation[it.id.displayName]
- )
- }
+ // Map from component id to Pom File.
+ return pomQuery.execute().resolvedComponents.associate {
+ val id = it.id as ModuleComponentIdentifier
+ ArtifactIdentifierImpl(id.group, id.module, id.version) to getFile(it, MavenPomArtifact::class.java)
}
- catch (t: Throwable) {
- message = "Unable to download sources/javadoc: " + t.message
- }
- return AdditionalClassifierArtifactsModelImpl(artifacts, message)
}
- private fun getSampleSources(parameter: AdditionalClassifierArtifactsModelParameter, project: Project): Map<String, File?> {
- val detachedConfiguration = project.configurations.detachedConfiguration()
- parameter.artifactIdentifiers
- // Only androidx.ui and androidx.compose use the @Sampled annotation as of today (January 2020).
+ private fun getArtifacts(project: Project,
+ docsType: String,
+ artifactIdentifiers: Collection<ArtifactIdentifier>): Map<ArtifactIdentifierImpl, File> {
+ val resolvableConfiguration = project.configurations.detachedConfiguration().also {
+ it.attributes.run<AttributeContainer, Unit> {
+ attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category::class.java, Category.DOCUMENTATION))
+ attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE)
+ attribute(DocsType.DOCS_TYPE_ATTRIBUTE, project.objects.named(DocsType::class.java, docsType))
+ }
+ }
+ artifactIdentifiers.asDependencies(project).forEach {
+ resolvableConfiguration.dependencies.add(it)
+ }
+ return resolvableConfiguration.incoming.artifactView { view ->
+ view.lenient(true)
+ view.componentFilter { it is ModuleComponentIdentifier }
+ }.artifacts.associate {
+ val id = it.id.componentIdentifier as ModuleComponentIdentifier
+ ArtifactIdentifierImpl(id.group, id.module, id.version) to it.file
+ }
+ }
+
+ private fun getSampleSources(parameter: AdditionalClassifierArtifactsModelParameter, project: Project): Map<ArtifactIdentifierImpl, File> {
+ if (!parameter.downloadAndroidxUISamplesSources) return emptyMap()
+
+ // Only androidx.ui and androidx.compose use the @Sampled annotation as of today (January 2020).
+ val artifactsWithSamples = parameter.artifactIdentifiers
.filter { it.groupId.startsWith("androidx.ui") || it.groupId.startsWith("androidx.compose") }
- .forEach {
- val dependency = project.dependencies.create(it.groupId + ":" + it.artifactId + ":" + it.version)
- detachedConfiguration.dependencies.add(dependency)
- }
- detachedConfiguration.attributes.attribute(
- DocsType.DOCS_TYPE_ATTRIBUTE,
- project.objects.named(DocsType::class.java, SAMPLE_SOURCE_CLASSIFIER))
- val samples = mutableMapOf<String, File?>()
-
- detachedConfiguration.incoming.artifactView {
- it.lenient(true) // this will make it not fail if something does not have samples
- }.artifacts.forEach {
- val id = it.id.componentIdentifier.displayName
- samples[id] = it.file
- }
- return samples
+ return getArtifacts(project, SAMPLE_SOURCE_CLASSIFIER, artifactsWithSamples)
}
- private fun getFile(result: ComponentArtifactsResult, clazz: Class<out Artifact>): File? {
- return result.getArtifacts(clazz)
- .filterIsInstance(ResolvedArtifactResult::class.java).firstOrNull()
- ?.file
+ private fun Collection<ArtifactIdentifier>.asDependencies(project: Project): Sequence<Dependency> {
+ return asSequence().map { project.dependencies.create(it.groupId + ":" + it.artifactId + ":" + it.version) }
}
}
\ No newline at end of file
diff --git a/kotlin-integration/testSrc/com/android/tools/idea/KotlinPluginTest.kt b/kotlin-integration/testSrc/com/android/tools/idea/KotlinPluginTest.kt
index cb48dec..29096c8 100644
--- a/kotlin-integration/testSrc/com/android/tools/idea/KotlinPluginTest.kt
+++ b/kotlin-integration/testSrc/com/android/tools/idea/KotlinPluginTest.kt
@@ -17,6 +17,7 @@
import com.google.common.truth.Truth.assertThat
import com.intellij.ide.plugins.IdeaPluginDescriptor
+import com.intellij.ide.plugins.PluginManager
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.application.ex.ApplicationInfoEx
import com.intellij.openapi.extensions.PluginId
@@ -25,6 +26,7 @@
import org.jetbrains.kotlin.idea.compiler.configuration.KotlinIdePluginVersion
import org.jetbrains.kotlin.idea.compiler.configuration.KotlinPluginLayout
import org.junit.ClassRule
+import org.junit.Ignore
import org.junit.Test
class KotlinPluginTest {
@@ -69,4 +71,15 @@
assertThat(apiVersion.asStringWithoutProductCode()).matches("\\d+\\.\\d+\\.\\d+")
assertThat(PluginManagerCore.checkBuildNumberCompatibility(plugin, apiVersion)).isNull()
}
+
+ @Test
+ @Ignore("Cannot accurately run in Bazel until b/270757509 is fixed")
+ fun testPluginCannotBeDisabled() {
+ val id = "org.jetbrains.kotlin"
+ val plugin = PluginManagerCore.getPlugin(PluginId.getId(id))
+ checkNotNull(plugin)
+ assertThat(plugin.isEnabled).isTrue()
+ PluginManager.disablePlugin(id)
+ assertThat(plugin.isEnabled).isTrue()
+ }
}
diff --git a/layout-inspector/src/com/android/tools/idea/layoutinspector/LayoutInspectorProjectService.kt b/layout-inspector/src/com/android/tools/idea/layoutinspector/LayoutInspectorProjectService.kt
index 21eced3..8d77b54 100644
--- a/layout-inspector/src/com/android/tools/idea/layoutinspector/LayoutInspectorProjectService.kt
+++ b/layout-inspector/src/com/android/tools/idea/layoutinspector/LayoutInspectorProjectService.kt
@@ -35,9 +35,11 @@
import com.android.tools.idea.layoutinspector.pipeline.foregroundprocessdetection.ForegroundProcessDetectionInitializer
import com.android.tools.idea.layoutinspector.settings.LayoutInspectorSettings
import com.android.tools.idea.layoutinspector.tree.InspectorTreeSettings
+import com.android.tools.idea.transport.manager.TransportStreamManagerService
import com.google.common.annotations.VisibleForTesting
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.util.concurrency.EdtExecutorService
@@ -187,6 +189,7 @@
processModel = processesModel,
deviceModel = deviceModel,
coroutineScope = coroutineScope,
+ streamManager = service<TransportStreamManagerService>().streamManager,
metrics = ForegroundProcessDetectionMetrics
)
} else {
diff --git a/layout-inspector/src/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionImpl.kt b/layout-inspector/src/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionImpl.kt
index 367ce2b..7b05b60 100644
--- a/layout-inspector/src/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionImpl.kt
+++ b/layout-inspector/src/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionImpl.kt
@@ -116,6 +116,7 @@
private val layoutInspectorMetrics: LayoutInspectorMetrics,
private val metrics: ForegroundProcessDetectionMetrics,
private val scope: CoroutineScope,
+ private val streamManager: TransportStreamManager,
workDispatcher: CoroutineDispatcher = AndroidDispatchers.workerThread,
@TestOnly private val onDeviceDisconnected: (DeviceDescriptor) -> Unit = {},
@TestOnly private val pollingIntervalMs: Long = 2000
@@ -222,11 +223,8 @@
}
}
- val manager =
- TransportStreamManager.createManager(transportClient.transportStub, workDispatcher)
-
scope.launch {
- manager.streamActivityFlow().collect { activity ->
+ streamManager.streamActivityFlow().collect { activity ->
val streamChannel = activity.streamChannel
val streamDevice = streamChannel.stream.device.toDeviceDescriptor()
val stream = streamChannel.stream
@@ -453,12 +451,8 @@
*
* @see ForegroundProcessDetectionImpl.deviceModels
*/
- private fun shouldStopPollingDevice(selectedDevice: DeviceDescriptor): Boolean {
- val deviceModels = ForegroundProcessDetectionImpl.deviceModels
- val count =
- deviceModels.mapNotNull { it.selectedDevice }.count { it.serial == selectedDevice.serial }
- return count <= 1
- }
+ private fun shouldStopPollingDevice(selectedDevice: DeviceDescriptor) =
+ deviceModels.mapNotNull { it.selectedDevice }.count { it.serial == selectedDevice.serial } <= 1
/**
* Initiates a new handshake. Only if [device] already executed the handshake that happens at
diff --git a/layout-inspector/src/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionInitializer.kt b/layout-inspector/src/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionInitializer.kt
index 3a70287..e4e3397 100644
--- a/layout-inspector/src/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionInitializer.kt
+++ b/layout-inspector/src/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionInitializer.kt
@@ -21,6 +21,7 @@
import com.android.tools.idea.layoutinspector.metrics.LayoutInspectorMetrics
import com.android.tools.idea.transport.TransportClient
import com.android.tools.idea.transport.TransportService
+import com.android.tools.idea.transport.manager.TransportStreamManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import kotlinx.coroutines.CoroutineScope
@@ -84,6 +85,7 @@
processModel: ProcessesModel,
deviceModel: DeviceModel,
coroutineScope: CoroutineScope,
+ streamManager: TransportStreamManager,
foregroundProcessListener: ForegroundProcessListener =
getDefaultForegroundProcessListener(deviceModel, processModel),
transportClient: TransportClient = getDefaultTransportClient(),
@@ -98,7 +100,8 @@
transportClient,
layoutInspectorMetrics,
metrics,
- coroutineScope
+ coroutineScope,
+ streamManager
)
foregroundProcessDetection.addForegroundProcessListener(foregroundProcessListener)
diff --git a/layout-inspector/src/com/android/tools/idea/layoutinspector/runningdevices/LayoutInspectorManager.kt b/layout-inspector/src/com/android/tools/idea/layoutinspector/runningdevices/LayoutInspectorManager.kt
index 91f6e3e..7880159 100644
--- a/layout-inspector/src/com/android/tools/idea/layoutinspector/runningdevices/LayoutInspectorManager.kt
+++ b/layout-inspector/src/com/android/tools/idea/layoutinspector/runningdevices/LayoutInspectorManager.kt
@@ -139,7 +139,7 @@
// Dispose to trigger clean up.
Disposer.dispose(previousTab.tabComponents)
- previousTab.layoutInspector.deviceModel?.selectedDevice = null
+ previousTab.layoutInspector.stopInspector()
}
field = value
diff --git a/layout-inspector/testSrc/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionInitializerTest.kt b/layout-inspector/testSrc/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionInitializerTest.kt
index fb5447d..2855d9c 100644
--- a/layout-inspector/testSrc/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionInitializerTest.kt
+++ b/layout-inspector/testSrc/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionInitializerTest.kt
@@ -30,6 +30,7 @@
import com.android.tools.idea.transport.faketransport.FakeGrpcServer
import com.android.tools.idea.transport.faketransport.FakeTransportService
import com.android.tools.idea.transport.faketransport.commands.CommandHandler
+import com.android.tools.idea.transport.manager.TransportStreamManagerRule
import com.android.tools.profiler.proto.Commands
import com.android.tools.profiler.proto.Common
import com.google.common.truth.Truth.assertThat
@@ -43,17 +44,20 @@
import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import org.junit.rules.RuleChain
class ForegroundProcessDetectionInitializerTest {
private val timer = FakeTimer()
private val transportService = FakeTransportService(timer, false)
- @get:Rule
- val grpcServerRule =
+ private val grpcServerRule =
FakeGrpcServer.createFakeGrpcServer(
"ForegroundProcessDetectionInitializerTest",
transportService
)
+ private val streamManagerRule = TransportStreamManagerRule(grpcServerRule)
+
+ @get:Rule val ruleChain = RuleChain.outerRule(grpcServerRule).around(streamManagerRule)
private val device1 = FakeDevice(serial = "1")
private val device2 = FakeDevice(serial = "2")
@@ -104,6 +108,7 @@
processModel = processModel,
deviceModel = deviceModel,
coroutineScope = CoroutineScope(SameThreadExecutor.INSTANCE.asCoroutineDispatcher()),
+ streamManager = streamManagerRule.streamManager,
foregroundProcessListener = foregroundProcessListener,
metrics = ForegroundProcessDetectionMetrics,
)
@@ -191,7 +196,8 @@
project = projectRule.project,
processModel = processModel,
deviceModel = deviceModel,
- coroutineScope = projectRule.project.coroutineScope,
+ coroutineScope = CoroutineScope(SameThreadExecutor.INSTANCE.asCoroutineDispatcher()),
+ streamManager = streamManagerRule.streamManager,
transportClient = transportClient,
metrics = ForegroundProcessDetectionMetrics,
)
diff --git a/layout-inspector/testSrc/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionTest.kt b/layout-inspector/testSrc/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionTest.kt
index b0b2a24..d2a7566 100644
--- a/layout-inspector/testSrc/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionTest.kt
+++ b/layout-inspector/testSrc/com/android/tools/idea/layoutinspector/pipeline/foregroundprocessdetection/ForegroundProcessDetectionTest.kt
@@ -31,16 +31,17 @@
import com.android.tools.idea.layoutinspector.pipeline.adb.AdbDebugViewProperties
import com.android.tools.idea.layoutinspector.pipeline.adb.FakeShellCommandHandler
import com.android.tools.idea.layoutinspector.pipeline.appinspection.DebugViewAttributes
+import com.android.tools.idea.testing.disposable
import com.android.tools.idea.transport.TransportClient
import com.android.tools.idea.transport.faketransport.FakeGrpcServer
import com.android.tools.idea.transport.faketransport.FakeTransportService
import com.android.tools.idea.transport.faketransport.commands.CommandHandler
+import com.android.tools.idea.transport.manager.TransportStreamManagerRule
import com.android.tools.profiler.proto.Commands.Command
import com.android.tools.profiler.proto.Common
import com.google.common.truth.Truth.assertThat
import com.google.wireless.android.sdk.stats.DynamicLayoutInspectorTransportError
import com.intellij.openapi.util.Disposer
-import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.ProjectRule
import com.intellij.util.containers.reverse
import java.util.concurrent.atomic.AtomicLong
@@ -63,24 +64,24 @@
import org.mockito.Mockito.verifyNoMoreInteractions
class ForegroundProcessDetectionTest {
- @get:Rule val disposableRule = DisposableRule()
-
private val projectRule = ProjectRule()
-
private val adbRule = FakeAdbRule()
private val adbProperties: AdbDebugViewProperties =
FakeShellCommandHandler().apply { adbRule.withDeviceCommandHandler(this) }
private val adbService = AdbServiceRule(projectRule::project, adbRule)
-
- @get:Rule
- val ruleChain: RuleChain = RuleChain.outerRule(projectRule).around(adbRule).around(adbService)
-
private val timer = FakeTimer()
private val transportService = FakeTransportService(timer, false)
+ private val grpcServerRule =
+ FakeGrpcServer.createFakeGrpcServer("ForegroundProcessDetectionTest", transportService)
+ private val streamManagerRule = TransportStreamManagerRule(grpcServerRule)
@get:Rule
- val grpcServerRule =
- FakeGrpcServer.createFakeGrpcServer("ForegroundProcessDetectionTest", transportService)
+ val ruleChain: RuleChain =
+ RuleChain.outerRule(projectRule)
+ .around(adbRule)
+ .around(adbService)
+ .around(grpcServerRule)
+ .around(streamManagerRule)
private val timestampGenerator = AtomicLong()
@@ -171,7 +172,7 @@
workDispatcher = AndroidDispatchers.workerThread
transportClient = TransportClient(grpcServerRule.name)
- coroutineScope = AndroidCoroutineScope(disposableRule.disposable)
+ coroutineScope = AndroidCoroutineScope(projectRule.disposable)
// mock device response to handshake command
transportService.setCommandHandler(
@@ -228,6 +229,7 @@
mock(),
mock(),
coroutineScope,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected = {},
pollingIntervalMs = 500L
@@ -271,8 +273,8 @@
val (deviceModel1, processModel1) = createDeviceModel(device1)
val (deviceModel2, processModel2) = createDeviceModel(device1)
- val coroutineScope1 = AndroidCoroutineScope(disposableRule.disposable)
- val coroutineScope2 = AndroidCoroutineScope(disposableRule.disposable)
+ val coroutineScope1 = AndroidCoroutineScope(projectRule.disposable)
+ val coroutineScope2 = AndroidCoroutineScope(projectRule.disposable)
// studio1
val foregroundProcessDetection1 =
@@ -284,6 +286,7 @@
mock(),
mock(),
coroutineScope1,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected = {},
pollingIntervalMs = 500L
@@ -299,6 +302,7 @@
mock(),
mock(),
coroutineScope2,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected = {},
pollingIntervalMs = 500L
@@ -360,6 +364,7 @@
mock(),
mock(),
coroutineScope,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected = {},
pollingIntervalMs = 500L
@@ -415,6 +420,7 @@
mock(),
mock(),
coroutineScope,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected = {},
pollingIntervalMs = 500L
@@ -459,6 +465,7 @@
mock(),
mock(),
coroutineScope,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected = {},
pollingIntervalMs = 500L
@@ -553,6 +560,7 @@
mock(),
mock(),
coroutineScope,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected,
pollingIntervalMs = 500L
@@ -592,6 +600,7 @@
mock(),
mock(),
coroutineScope,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected = {},
pollingIntervalMs = 500L
@@ -683,6 +692,7 @@
mock(),
mock(),
coroutineScope,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected = {},
pollingIntervalMs = 500L
@@ -763,6 +773,7 @@
mock(),
mock(),
coroutineScope,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected = {},
pollingIntervalMs = 500L
@@ -804,6 +815,7 @@
mock(),
mock(),
coroutineScope,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected = {},
pollingIntervalMs = 500L
@@ -868,6 +880,7 @@
layoutInspectorMetrics,
mock(),
coroutineScope,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected = onDeviceDisconnected,
pollingIntervalMs = 500L
@@ -933,6 +946,7 @@
mock(),
mock(),
coroutineScope,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected = {},
pollingIntervalMs = 500L
@@ -978,6 +992,7 @@
mock(),
mock(),
coroutineScope,
+ streamManagerRule.streamManager,
workDispatcher,
onDeviceDisconnected = {},
pollingIntervalMs = 500L
@@ -1080,7 +1095,7 @@
): Pair<DeviceModel, ProcessesModel> {
devices.forEach { testProcessDiscovery.addDevice(it.toDeviceDescriptor()) }
val processModel = ProcessesModel(testProcessDiscovery)
- return DeviceModel(disposableRule.disposable, processModel) to processModel
+ return DeviceModel(projectRule.disposable, processModel) to processModel
}
private fun createForegroundProcessEvent(
diff --git a/lint/BUILD b/lint/BUILD
index dceb166..b5db901 100644
--- a/lint/BUILD
+++ b/lint/BUILD
@@ -23,12 +23,13 @@
# managed by go/iml_to_build
iml_module(
name = "intellij.lint.tests",
+ generate_k2_tests = True,
iml_files = ["tests/intellij.lint.tests.iml"],
test_class = "com.android.tools.idea.lint.common.LintIdeTestSuite",
test_data = [
+ "//tools/adt/idea/android/annotations",
"//tools/adt/idea/android/testData",
"//tools/adt/idea/lint/tests/testData",
- "//tools/adt/idea/android/annotations",
],
test_resources = ["tests/testData"],
test_srcs = ["tests/testSrc"],
diff --git a/lint/tests/testSrc/com/android/tools/idea/lint/common/AnnotateQuickFixTest.kt b/lint/tests/testSrc/com/android/tools/idea/lint/common/AnnotateQuickFixTest.kt
index 06b89aa..12a4948 100644
--- a/lint/tests/testSrc/com/android/tools/idea/lint/common/AnnotateQuickFixTest.kt
+++ b/lint/tests/testSrc/com/android/tools/idea/lint/common/AnnotateQuickFixTest.kt
@@ -18,9 +18,9 @@
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.psi.PsiElement
import com.intellij.refactoring.suggested.startOffset
-import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase
+import org.jetbrains.android.JavaCodeInsightFixtureAdtTestCase
-class AnnotateQuickFixTest : JavaCodeInsightFixtureTestCase() {
+class AnnotateQuickFixTest : JavaCodeInsightFixtureAdtTestCase() {
fun testKotlinAnnotationArgs() {
val file =
diff --git a/lint/tests/testSrc/com/android/tools/idea/lint/common/LintIdeTest.kt b/lint/tests/testSrc/com/android/tools/idea/lint/common/LintIdeTest.kt
index e9d984a..2bf082c 100644
--- a/lint/tests/testSrc/com/android/tools/idea/lint/common/LintIdeTest.kt
+++ b/lint/tests/testSrc/com/android/tools/idea/lint/common/LintIdeTest.kt
@@ -263,12 +263,17 @@
}
fun testDisabledTestsEnabledOnTheFly() {
- // If this changes test no longer applies; pick different disabled issue
- /* b/214265385
- assertThat(CommentDetector.STOP_SHIP.isEnabledByDefault()).isFalse()
- b/214265385 */
- myFixture.copyFileToProject("$globalTestDir/Stopship.java", "src/p1/p2/Stopship.java")
- doGlobalInspectionTest(AndroidLintStopShipInspection())
+ // Verifies that inspections are force-enabled when the IDE inspection profile mentions them.
+ val wasEnabled = CommentDetector.STOP_SHIP.isEnabledByDefault()
+ try {
+ CommentDetector.STOP_SHIP.setEnabledByDefault(false)
+ myFixture.copyFileToProject("$globalTestDir/Stopship.java", "src/p1/p2/Stopship.java")
+ doGlobalInspectionTest(AndroidLintStopShipInspection())
+ assertThat(CommentDetector.STOP_SHIP.isEnabledByDefault()).isTrue()
+ }
+ finally {
+ CommentDetector.STOP_SHIP.setEnabledByDefault(wasEnabled)
+ }
}
fun testGradleWindows() {
diff --git a/lint/tests/testSrc/com/android/tools/idea/lint/common/ReplaceStringQuickFixTest.kt b/lint/tests/testSrc/com/android/tools/idea/lint/common/ReplaceStringQuickFixTest.kt
index a62ef93..2a60a7b 100644
--- a/lint/tests/testSrc/com/android/tools/idea/lint/common/ReplaceStringQuickFixTest.kt
+++ b/lint/tests/testSrc/com/android/tools/idea/lint/common/ReplaceStringQuickFixTest.kt
@@ -15,20 +15,15 @@
*/
package com.android.tools.idea.lint.common
-import com.android.testutils.TestUtils
import com.android.tools.lint.detector.api.LintFix
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiManager
-import com.intellij.testFramework.builders.JavaModuleFixtureBuilder
-import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase
import com.intellij.util.ThrowableRunnable
+import org.jetbrains.android.JavaCodeInsightFixtureAdtTestCase
-class ReplaceStringQuickFixTest : JavaCodeInsightFixtureTestCase() {
- override fun tuneFixture(builder: JavaModuleFixtureBuilder<*>) {
- builder.addJdk(TestUtils.getMockJdk().toString())
- }
+class ReplaceStringQuickFixTest : JavaCodeInsightFixtureAdtTestCase() {
fun testImportsJava() {
// Unit test for [ReplaceStringQuickFix] import handling of Java files.
diff --git a/logcat/testSrc/com/android/tools/idea/logcat/testing/TestDevice.kt b/logcat/testSrc/com/android/tools/idea/logcat/testing/TestDevice.kt
index 91ddf9e..5c4edd4 100644
--- a/logcat/testSrc/com/android/tools/idea/logcat/testing/TestDevice.kt
+++ b/logcat/testSrc/com/android/tools/idea/logcat/testing/TestDevice.kt
@@ -54,7 +54,7 @@
makeAvdInfo(avdName, manufacturer, model, AndroidVersion(sdk))
) {
icon = StudioIcons.DeviceExplorer.PHYSICAL_DEVICE_PHONE
- populateDeviceInfoProto("Test", null, emptyMap())
+ populateDeviceInfoProto("Test", null, emptyMap(), "connectionId")
}
else ->
DeviceProperties.buildForTest {
diff --git a/mlkit/BUILD b/mlkit/BUILD
index 178833a..cf7dfca 100644
--- a/mlkit/BUILD
+++ b/mlkit/BUILD
@@ -36,6 +36,7 @@
# managed by go/iml_to_build
iml_module(
name = "intellij.android.mlkit.tests",
+ generate_k2_tests = True,
iml_files = ["intellij.android.mlkit.tests.iml"],
tags = [
"no_test_windows", # b/135665870
diff --git a/mlkit/testSrc/com/android/tools/idea/mlkit/MlProjectTestUtil.java b/mlkit/testSrc/com/android/tools/idea/mlkit/MlProjectTestUtil.java
index ddc7cb6..76205e7 100644
--- a/mlkit/testSrc/com/android/tools/idea/mlkit/MlProjectTestUtil.java
+++ b/mlkit/testSrc/com/android/tools/idea/mlkit/MlProjectTestUtil.java
@@ -23,6 +23,8 @@
import com.android.tools.idea.gradle.model.impl.IdeSourceProviderImpl;
import com.android.tools.idea.testing.AndroidModuleModelBuilder;
import com.android.tools.idea.testing.AndroidProjectBuilder;
+import com.android.tools.idea.testing.JavaLibraryDependency;
+import com.android.tools.tests.AdtTestKotlinArtifacts;
import com.google.common.collect.ImmutableList;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
@@ -51,6 +53,18 @@
.withMinSdk(it -> minSdk)
.withMlModelBindingEnabled(it -> true)
.withNamespace("p1.p2")
+ .withJavaLibraryDependencyList(
+ // TODO(b/300170256): Remove this once 2023.3 merges and we no longer need kotlin-stdlib for every Kotlin test.
+ (it, variant) -> {
+ // NB: yes, this is fragile, depending on test name pattern that ends with "kotlin" suffix.
+ // But, it's still helpful to avoid unnecessary lib loading for all other tests.
+ if (it.getProjectName().endsWith("kotlin")) {
+ return ImmutableList.of(JavaLibraryDependency.Companion.forJar(AdtTestKotlinArtifacts.INSTANCE.getKotlinStdlib()));
+ } else {
+ return ImmutableList.of();
+ }
+ }
+ )
.withMainSourceProvider(it -> new IdeSourceProviderImpl(
ARTIFACT_NAME_MAIN,
it.getModuleBasePath(),
diff --git a/nav/editor/lint_baseline.xml b/nav/editor/lint_baseline.xml
index ed1eea1..379fb70 100644
--- a/nav/editor/lint_baseline.xml
+++ b/nav/editor/lint_baseline.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 8.2.0-dev">
+<issues format="5" by="lint 8.3.0-dev">
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`.">
<location
file="src/com/android/tools/idea/naveditor/editor/AddDestinationMenu.kt"
- line="329"/>
+ line="327"/>
</issue>
<issue
@@ -1331,6 +1331,14 @@
<issue
id="VisibleForTests"
+ message="This class should only be accessed from tests or within private scope">
+ <location
+ file="src/com/android/tools/idea/naveditor/analytics/NavUsageTracker.kt"
+ line="36"/>
+ </issue>
+
+ <issue
+ id="VisibleForTests"
message="This method should only be accessed from tests or within private scope">
<location
file="src/com/android/tools/idea/naveditor/analytics/NavUsageTracker.kt"
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/SafeArgsMode.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/SafeArgsMode.kt
index b303df9..a3d4dae 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/SafeArgsMode.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/SafeArgsMode.kt
@@ -23,23 +23,16 @@
import org.jetbrains.annotations.TestOnly
enum class SafeArgsMode {
- /**
- * Safe Args is not enabled for this module.
- */
+ /** Safe Args is not enabled for this module. */
NONE,
- /**
- * Safe Args is enabled for this module and will generate Java classes.
- */
+ /** Safe Args is enabled for this module and will generate Java classes. */
JAVA,
- /**
- * Safe Args is enabled for this module and will generate Kotlin classes.
- */
+ /** Safe Args is enabled for this module and will generate Kotlin classes. */
KOTLIN,
}
-
var AndroidFacet.safeArgsMode: SafeArgsMode
get() = SafeArgsModeModuleService.getInstance(module).safeArgsMode
/**
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/ActionBuilderShortNamesCache.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/ActionBuilderShortNamesCache.kt
index bb1f611..8c49d0b 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/ActionBuilderShortNamesCache.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/ActionBuilderShortNamesCache.kt
@@ -32,7 +32,8 @@
import com.intellij.util.Processor
/**
- * A short names cache for finding any [LightActionBuilderClass] instances by their unqualified name.
+ * A short names cache for finding any [LightActionBuilderClass] instances by their unqualified
+ * name.
*/
class ActionBuilderShortNamesCache(project: Project) : PsiShortNamesCache() {
private val enabledFacetsProvider = SafeArgsEnabledFacetsProjectService.getInstance(project)
@@ -41,21 +42,28 @@
init {
val cachedValuesManager = CachedValuesManager.getManager(project)
- lightClassesCache = cachedValuesManager.createCachedValue {
- val builders = enabledFacetsProvider.modulesUsingSafeArgs
- .asSequence()
- .flatMap { facet -> SafeArgsCacheModuleService.getInstance(facet).directions.asSequence() }
- .flatMap { it.innerClasses.asSequence() }
- .filterIsInstance<LightActionBuilderClass>()
- .toList()
+ lightClassesCache =
+ cachedValuesManager.createCachedValue {
+ val builders =
+ enabledFacetsProvider.modulesUsingSafeArgs
+ .asSequence()
+ .flatMap { facet ->
+ SafeArgsCacheModuleService.getInstance(facet).directions.asSequence()
+ }
+ .flatMap { it.innerClasses.asSequence() }
+ .filterIsInstance<LightActionBuilderClass>()
+ .toList()
- CachedValueProvider.Result.create(builders,
- ProjectNavigationResourceModificationTracker.getInstance(project),
- project.safeArgsModeTracker)
- }
+ CachedValueProvider.Result.create(
+ builders,
+ ProjectNavigationResourceModificationTracker.getInstance(project),
+ project.safeArgsModeTracker
+ )
+ }
}
- override fun getAllClassNames(): Array<String> = lightClassesCache.value.mapNotNull { it.name }.toTypedArray()
+ override fun getAllClassNames(): Array<String> =
+ lightClassesCache.value.mapNotNull { it.name }.toTypedArray()
override fun getClassesByName(name: String, scope: GlobalSearchScope): Array<PsiClass> {
return lightClassesCache.value.filter { it.name == name }.toTypedArray()
@@ -63,13 +71,19 @@
override fun getAllMethodNames() = arrayOf<String>()
override fun getMethodsByName(name: String, scope: GlobalSearchScope) = arrayOf<PsiMethod>()
- override fun getMethodsByNameIfNotMoreThan(name: String, scope: GlobalSearchScope, maxCount: Int): Array<PsiMethod> {
+ override fun getMethodsByNameIfNotMoreThan(
+ name: String,
+ scope: GlobalSearchScope,
+ maxCount: Int
+ ): Array<PsiMethod> {
return getMethodsByName(name, scope).take(maxCount).toTypedArray()
}
- override fun processMethodsWithName(name: String,
- scope: GlobalSearchScope,
- processor: Processor<in PsiMethod>): Boolean {
+ override fun processMethodsWithName(
+ name: String,
+ scope: GlobalSearchScope,
+ processor: Processor<in PsiMethod>
+ ): Boolean {
// We are asked to process each method in turn, aborting if false is ever returned, and passing
// that result back up the chain.
return getMethodsByName(name, scope).all { method -> processor.process(method) }
@@ -77,7 +91,11 @@
override fun getAllFieldNames() = arrayOf<String>()
override fun getFieldsByName(name: String, scope: GlobalSearchScope) = arrayOf<PsiField>()
- override fun getFieldsByNameIfNotMoreThan(name: String, scope: GlobalSearchScope, maxCount: Int): Array<PsiField> {
+ override fun getFieldsByNameIfNotMoreThan(
+ name: String,
+ scope: GlobalSearchScope,
+ maxCount: Int
+ ): Array<PsiField> {
return getFieldsByName(name, scope).take(maxCount).toTypedArray()
}
}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/ArgsBuilderShortNamesCache.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/ArgsBuilderShortNamesCache.kt
index 6095283..6d05ad5 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/ArgsBuilderShortNamesCache.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/ArgsBuilderShortNamesCache.kt
@@ -41,18 +41,22 @@
init {
val cachedValuesManager = CachedValuesManager.getManager(project)
- lightClassesCache = cachedValuesManager.createCachedValue {
- val builders = enabledFacetsProvider.modulesUsingSafeArgs
- .asSequence()
- .flatMap { facet -> SafeArgsCacheModuleService.getInstance(facet).args.asSequence() }
- .flatMap { it.innerClasses.asSequence() }
- .filterIsInstance<LightArgsBuilderClass>()
- .toList()
+ lightClassesCache =
+ cachedValuesManager.createCachedValue {
+ val builders =
+ enabledFacetsProvider.modulesUsingSafeArgs
+ .asSequence()
+ .flatMap { facet -> SafeArgsCacheModuleService.getInstance(facet).args.asSequence() }
+ .flatMap { it.innerClasses.asSequence() }
+ .filterIsInstance<LightArgsBuilderClass>()
+ .toList()
- CachedValueProvider.Result.create(builders,
- ProjectNavigationResourceModificationTracker.getInstance(project),
- project.safeArgsModeTracker)
- }
+ CachedValueProvider.Result.create(
+ builders,
+ ProjectNavigationResourceModificationTracker.getInstance(project),
+ project.safeArgsModeTracker
+ )
+ }
}
override fun getAllClassNames(): Array<String> = arrayOf("Builder")
@@ -67,13 +71,19 @@
override fun getAllMethodNames() = arrayOf<String>()
override fun getMethodsByName(name: String, scope: GlobalSearchScope) = arrayOf<PsiMethod>()
- override fun getMethodsByNameIfNotMoreThan(name: String, scope: GlobalSearchScope, maxCount: Int): Array<PsiMethod> {
+ override fun getMethodsByNameIfNotMoreThan(
+ name: String,
+ scope: GlobalSearchScope,
+ maxCount: Int
+ ): Array<PsiMethod> {
return getMethodsByName(name, scope).take(maxCount).toTypedArray()
}
- override fun processMethodsWithName(name: String,
- scope: GlobalSearchScope,
- processor: Processor<in PsiMethod>): Boolean {
+ override fun processMethodsWithName(
+ name: String,
+ scope: GlobalSearchScope,
+ processor: Processor<in PsiMethod>
+ ): Boolean {
// We are asked to process each method in turn, aborting if false is ever returned, and passing
// that result back up the chain.
return getMethodsByName(name, scope).all { method -> processor.process(method) }
@@ -81,7 +91,11 @@
override fun getAllFieldNames() = arrayOf<String>()
override fun getFieldsByName(name: String, scope: GlobalSearchScope) = arrayOf<PsiField>()
- override fun getFieldsByNameIfNotMoreThan(name: String, scope: GlobalSearchScope, maxCount: Int): Array<PsiField> {
+ override fun getFieldsByNameIfNotMoreThan(
+ name: String,
+ scope: GlobalSearchScope,
+ maxCount: Int
+ ): Array<PsiField> {
return getFieldsByName(name, scope).take(maxCount).toTypedArray()
}
}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/ArgsShortNamesCache.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/ArgsShortNamesCache.kt
index 206a4c2..136debb 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/ArgsShortNamesCache.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/ArgsShortNamesCache.kt
@@ -32,9 +32,7 @@
import com.intellij.psi.util.CachedValuesManager
import com.intellij.util.Processor
-/**
- * A short names cache for finding any [LightArgsClass] instances by their unqualified name.
- */
+/** A short names cache for finding any [LightArgsClass] instances by their unqualified name. */
class ArgsShortNamesCache(project: Project) : PsiShortNamesCache() {
private val enabledFacetsProvider = SafeArgsEnabledFacetsProjectService.getInstance(project)
private val lightClassesCache: CachedValue<Map<String, List<LightArgsClass>>>
@@ -44,45 +42,56 @@
init {
val cachedValuesManager = CachedValuesManager.getManager(project)
- lightClassesCache = cachedValuesManager.createCachedValue {
- val lightClasses = enabledFacetsProvider.modulesUsingSafeArgs
- .asSequence()
- .flatMap { facet ->
- SafeArgsCacheModuleService.getInstance(facet).args.asSequence()
- }
- .groupBy { lightClass -> lightClass.name }
- CachedValueProvider.Result.create(lightClasses,
- ProjectNavigationResourceModificationTracker.getInstance(project),
- project.safeArgsModeTracker)
- }
+ lightClassesCache =
+ cachedValuesManager.createCachedValue {
+ val lightClasses =
+ enabledFacetsProvider.modulesUsingSafeArgs
+ .asSequence()
+ .flatMap { facet -> SafeArgsCacheModuleService.getInstance(facet).args.asSequence() }
+ .groupBy { lightClass -> lightClass.name }
+ CachedValueProvider.Result.create(
+ lightClasses,
+ ProjectNavigationResourceModificationTracker.getInstance(project),
+ project.safeArgsModeTracker
+ )
+ }
- allClassNamesCache = cachedValuesManager.createCachedValue {
- CachedValueProvider.Result.create(lightClassesCache.value.keys.toTypedArray(), lightClassesCache)
- }
+ allClassNamesCache =
+ cachedValuesManager.createCachedValue {
+ CachedValueProvider.Result.create(
+ lightClassesCache.value.keys.toTypedArray(),
+ lightClassesCache
+ )
+ }
}
-
override fun getAllClassNames(): Array<String> = allClassNamesCache.value
override fun getClassesByName(name: String, scope: GlobalSearchScope): Array<PsiClass> {
return lightClassesCache.value[name]
- ?.asSequence()
- ?.filter { PsiSearchScopeUtil.isInScope(scope, it) }
- ?.map { it as PsiClass }
- ?.toList()
- ?.toTypedArray()
- ?: PsiClass.EMPTY_ARRAY
+ ?.asSequence()
+ ?.filter { PsiSearchScopeUtil.isInScope(scope, it) }
+ ?.map { it as PsiClass }
+ ?.toList()
+ ?.toTypedArray()
+ ?: PsiClass.EMPTY_ARRAY
}
override fun getAllMethodNames() = arrayOf<String>()
override fun getMethodsByName(name: String, scope: GlobalSearchScope) = arrayOf<PsiMethod>()
- override fun getMethodsByNameIfNotMoreThan(name: String, scope: GlobalSearchScope, maxCount: Int): Array<PsiMethod> {
+ override fun getMethodsByNameIfNotMoreThan(
+ name: String,
+ scope: GlobalSearchScope,
+ maxCount: Int
+ ): Array<PsiMethod> {
return getMethodsByName(name, scope).take(maxCount).toTypedArray()
}
- override fun processMethodsWithName(name: String,
- scope: GlobalSearchScope,
- processor: Processor<in PsiMethod>): Boolean {
+ override fun processMethodsWithName(
+ name: String,
+ scope: GlobalSearchScope,
+ processor: Processor<in PsiMethod>
+ ): Boolean {
// We are asked to process each method in turn, aborting if false is ever returned, and passing
// that result back up the chain.
return getMethodsByName(name, scope).all { method -> processor.process(method) }
@@ -91,7 +100,11 @@
override fun getAllFieldNames() = arrayOf<String>()
override fun getFieldsByName(name: String, scope: GlobalSearchScope) = arrayOf<PsiField>()
- override fun getFieldsByNameIfNotMoreThan(name: String, scope: GlobalSearchScope, maxCount: Int): Array<PsiField> {
+ override fun getFieldsByNameIfNotMoreThan(
+ name: String,
+ scope: GlobalSearchScope,
+ maxCount: Int
+ ): Array<PsiField> {
return getFieldsByName(name, scope).take(maxCount).toTypedArray()
}
}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/DirectionsShortNamesCache.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/DirectionsShortNamesCache.kt
index a6a31fc..cf239ca 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/DirectionsShortNamesCache.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/cache/DirectionsShortNamesCache.kt
@@ -33,8 +33,8 @@
import com.intellij.util.Processor
/**
- * A short names cache for finding any [LightDirectionsClass] classes or their methods by their unqualified
- * name.
+ * A short names cache for finding any [LightDirectionsClass] classes or their methods by their
+ * unqualified name.
*/
class DirectionsShortNamesCache(project: Project) : PsiShortNamesCache() {
private val enabledFacetsProvider = SafeArgsEnabledFacetsProjectService.getInstance(project)
@@ -45,45 +45,58 @@
init {
val cachedValuesManager = CachedValuesManager.getManager(project)
- lightClassesCache = cachedValuesManager.createCachedValue {
- val lightClasses = enabledFacetsProvider.modulesUsingSafeArgs
- .asSequence()
- .flatMap { facet ->
- SafeArgsCacheModuleService.getInstance(facet).directions.asSequence()
- }
- .groupBy { lightClass -> lightClass.name }
- CachedValueProvider.Result.create(lightClasses,
- ProjectNavigationResourceModificationTracker.getInstance(project),
- project.safeArgsModeTracker)
- }
+ lightClassesCache =
+ cachedValuesManager.createCachedValue {
+ val lightClasses =
+ enabledFacetsProvider.modulesUsingSafeArgs
+ .asSequence()
+ .flatMap { facet ->
+ SafeArgsCacheModuleService.getInstance(facet).directions.asSequence()
+ }
+ .groupBy { lightClass -> lightClass.name }
+ CachedValueProvider.Result.create(
+ lightClasses,
+ ProjectNavigationResourceModificationTracker.getInstance(project),
+ project.safeArgsModeTracker
+ )
+ }
- allClassNamesCache = cachedValuesManager.createCachedValue {
- CachedValueProvider.Result.create(lightClassesCache.value.keys.toTypedArray(), lightClassesCache)
- }
+ allClassNamesCache =
+ cachedValuesManager.createCachedValue {
+ CachedValueProvider.Result.create(
+ lightClassesCache.value.keys.toTypedArray(),
+ lightClassesCache
+ )
+ }
}
-
override fun getAllClassNames(): Array<String> = allClassNamesCache.value
override fun getClassesByName(name: String, scope: GlobalSearchScope): Array<PsiClass> {
return lightClassesCache.value[name]
- ?.asSequence()
- ?.filter { PsiSearchScopeUtil.isInScope(scope, it) }
- ?.map { it as PsiClass }
- ?.toList()
- ?.toTypedArray()
- ?: PsiClass.EMPTY_ARRAY
+ ?.asSequence()
+ ?.filter { PsiSearchScopeUtil.isInScope(scope, it) }
+ ?.map { it as PsiClass }
+ ?.toList()
+ ?.toTypedArray()
+ ?: PsiClass.EMPTY_ARRAY
}
override fun getAllMethodNames() = arrayOf<String>()
override fun getMethodsByName(name: String, scope: GlobalSearchScope) = arrayOf<PsiMethod>()
- override fun getMethodsByNameIfNotMoreThan(name: String, scope: GlobalSearchScope, maxCount: Int): Array<PsiMethod> {
+ override fun getMethodsByNameIfNotMoreThan(
+ name: String,
+ scope: GlobalSearchScope,
+ maxCount: Int
+ ): Array<PsiMethod> {
return getMethodsByName(name, scope).take(maxCount).toTypedArray()
}
- override fun processMethodsWithName(name: String,
- scope: GlobalSearchScope,
- processor: Processor<in PsiMethod>): Boolean {
+ override fun processMethodsWithName(
+ name: String,
+ scope: GlobalSearchScope,
+ processor: Processor<in PsiMethod>
+ ): Boolean {
// We are asked to process each method in turn, aborting if false is ever returned, and passing
// that result back up the chain.
return getMethodsByName(name, scope).all { method -> processor.process(method) }
@@ -92,7 +105,11 @@
override fun getAllFieldNames() = arrayOf<String>()
override fun getFieldsByName(name: String, scope: GlobalSearchScope) = arrayOf<PsiField>()
- override fun getFieldsByNameIfNotMoreThan(name: String, scope: GlobalSearchScope, maxCount: Int): Array<PsiField> {
+ override fun getFieldsByNameIfNotMoreThan(
+ name: String,
+ scope: GlobalSearchScope,
+ maxCount: Int
+ ): Array<PsiField> {
return getFieldsByName(name, scope).take(maxCount).toTypedArray()
}
}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/ActionBuilderClassFinder.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/ActionBuilderClassFinder.kt
index dc3729b..0e235f3 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/ActionBuilderClassFinder.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/ActionBuilderClassFinder.kt
@@ -20,13 +20,12 @@
import com.intellij.openapi.project.Project
import org.jetbrains.android.facet.AndroidFacet
-/**
- * A finder that can find instances of [LightActionBuilderClass] by qualified name / package.
- */
+/** A finder that can find instances of [LightActionBuilderClass] by qualified name / package. */
class ActionBuilderClassFinder(project: Project) : SafeArgsClassFinderBase(project) {
override fun findAll(facet: AndroidFacet): List<LightActionBuilderClass> {
- return SafeArgsCacheModuleService.getInstance(facet).directions
+ return SafeArgsCacheModuleService.getInstance(facet)
+ .directions
.flatMap { it.innerClasses.toList() }
.filterIsInstance<LightActionBuilderClass>()
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/ArgsBuilderClassFinder.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/ArgsBuilderClassFinder.kt
index d935d51..412e26d 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/ArgsBuilderClassFinder.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/ArgsBuilderClassFinder.kt
@@ -20,12 +20,11 @@
import com.intellij.openapi.project.Project
import org.jetbrains.android.facet.AndroidFacet
-/**
- * A finder that can find instances of [LightArgsBuilderClass] by qualified name / package.
- */
+/** A finder that can find instances of [LightArgsBuilderClass] by qualified name / package. */
class ArgsBuilderClassFinder(project: Project) : SafeArgsClassFinderBase(project) {
override fun findAll(facet: AndroidFacet): List<LightArgsBuilderClass> {
- return SafeArgsCacheModuleService.getInstance(facet).args
+ return SafeArgsCacheModuleService.getInstance(facet)
+ .args
.flatMap { it.innerClasses.toList() }
.filterIsInstance<LightArgsBuilderClass>()
}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/ArgsClassFinder.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/ArgsClassFinder.kt
index d5f15ae..f658cd6 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/ArgsClassFinder.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/ArgsClassFinder.kt
@@ -21,11 +21,9 @@
import com.intellij.openapi.project.Project
import org.jetbrains.android.facet.AndroidFacet
-/**
- * A finder that can find instances of [LightArgsClass] by qualified name / package.
- */
+/** A finder that can find instances of [LightArgsClass] by qualified name / package. */
class ArgsClassFinder(project: Project) : SafeArgsClassFinderBase(project) {
override fun findAll(facet: AndroidFacet): List<SafeArgsLightBaseClass> {
return SafeArgsCacheModuleService.getInstance(facet).args
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/DirectionsClassFinder.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/DirectionsClassFinder.kt
index f2d731f..d88ad2f 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/DirectionsClassFinder.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/DirectionsClassFinder.kt
@@ -21,9 +21,7 @@
import com.intellij.openapi.project.Project
import org.jetbrains.android.facet.AndroidFacet
-/**
- * A finder that can find instances of [LightDirectionsClass] by qualified name / package.
- */
+/** A finder that can find instances of [LightDirectionsClass] by qualified name / package. */
class DirectionsClassFinder(project: Project) : SafeArgsClassFinderBase(project) {
override fun findAll(facet: AndroidFacet): List<SafeArgsLightBaseClass> {
return SafeArgsCacheModuleService.getInstance(facet).directions
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/SafeArgsClassFinderBase.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/SafeArgsClassFinderBase.kt
index c71b5a7..32a3bad 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/SafeArgsClassFinderBase.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/SafeArgsClassFinderBase.kt
@@ -29,23 +29,23 @@
import org.jetbrains.android.augment.AndroidLightClassBase
import org.jetbrains.android.facet.AndroidFacet
-/**
- * A base class for safe arg light class finder
- *
- */
+/** A base class for safe arg light class finder */
abstract class SafeArgsClassFinderBase(private val project: Project) : PsiElementFinder() {
private fun findAll(project: Project): List<AndroidLightClassBase> {
val provider = {
- val result = SafeArgsEnabledFacetsProjectService.getInstance(project)
- .modulesUsingSafeArgs
- .asSequence()
- .flatMap { facet -> findAll(facet).asSequence() }
- .toList()
+ val result =
+ SafeArgsEnabledFacetsProjectService.getInstance(project)
+ .modulesUsingSafeArgs
+ .asSequence()
+ .flatMap { facet -> findAll(facet).asSequence() }
+ .toList()
- CachedValueProvider.Result.create(result,
- ProjectNavigationResourceModificationTracker.getInstance(project),
- project.safeArgsModeTracker)
+ CachedValueProvider.Result.create(
+ result,
+ ProjectNavigationResourceModificationTracker.getInstance(project),
+ project.safeArgsModeTracker
+ )
}
val manager = CachedValuesManager.getManager(project)
@@ -55,18 +55,15 @@
abstract fun findAll(facet: AndroidFacet): List<AndroidLightClassBase>
override fun findClass(qualifiedName: String, scope: GlobalSearchScope): PsiClass? {
- return findAll(project)
- .firstOrNull { lightClass ->
- lightClass.qualifiedName == qualifiedName
- && PsiSearchScopeUtil.isInScope(scope, lightClass)
- }
+ return findAll(project).firstOrNull { lightClass ->
+ lightClass.qualifiedName == qualifiedName && PsiSearchScopeUtil.isInScope(scope, lightClass)
+ }
}
override fun findClasses(qualifiedName: String, scope: GlobalSearchScope): Array<PsiClass> {
return findAll(project)
.filter { lightClass ->
- lightClass.qualifiedName == qualifiedName
- && PsiSearchScopeUtil.isInScope(scope, lightClass)
+ lightClass.qualifiedName == qualifiedName && PsiSearchScopeUtil.isInScope(scope, lightClass)
}
.toTypedArray()
}
@@ -78,9 +75,9 @@
return findAll(psiPackage.project)
.filter { lightClass ->
- psiPackage.qualifiedName == lightClass.qualifiedName?.substringBeforeLast('.')
- && PsiSearchScopeUtil.isInScope(scope, lightClass)
+ psiPackage.qualifiedName == lightClass.qualifiedName?.substringBeforeLast('.') &&
+ PsiSearchScopeUtil.isInScope(scope, lightClass)
}
.toTypedArray()
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/SafeArgsScopeEnlarger.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/SafeArgsScopeEnlarger.kt
index 2503e01..4bda74f 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/SafeArgsScopeEnlarger.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/finder/SafeArgsScopeEnlarger.kt
@@ -47,16 +47,24 @@
internal fun getAdditionalResolveScope(facet: AndroidFacet): SearchScope? {
return CachedValuesManager.getManager(facet.module.project).getCachedValue(facet) {
- val allFacets = listOf(facet) + ModuleRootManager.getInstance(facet.module)
- .getDependencies(false)
- .mapNotNull { module -> module.androidFacet }
+ val allFacets =
+ listOf(facet) +
+ ModuleRootManager.getInstance(facet.module).getDependencies(false).mapNotNull { module ->
+ module.androidFacet
+ }
- val scopeIncludingDeps = allFacets
- .filter { it.safeArgsMode == SafeArgsMode.JAVA }
- .map { it.getLocalScope() }
- .fold(GlobalSearchScope.EMPTY_SCOPE) { scopeAccum, depScope -> scopeAccum.union(depScope) }
+ val scopeIncludingDeps =
+ allFacets
+ .filter { it.safeArgsMode == SafeArgsMode.JAVA }
+ .map { it.getLocalScope() }
+ .fold(GlobalSearchScope.EMPTY_SCOPE) { scopeAccum, depScope ->
+ scopeAccum.union(depScope)
+ }
- CachedValueProvider.Result.create(scopeIncludingDeps, PsiModificationTracker.MODIFICATION_COUNT)
+ CachedValueProvider.Result.create(
+ scopeIncludingDeps,
+ PsiModificationTracker.MODIFICATION_COUNT
+ )
}
}
@@ -83,10 +91,11 @@
* Therefore, we provide one here that simply delegates to it.
*/
class SafeArgsKotlinScopeEnlarger : KotlinResolveScopeEnlarger {
- private val delegateEnlarger = ResolveScopeEnlarger.EP_NAME.findExtensionOrFail(SafeArgsScopeEnlarger::class.java)
+ private val delegateEnlarger =
+ ResolveScopeEnlarger.EP_NAME.findExtensionOrFail(SafeArgsScopeEnlarger::class.java)
override fun getAdditionalResolveScope(module: Module, isTestScope: Boolean): SearchScope? {
val facet = module.androidFacet ?: return null
return delegateEnlarger.getAdditionalResolveScope(facet)
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/index/JaxbNavData.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/index/JaxbNavData.kt
index 4bbb932..59eafc2 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/index/JaxbNavData.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/index/JaxbNavData.kt
@@ -20,8 +20,6 @@
import com.android.SdkConstants.ANDROID_URI
import com.android.SdkConstants.AUTO_URI
import com.android.resources.ResourceUrl
-import org.w3c.dom.Document
-import org.w3c.dom.Element
import javax.xml.bind.JAXBContext
import javax.xml.bind.annotation.XmlAccessType
import javax.xml.bind.annotation.XmlAccessorType
@@ -32,20 +30,20 @@
import javax.xml.bind.annotation.adapters.XmlAdapter
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter
import javax.xml.transform.dom.DOMResult
+import org.w3c.dom.Document
+import org.w3c.dom.Element
// NOTE: If you change any class in this file, you should also increment NavXmlIndex#getVersion()
-/**
- * Adapter to handle serializing attributes that represent android IDs (e.g. "@id/asdf")
- */
+/** Adapter to handle serializing attributes that represent android IDs (e.g. "@id/asdf") */
private class AndroidIdAdapter : XmlAdapter<String, String>() {
override fun marshal(s: String) = "@id/$s"
override fun unmarshal(s: String) = ResourceUrl.parse(s)?.name ?: ""
}
/**
- * Adapter to handle serializing attributes that represent android IDs that might not be
- * present. See also: [AndroidIdAdapter]
+ * Adapter to handle serializing attributes that represent android IDs that might not be present.
+ * See also: [AndroidIdAdapter]
*/
private class OptionalAndroidIdAdapter : XmlAdapter<String?, String?>() {
private val delegateAdapter = AndroidIdAdapter()
@@ -58,9 +56,9 @@
* An adapter which allows us to catch any unspecified tag and try to fit it into a
* [MaybeNavDestinationData].
*
- * This adapter will always succeed at creating a "maybe" destination, but only if
- * expected attributes are found on the custom tag will it return a non-null
- * destination if [MaybeNavDestinationData.toDestination] is called.
+ * This adapter will always succeed at creating a "maybe" destination, but only if expected
+ * attributes are found on the custom tag will it return a non-null destination if
+ * [MaybeNavDestinationData.toDestination] is called.
*/
private class MaybeDestinationAdapter : XmlAdapter<Element, MaybeNavDestinationData>() {
private val jaxbContext = JAXBContext.newInstance(MutableMaybeNavDestinationData::class.java)
@@ -82,15 +80,9 @@
@XmlRootElement(name = "argument")
@XmlAccessorType(XmlAccessType.FIELD)
data class MutableNavArgumentData(
- @field:XmlAttribute(namespace = ANDROID_URI)
- override var name: String,
-
- @field:XmlAttribute(namespace = AUTO_URI, name = "argType")
- override var type: String?,
-
- @field:XmlAttribute(namespace = AUTO_URI, name = "nullable")
- override var nullable: String?,
-
+ @field:XmlAttribute(namespace = ANDROID_URI) override var name: String,
+ @field:XmlAttribute(namespace = AUTO_URI, name = "argType") override var type: String?,
+ @field:XmlAttribute(namespace = AUTO_URI, name = "nullable") override var nullable: String?,
@field:XmlAttribute(namespace = ANDROID_URI, name = "defaultValue")
override var defaultValue: String?
) : NavArgumentData {
@@ -103,37 +95,29 @@
@field:XmlJavaTypeAdapter(AndroidIdAdapter::class)
@field:XmlAttribute(namespace = ANDROID_URI)
override var id: String,
-
@field:XmlJavaTypeAdapter(OptionalAndroidIdAdapter::class)
@field:XmlAttribute(namespace = AUTO_URI)
override var destination: String?,
-
@field:XmlJavaTypeAdapter(OptionalAndroidIdAdapter::class)
@field:XmlAttribute(namespace = AUTO_URI)
override var popUpTo: String?,
-
- @field:XmlElement(name = "argument")
- override var arguments: List<MutableNavArgumentData>
+ @field:XmlElement(name = "argument") override var arguments: List<MutableNavArgumentData>
) : NavActionData {
constructor() : this("", null, null, mutableListOf())
}
-@XmlRootElement(name = "maybeDestination") // Fake root element name only used for indexing, required by JAXB marshalling
+@XmlRootElement(
+ name = "maybeDestination"
+) // Fake root element name only used for indexing, required by JAXB marshalling
@XmlAccessorType(XmlAccessType.FIELD)
data class MutableMaybeNavDestinationData(
@field:XmlJavaTypeAdapter(OptionalAndroidIdAdapter::class)
@field:XmlAttribute(namespace = ANDROID_URI)
var id: String?,
-
- @field:XmlAttribute(namespace = ANDROID_URI)
- var name: String?,
-
- @field:XmlElement(name = "argument")
- var arguments: List<MutableNavArgumentData>,
-
- @field:XmlElement(name = "action")
- var actions: List<MutableNavActionData>)
- : MaybeNavDestinationData {
+ @field:XmlAttribute(namespace = ANDROID_URI) var name: String?,
+ @field:XmlElement(name = "argument") var arguments: List<MutableNavArgumentData>,
+ @field:XmlElement(name = "action") var actions: List<MutableNavActionData>
+) : MaybeNavDestinationData {
constructor() : this(null, null, mutableListOf(), mutableListOf())
override fun toDestination(): NavDestinationData? {
@@ -155,20 +139,12 @@
@field:XmlJavaTypeAdapter(OptionalAndroidIdAdapter::class)
@field:XmlAttribute(namespace = ANDROID_URI)
override var id: String?,
-
@field:XmlJavaTypeAdapter(AndroidIdAdapter::class)
@field:XmlAttribute(namespace = AUTO_URI)
override var startDestination: String,
-
- @field:XmlElement(name = "action")
- override var actions: List<MutableNavActionData>,
-
- @field:XmlElement(name = "argument")
- override var arguments: List<MutableNavArgumentData>,
-
- @field:XmlElement(name = "navigation")
- override var navigations: List<MutableNavNavigationData>,
-
+ @field:XmlElement(name = "action") override var actions: List<MutableNavActionData>,
+ @field:XmlElement(name = "argument") override var arguments: List<MutableNavArgumentData>,
+ @field:XmlElement(name = "navigation") override var navigations: List<MutableNavNavigationData>,
@field:XmlAnyElement()
@field:XmlJavaTypeAdapter(MaybeDestinationAdapter::class)
override var potentialDestinations: List<MaybeNavDestinationData>
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/index/NavData.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/index/NavData.kt
index da4cf09..7ad4654 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/index/NavData.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/index/NavData.kt
@@ -24,9 +24,8 @@
* A destination may have zero or more arguments. Actions may also have arguments, which act as
* default values for their destinations.
*
- * In most cases, users will provide [type] explicitly, but if not specified, it can be
- * inferred from [defaultValue]. If neither [type] nor [defaultValue] are set, this
- * argument data is invalid.
+ * In most cases, users will provide [type] explicitly, but if not specified, it can be inferred
+ * from [defaultValue]. If neither [type] nor [defaultValue] are set, this argument data is invalid.
*/
interface NavArgumentData {
val name: String
@@ -45,9 +44,9 @@
*
* A destination may have zero or more actions.
*
- * An action itself should point to a single target destination, which is usually set by `destination`,
- * but could also just be set by `popUpTo` (which essentially means the user wants to navigate backwards
- * to a destination that's in the back stack)
+ * An action itself should point to a single target destination, which is usually set by
+ * `destination`, but could also just be set by `popUpTo` (which essentially means the user wants to
+ * navigate backwards to a destination that's in the back stack)
*/
interface NavActionData {
val id: String
@@ -60,9 +59,7 @@
}
}
-/**
- * A useful abstraction across multiple destination types, e.g. <activity> and <fragment>
- */
+/** A useful abstraction across multiple destination types, e.g. <activity> and <fragment> */
interface NavDestinationData {
val id: String
val name: String
@@ -98,7 +95,9 @@
val id = this.id ?: return null
return object : NavDestinationData {
override val id = id
- override val name = ".${id.toUpperCamelCase()}" // The prefix '.' means this class should be scoped in the current module
+ override val name =
+ ".${id.toUpperCamelCase()}" // The prefix '.' means this class should be scoped in the
+ // current module
override val arguments = this@NavNavigationData.arguments
override val actions = this@NavNavigationData.actions
}
@@ -106,15 +105,16 @@
}
/**
- * Data class for storing the indexed content nav XML files, useful for generating relevant
- * safe args classes.
+ * Data class for storing the indexed content nav XML files, useful for generating relevant safe
+ * args classes.
*/
data class NavXmlData(val root: NavNavigationData) {
/**
* Returns a list of all destinations with global actions updated.
* (https://developer.android.com/guide/navigation/navigation-global-action)
*
- * Global actions are collected along the path while traversing, and duplicates are resolved like actions overrides.
+ * Global actions are collected along the path while traversing, and duplicates are resolved like
+ * actions overrides.
*/
val resolvedDestinations: List<NavDestinationData> by lazy {
root.traverse(emptyList(), mutableListOf())
@@ -131,18 +131,18 @@
.mapNotNull { it.toDestination()?.withGlobalActions(newGlobalActions) }
.let { allDestinations.addAll(it) }
- this.navigations.map {
- it.traverse(newGlobalActions, allDestinations)
- }
+ this.navigations.map { it.traverse(newGlobalActions, allDestinations) }
return allDestinations
}
- private fun NavDestinationData.withGlobalActions(globalActions: List<NavActionData>): NavDestinationData {
+ private fun NavDestinationData.withGlobalActions(
+ globalActions: List<NavActionData>
+ ): NavDestinationData {
if (globalActions.isEmpty()) return this
return object : NavDestinationData by this {
override val actions = (this@withGlobalActions.actions + globalActions).distinctBy { it.id }
}
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/index/NavXmlIndex.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/index/NavXmlIndex.kt
index 0ecae16..a565fd8 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/index/NavXmlIndex.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/index/NavXmlIndex.kt
@@ -46,11 +46,13 @@
fun getDataForFile(project: Project, file: VirtualFile): NavXmlData? {
ApplicationManager.getApplication().assertReadAccessAllowed()
- val navXmlData = FileBasedIndex.getInstance().getSingleEntryIndexData(NAME, file, project) ?: return null
+ val navXmlData =
+ FileBasedIndex.getInstance().getSingleEntryIndexData(NAME, file, project) ?: return null
// Verify that this is a navigation resource file before returning.
val containingFolderName = file.parent?.name ?: return null
- if (ResourceFolderType.getFolderType(containingFolderName) != ResourceFolderType.NAVIGATION) return null
+ if (ResourceFolderType.getFolderType(containingFolderName) != ResourceFolderType.NAVIGATION)
+ return null
return navXmlData
}
@@ -58,8 +60,10 @@
private val jaxbContext = JAXBContext.newInstance(MutableNavNavigationData::class.java)
// JAXB marshallers / unmarshallers are not thread-safe, so create a new one each time
- private val jaxbSerializer get() = jaxbContext.createMarshaller()
- private val jaxbDeserializer get() = jaxbContext.createUnmarshaller()
+ private val jaxbSerializer
+ get() = jaxbContext.createMarshaller()
+ private val jaxbDeserializer
+ get() = jaxbContext.createUnmarshaller()
override fun getVersion() = 11
override fun dependsOnFileContent() = true
@@ -74,15 +78,17 @@
}
/**
- * Defines the data externalizer handling the serialization/de-serialization of indexed information.
+ * Defines the data externalizer handling the serialization/de-serialization of indexed
+ * information.
*/
override fun getValueExternalizer(): DataExternalizer<NavXmlData> {
return object : DataExternalizer<NavXmlData> {
override fun save(out: DataOutput, value: NavXmlData) {
- val outBytes = ByteArrayOutputStream().use { writer ->
- jaxbSerializer.marshal(value.root, writer)
- writer.toByteArray()
- }
+ val outBytes =
+ ByteArrayOutputStream().use { writer ->
+ jaxbSerializer.marshal(value.root, writer)
+ writer.toByteArray()
+ }
out.writeInt(outBytes.size)
out.write(outBytes)
}
@@ -90,7 +96,10 @@
override fun read(`in`: DataInput): NavXmlData {
val inBytes = ByteArray(`in`.readInt())
`in`.readFully(inBytes)
- val rootNav = ByteArrayInputStream(inBytes).use { bytes -> jaxbDeserializer.unmarshal(bytes) as NavNavigationData }
+ val rootNav =
+ ByteArrayInputStream(inBytes).use { bytes ->
+ jaxbDeserializer.unmarshal(bytes) as NavNavigationData
+ }
return NavXmlData(rootNav)
}
}
@@ -103,7 +112,8 @@
if (!text.contains("<navigation")) return null
return try {
- val rootNav = jaxbDeserializer.unmarshal(StringReader(text.toString())) as NavNavigationData
+ val rootNav =
+ jaxbDeserializer.unmarshal(StringReader(text.toString())) as NavNavigationData
NavXmlData(rootNav)
}
// Normally we'd just catch explicit exceptions, like UnmarshalException, but JAXB also
@@ -111,7 +121,10 @@
// failed, and we definitely don't want any exceptions to leak to our users here, to be safe
// we just catch and log all possible problems.
catch (e: Throwable) {
- getLog().info("${NavXmlIndex::class.java.simpleName} skipping over \"${inputData.file.path}\": ${e.message}")
+ getLog()
+ .info(
+ "${NavXmlIndex::class.java.simpleName} skipping over \"${inputData.file.path}\": ${e.message}"
+ )
null
}
}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/ModuleNavigationResourcesModificationTracker.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/ModuleNavigationResourcesModificationTracker.kt
index 88441dc..669ee8b 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/ModuleNavigationResourcesModificationTracker.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/ModuleNavigationResourcesModificationTracker.kt
@@ -17,37 +17,38 @@
import com.android.tools.idea.nav.safeargs.project.NAVIGATION_RESOURCES_CHANGED
import com.android.tools.idea.nav.safeargs.project.NavigationResourcesChangeListener
-import com.android.tools.idea.nav.safeargs.project.NavigationResourcesModificationListener
-import com.android.tools.idea.nav.safeargs.safeArgsModeTracker
import com.intellij.openapi.Disposable
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.module.Module
-import com.intellij.openapi.startup.StartupManager
import com.intellij.openapi.util.ModificationTracker
import com.intellij.openapi.util.SimpleModificationTracker
/**
- * A module-wide modification tracker whose modification count is a value incremented by any modifications of
- * corresponding navigation resource files.
+ * A module-wide modification tracker whose modification count is a value incremented by any
+ * modifications of corresponding navigation resource files.
*/
-class ModuleNavigationResourcesModificationTracker(val module: Module) : ModificationTracker, Disposable {
+class ModuleNavigationResourcesModificationTracker(val module: Module) :
+ ModificationTracker, Disposable {
private val navigationModificationTracker = SimpleModificationTracker()
init {
- module.project.messageBus.connect(this).subscribe(
- NAVIGATION_RESOURCES_CHANGED,
- NavigationResourcesChangeListener { changedModule ->
- if (changedModule == null || changedModule == module) {
- navigationChanged()
+ module.project.messageBus
+ .connect(this)
+ .subscribe(
+ NAVIGATION_RESOURCES_CHANGED,
+ NavigationResourcesChangeListener { changedModule ->
+ if (changedModule == null || changedModule == module) {
+ navigationChanged()
+ }
}
- }
- )
+ )
}
companion object {
@JvmStatic
- fun getInstance(module: Module) = module.getService(ModuleNavigationResourcesModificationTracker::class.java)!!
+ fun getInstance(module: Module) =
+ module.getService(ModuleNavigationResourcesModificationTracker::class.java)!!
}
override fun getModificationCount() = navigationModificationTracker.modificationCount
@@ -55,11 +56,13 @@
override fun dispose() {}
/**
- * This is invoked when NavigationModificationListener detects a navigation file has been changed or added or deleted for this module
+ * This is invoked when NavigationModificationListener detects a navigation file has been changed
+ * or added or deleted for this module
*/
private fun navigationChanged() {
navigationModificationTracker.incModificationCount()
- logger<ModuleNavigationResourcesModificationTracker>()
- .debug { "Navigation Modification Tracker of $module is updated to $modificationCount" }
+ logger<ModuleNavigationResourcesModificationTracker>().debug {
+ "Navigation Modification Tracker of $module is updated to $modificationCount"
+ }
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/NavInfoFetcher.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/NavInfoFetcher.kt
index 8c27511..44d5c38 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/NavInfoFetcher.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/NavInfoFetcher.kt
@@ -42,9 +42,9 @@
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import com.intellij.psi.xml.XmlFile
+import kotlin.reflect.KProperty
import net.jcip.annotations.GuardedBy
import org.jetbrains.android.facet.AndroidFacet
-import kotlin.reflect.KProperty
/** Information about a single navigation file. */
data class NavEntry(
@@ -72,7 +72,9 @@
val entries: List<NavEntry>,
/** The configured Jetpack Navigation version for which this [NavInfo] is valid. */
val navVersion: Version,
- /** The modification count, from the source [NavInfoFetcher], at which this [NavInfo] was valid. */
+ /**
+ * The modification count, from the source [NavInfoFetcher], at which this [NavInfo] was valid.
+ */
val modificationCount: Long,
)
@@ -81,10 +83,10 @@
SAFE_ARGS_MODE_CHANGED,
GRADLE_SYNC,
DUMB_MODE_CHANGED,
- ;
}
-@VisibleForTesting interface NavInfoFetcherBase : ModificationTracker {
+@VisibleForTesting
+interface NavInfoFetcherBase : ModificationTracker {
val isEnabled: Boolean
fun getCurrentNavInfo(): NavInfo?
}
@@ -95,7 +97,10 @@
parent: Disposable,
/** The [Module] for which to fetch navigation state. */
private val module: Module,
- /** The [SafeArgsMode] for which the navigation state is valid. [getCurrentNavInfo] will return `null` for projects in other modes. */
+ /**
+ * The [SafeArgsMode] for which the navigation state is valid. [getCurrentNavInfo] will return
+ * `null` for projects in other modes.
+ */
private val mode: SafeArgsMode,
/** A callback that will be called when the result of [getCurrentNavInfo] may have changed. */
private val onChange: (NavInfoChangeReason) -> Unit = {}
@@ -127,9 +132,7 @@
// Invalidate on project sync, in case nav version changes.
subscribe(
PROJECT_SYSTEM_SYNC_TOPIC,
- ProjectSystemSyncManager.SyncResultListener {
- invalidate(NavInfoChangeReason.GRADLE_SYNC)
- }
+ ProjectSystemSyncManager.SyncResultListener { invalidate(NavInfoChangeReason.GRADLE_SYNC) }
)
subscribe(
DumbService.DUMB_MODE,
@@ -149,9 +152,13 @@
}
private val androidFacetIfEnabled: AndroidFacet?
- get() = AndroidFacet.getInstance(module)?.takeIf { it.isSafeArgsEnabled() && it.safeArgsMode == mode }
+ get() =
+ AndroidFacet.getInstance(module)?.takeIf { it.isSafeArgsEnabled() && it.safeArgsMode == mode }
- /** Whether the project is currently enabled (SafeArgs enabled for project, and [SafeArgsMode] matches filter). */
+ /**
+ * Whether the project is currently enabled (SafeArgs enabled for project, and [SafeArgsMode]
+ * matches filter).
+ */
override val isEnabled: Boolean
get() = androidFacetIfEnabled != null
@@ -171,8 +178,8 @@
/**
* Gets the [NavInfo] for the current state of the project.
*
- * Returns `null` if Jetpack Navigation is [disabled](isEnabled) for the project, the project is in the wrong [SafeArgsMode], or the
- * project indices are not yet ready for querying.
+ * Returns `null` if Jetpack Navigation is [disabled](isEnabled) for the project, the project is
+ * in the wrong [SafeArgsMode], or the project indices are not yet ready for querying.
*/
override fun getCurrentNavInfo(): NavInfo? {
val facet = androidFacetIfEnabled ?: return null
@@ -180,30 +187,40 @@
if (DumbService.getInstance(module.project).isDumb) {
Logger.getInstance(this.javaClass)
- .warn("Safe Args classes may be temporarily stale or unavailable due to indices not being ready right now.")
+ .warn(
+ "Safe Args classes may be temporarily stale or unavailable due to indices not being ready right now."
+ )
return null
}
- // Save version and modification count _before_ reading resources - in the event of a change, this ensures that we don't match up the
+ // Save version and modification count _before_ reading resources - in the event of a change,
+ // this ensures that we don't match up the
// current modification count with stale data.
val navVersion = facet.findNavigationVersion()
val modificationCount = modificationCount
val moduleResources = StudioResourceRepositoryManager.getModuleResources(facet)
- val navResources = moduleResources.getResources(ResourceNamespace.RES_AUTO, ResourceType.NAVIGATION)
+ val navResources =
+ moduleResources.getResources(ResourceNamespace.RES_AUTO, ResourceType.NAVIGATION)
- val entries = navResources.values().mapNotNull { resource ->
- val file = resource.getSourceAsVirtualFile() ?: return@mapNotNull null
- val data = NavXmlIndex.getDataForFile(module.project, file) ?: return@mapNotNull null
- NavEntry(facet, resource, file, data)
- }
+ val entries =
+ navResources.values().mapNotNull { resource ->
+ val file = resource.getSourceAsVirtualFile() ?: return@mapNotNull null
+ val data = NavXmlIndex.getDataForFile(module.project, file) ?: return@mapNotNull null
+ NavEntry(facet, resource, file, data)
+ }
return NavInfo(facet, modulePackage, entries, navVersion, modificationCount)
}
}
-/** An object that creates and caches arbitrary status objects that depend on the current state of Jetpack Navigation. */
-class NavStatusCache<TStatus : Any> @VisibleForTesting constructor(
+/**
+ * An object that creates and caches arbitrary status objects that depend on the current state of
+ * Jetpack Navigation.
+ */
+class NavStatusCache<TStatus : Any>
+@VisibleForTesting
+constructor(
private val onCacheInvalidate: (NavInfoChangeReason) -> Unit,
private val update: (NavInfo) -> TStatus,
navInfoFetcherFactory: ((NavInfoChangeReason) -> Unit) -> NavInfoFetcherBase
@@ -227,46 +244,48 @@
* Will be called with a lock held, and will be called at most once per new [navInfo] generated.
*/
update: (NavInfo) -> TStatus
- ) : this(onCacheInvalidate, update, { invalidate -> NavInfoFetcher(parent, module, mode, invalidate) })
+ ) : this(
+ onCacheInvalidate,
+ update,
+ { invalidate -> NavInfoFetcher(parent, module, mode, invalidate) }
+ )
private val lock = Any()
private val fetcher = navInfoFetcherFactory { invalidateReason ->
// Invalidate cached status.
- synchronized(lock) {
- lastStatusValid = false
- }
+ synchronized(lock) { lastStatusValid = false }
onCacheInvalidate(invalidateReason)
}
- @GuardedBy("lock")
- private var lastStatus: TStatus? = null
+ @GuardedBy("lock") private var lastStatus: TStatus? = null
- @GuardedBy("lock")
- private var lastStatusValid = false
+ @GuardedBy("lock") private var lastStatusValid = false
/**
* Gets the current status, as generated by [update] for the current state of Jetpack Navigation.
*
- * If [NavInfoFetcher.getCurrentNavInfo] returns `null`, returns a previously-cached result if available.
+ * If [NavInfoFetcher.getCurrentNavInfo] returns `null`, returns a previously-cached result if
+ * available.
*/
val currentStatus: TStatus?
- get() = synchronized(lock) {
- // Don't return cached data if this entire service is no longer enabled (mode change).
- if (!fetcher.isEnabled) return null
- if (!lastStatusValid) {
- // A null from getCurrentNavInfo() here means either SafeArgs is disabled, or we're
- // in dumb mode. The former case is handled by the isEnabled check above.
- // If we're in dumb mode, stale data is the best we can do, so return it anyway.
- // We'll get a cache-invalidate event from NavInfoFetcher when we become smart, and
- // our caller will know to query us again.
- val newNavInfo = fetcher.getCurrentNavInfo() ?: return lastStatus
- val newStatus = update(newNavInfo)
- lastStatus = newStatus
- lastStatusValid = true
+ get() =
+ synchronized(lock) {
+ // Don't return cached data if this entire service is no longer enabled (mode change).
+ if (!fetcher.isEnabled) return null
+ if (!lastStatusValid) {
+ // A null from getCurrentNavInfo() here means either SafeArgs is disabled, or we're
+ // in dumb mode. The former case is handled by the isEnabled check above.
+ // If we're in dumb mode, stale data is the best we can do, so return it anyway.
+ // We'll get a cache-invalidate event from NavInfoFetcher when we become smart, and
+ // our caller will know to query us again.
+ val newNavInfo = fetcher.getCurrentNavInfo() ?: return lastStatus
+ val newStatus = update(newNavInfo)
+ lastStatus = newStatus
+ lastStatusValid = true
+ }
+ return lastStatus
}
- return lastStatus
- }
/** A [ModificationTracker] that will update when provided cached data needs to be invalidated. */
val modificationTracker: ModificationTracker = fetcher
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/SafeArgsCacheModuleService.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/SafeArgsCacheModuleService.kt
index 293814f..870610b 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/SafeArgsCacheModuleService.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/SafeArgsCacheModuleService.kt
@@ -15,7 +15,6 @@
*/
package com.android.tools.idea.nav.safeargs.module
-import com.android.ide.common.gradle.Version
import com.android.tools.idea.nav.safeargs.SafeArgsMode
import com.android.tools.idea.nav.safeargs.psi.java.LightArgsClass
import com.android.tools.idea.nav.safeargs.psi.java.LightDirectionsClass
@@ -35,17 +34,16 @@
class SafeArgsCacheModuleService private constructor(module: Module) : Disposable.Default {
private class Status(val directions: List<LightDirectionsClass>, val args: List<LightArgsClass>)
- private val currentStatus by NavStatusCache(this, module, SafeArgsMode.JAVA) { navInfo ->
- val directions = navInfo.entries
- .flatMap { entry -> createLightDirectionsClasses(navInfo, entry) }
- .toList()
+ private val currentStatus by
+ NavStatusCache(this, module, SafeArgsMode.JAVA) { navInfo ->
+ val directions =
+ navInfo.entries.flatMap { entry -> createLightDirectionsClasses(navInfo, entry) }.toList()
- val args = navInfo.entries
- .flatMap { entry -> createLightArgsClasses(navInfo, entry) }
- .toList()
+ val args =
+ navInfo.entries.flatMap { entry -> createLightArgsClasses(navInfo, entry) }.toList()
- Status(directions, args)
- }
+ Status(directions, args)
+ }
val directions: List<LightDirectionsClass>
get() = currentStatus?.directions ?: emptyList()
@@ -53,14 +51,20 @@
val args: List<LightArgsClass>
get() = currentStatus?.args ?: emptyList()
- private fun createLightDirectionsClasses(navInfo: NavInfo, navEntry: NavEntry): Collection<LightDirectionsClass> {
+ private fun createLightDirectionsClasses(
+ navInfo: NavInfo,
+ navEntry: NavEntry
+ ): Collection<LightDirectionsClass> {
return navEntry.data.resolvedDestinations
.filter { destination -> destination.actions.isNotEmpty() }
.map { destination -> LightDirectionsClass(navInfo, navEntry, destination) }
.toSet()
}
- private fun createLightArgsClasses(navInfo: NavInfo, navEntry: NavEntry): Collection<LightArgsClass> {
+ private fun createLightArgsClasses(
+ navInfo: NavInfo,
+ navEntry: NavEntry
+ ): Collection<LightArgsClass> {
return navEntry.data.resolvedDestinations
.filter { destination -> destination.arguments.isNotEmpty() }
.map { destination -> LightArgsClass(navInfo, navEntry, destination) }
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/SafeArgsModeModuleService.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/SafeArgsModeModuleService.kt
index ce537b6..7fb4b2d 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/SafeArgsModeModuleService.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/module/SafeArgsModeModuleService.kt
@@ -30,8 +30,7 @@
/**
* Component that owns and updates a module's [SafeArgsMode] state.
*
- * See also: [SafeArgsModeTrackerProjectService]
- * See also: [safeArgsMode]
+ * See also: [SafeArgsModeTrackerProjectService] See also: [safeArgsMode]
*/
class SafeArgsModeModuleService(val module: Module) : Disposable.Default {
fun interface SafeArgsModeChangedListener {
@@ -39,7 +38,8 @@
}
companion object {
- fun getInstance(module: Module): SafeArgsModeModuleService = module.getService(SafeArgsModeModuleService::class.java)
+ fun getInstance(module: Module): SafeArgsModeModuleService =
+ module.getService(SafeArgsModeModuleService::class.java)
val MODE_CHANGED: Topic<SafeArgsModeChangedListener> =
Topic(SafeArgsModeChangedListener::class.java, Topic.BroadcastDirection.TO_CHILDREN, true)
@@ -60,19 +60,23 @@
// initialized before here, so call update immediately just in case.
updateSafeArgsMode()
- GradleSyncState.subscribe(module.project, object : GradleSyncListener {
- override fun syncSucceeded(project: Project) {
- updateSafeArgsMode()
- }
+ GradleSyncState.subscribe(
+ module.project,
+ object : GradleSyncListener {
+ override fun syncSucceeded(project: Project) {
+ updateSafeArgsMode()
+ }
- override fun syncFailed(project: Project, errorMessage: String) {
- updateSafeArgsMode()
- }
+ override fun syncFailed(project: Project, errorMessage: String) {
+ updateSafeArgsMode()
+ }
- override fun syncSkipped(project: Project) {
- updateSafeArgsMode()
- }
- }, this)
+ override fun syncSkipped(project: Project) {
+ updateSafeArgsMode()
+ }
+ },
+ this
+ )
}
private fun updateSafeArgsMode() {
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/NavigationResourcesModificationListener.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/NavigationResourcesModificationListener.kt
index 1c4a172..cf50472 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/NavigationResourcesModificationListener.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/NavigationResourcesModificationListener.kt
@@ -51,18 +51,16 @@
import org.jetbrains.annotations.TestOnly
/**
- * A project-wide listener that determines which modules' navigation files are affected by VFS changes or Document
- * changes and sends a [NAVIGATION_RESOURCES_CHANGED] event to tell the corresponding
- * [ModuleNavigationResourcesModificationTracker]s and [ProjectNavigationResourceModificationTracker]s to increment counter.
+ * A project-wide listener that determines which modules' navigation files are affected by VFS
+ * changes or Document changes and sends a [NAVIGATION_RESOURCES_CHANGED] event to tell the
+ * corresponding [ModuleNavigationResourcesModificationTracker]s and
+ * [ProjectNavigationResourceModificationTracker]s to increment counter.
*
- * [NavigationResourcesModificationListener] registers itself to start actively listening for VFS changes and Document
- * changes after the project opening.
+ * [NavigationResourcesModificationListener] registers itself to start actively listening for VFS
+ * changes and Document changes after the project opening.
*/
-class NavigationResourcesModificationListener(
- project: Project
-) : PoliteAndroidVirtualFileListener(project),
- DocumentListener,
- FileDocumentManagerListener {
+class NavigationResourcesModificationListener(project: Project) :
+ PoliteAndroidVirtualFileListener(project), DocumentListener, FileDocumentManagerListener {
private val psiDocumentManager = PsiDocumentManager.getInstance(project)
private val fileDocumentManager = FileDocumentManager.getInstance()
@@ -74,7 +72,9 @@
}
override fun isRelevant(file: VirtualFile, facet: AndroidFacet): Boolean {
- if (ResourceFolderType.getFolderType(file.parent?.name.orEmpty()) == ResourceFolderType.NAVIGATION) {
+ if (
+ ResourceFolderType.getFolderType(file.parent?.name.orEmpty()) == ResourceFolderType.NAVIGATION
+ ) {
return true
}
@@ -82,18 +82,20 @@
return false
}
- // If module resources aren't cached, we don't want to load them now on the event thread; just say the file is relevant. In the case
- // where it's not truly relevant but we increment the modification trackers anyway, we may have some unnecessary cache invalidation.
- val moduleResources = StudioResourceRepositoryManager.getInstance(facet).cachedModuleResources ?: return true
- val navResourceVfs = moduleResources
- .getResources(ResourceNamespace.RES_AUTO, ResourceType.NAVIGATION)
- .values()
- .mapNotNull(ResourceItem::getSourceAsVirtualFile)
+ // If module resources aren't cached, we don't want to load them now on the event thread; just
+ // say the file is relevant. In the case
+ // where it's not truly relevant but we increment the modification trackers anyway, we may have
+ // some unnecessary cache invalidation.
+ val moduleResources =
+ StudioResourceRepositoryManager.getInstance(facet).cachedModuleResources ?: return true
+ val navResourceVfs =
+ moduleResources
+ .getResources(ResourceNamespace.RES_AUTO, ResourceType.NAVIGATION)
+ .values()
+ .mapNotNull(ResourceItem::getSourceAsVirtualFile)
// If the directory is an ancestor of any navigation resource files.
- return navResourceVfs.any { navVFile ->
- VfsUtilCore.isAncestor(file, navVFile, false)
- }
+ return navResourceVfs.any { navVFile -> VfsUtilCore.isAncestor(file, navVFile, false) }
}
override fun fileChanged(path: PathString, facet: AndroidFacet) {
@@ -101,7 +103,8 @@
}
override fun contentsChanged(event: VirtualFileEvent) {
- // Content changes are not handled at the VFS level but either in fileWithNoDocumentChanged or documentChanged
+ // Content changes are not handled at the VFS level but either in fileWithNoDocumentChanged or
+ // documentChanged
}
override fun fileWithNoDocumentChanged(file: VirtualFile) = possiblyIrrelevantFileChanged(file)
@@ -112,15 +115,15 @@
if (psiFile == null) {
fileDocumentManager.getFile(document)?.let { possiblyIrrelevantFileChanged(it) }
- }
- else {
+ } else {
psiFile.virtualFile?.let { possiblyIrrelevantFileChanged(it) }
}
}
/**
- * [StartupActivity] responsible for ensuring that a [Project] has a [NavigationResourcesModificationListener]
- * subscribed to listen for both VFS and Document changes when opening projects.
+ * [StartupActivity] responsible for ensuring that a [Project] has a
+ * [NavigationResourcesModificationListener] subscribed to listen for both VFS and Document
+ * changes when opening projects.
*/
class SubscriptionStartupActivity : ProjectActivity {
override suspend fun execute(project: Project) {
@@ -131,9 +134,11 @@
@Service(Service.Level.PROJECT)
private class Subscriber(private val project: Project) : Disposable.Default {
private val subscriber =
- object : LazyFileListenerSubscriber<NavigationResourcesModificationListener>(
- NavigationResourcesModificationListener(project), this
- ) {
+ object :
+ LazyFileListenerSubscriber<NavigationResourcesModificationListener>(
+ NavigationResourcesModificationListener(project),
+ this
+ ) {
override fun subscribe() {
// To receive all changes happening in the VFS. File modifications may
// not be picked up immediately if such changes are not saved on the disk yet
@@ -158,14 +163,15 @@
}
private fun dispatchResourcesChanged(module: Module?) {
- project.messageBus.syncPublisher(NAVIGATION_RESOURCES_CHANGED).onNavigationResourcesChanged(module)
+ project.messageBus
+ .syncPublisher(NAVIGATION_RESOURCES_CHANGED)
+ .onNavigationResourcesChanged(module)
}
companion object {
/**
- * Normally, this listener waits for the project to finish syncing before subscribing
- * to events, but for tests, we sometimes have to kickstart the subscription process
- * manually.
+ * Normally, this listener waits for the project to finish syncing before subscribing to events,
+ * but for tests, we sometimes have to kickstart the subscription process manually.
*/
@TestOnly
fun ensureSubscribed(project: Project) {
@@ -178,7 +184,8 @@
/**
* Called when the navigation resources for a given module have changed.
*
- * If [module] is `null`, this is the result of a project-wide change, and all modules should be considered changed.
+ * If [module] is `null`, this is the result of a project-wide change, and all modules should be
+ * considered changed.
*/
fun onNavigationResourcesChanged(module: Module?)
}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/ProjectNavigationResourceModificationTracker.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/ProjectNavigationResourceModificationTracker.kt
index 1af55a3..5a13bb0 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/ProjectNavigationResourceModificationTracker.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/ProjectNavigationResourceModificationTracker.kt
@@ -16,39 +16,40 @@
package com.android.tools.idea.nav.safeargs.project
import com.intellij.openapi.Disposable
-import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
-import com.intellij.openapi.startup.StartupManager
import com.intellij.openapi.util.ModificationTracker
import com.intellij.openapi.util.SimpleModificationTracker
/**
- * A project-wide modification tracker whose modification count is a value incremented by any modifications of
- * corresponding navigation resource files.
+ * A project-wide modification tracker whose modification count is a value incremented by any
+ * modifications of corresponding navigation resource files.
*/
-class ProjectNavigationResourceModificationTracker(project: Project) : ModificationTracker, Disposable.Default {
+class ProjectNavigationResourceModificationTracker(project: Project) :
+ ModificationTracker, Disposable.Default {
private val navigationModificationTracker = SimpleModificationTracker()
init {
- project.messageBus.connect(this).subscribe(
- NAVIGATION_RESOURCES_CHANGED,
- NavigationResourcesChangeListener {
- navigationChanged()
- }
- )
+ project.messageBus
+ .connect(this)
+ .subscribe(
+ NAVIGATION_RESOURCES_CHANGED,
+ NavigationResourcesChangeListener { navigationChanged() }
+ )
}
companion object {
@JvmStatic
- fun getInstance(project: Project) = project.getService(ProjectNavigationResourceModificationTracker::class.java)!!
+ fun getInstance(project: Project) =
+ project.getService(ProjectNavigationResourceModificationTracker::class.java)!!
}
override fun getModificationCount() = navigationModificationTracker.modificationCount
/**
- * This is invoked when NavigationModificationListener detects a navigation file has been changed or added or deleted for this project
+ * This is invoked when NavigationModificationListener detects a navigation file has been changed
+ * or added or deleted for this project
*/
private fun navigationChanged() {
navigationModificationTracker.incModificationCount()
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/SafeArgsEnabledFacetsProjectService.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/SafeArgsEnabledFacetsProjectService.kt
index 1db87bc..d6c3828 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/SafeArgsEnabledFacetsProjectService.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/SafeArgsEnabledFacetsProjectService.kt
@@ -28,17 +28,17 @@
import org.jetbrains.android.facet.AndroidFacet
/**
- * Component that tracks / caches all [AndroidFacet] instances that have enabled
- * safe args on them, for quick iteration in light class short names caches and
- * class finders.
+ * Component that tracks / caches all [AndroidFacet] instances that have enabled safe args on them,
+ * for quick iteration in light class short names caches and class finders.
*
- * This component also serves as a [ModificationTracker] that will allow caches
- * to know when this list might have been updated.
+ * This component also serves as a [ModificationTracker] that will allow caches to know when this
+ * list might have been updated.
*/
@Service
class SafeArgsEnabledFacetsProjectService(val project: Project) : ModificationTracker {
companion object {
- fun getInstance(project: Project): SafeArgsEnabledFacetsProjectService = project.getService(SafeArgsEnabledFacetsProjectService::class.java)
+ fun getInstance(project: Project): SafeArgsEnabledFacetsProjectService =
+ project.getService(SafeArgsEnabledFacetsProjectService::class.java)
}
private val modulesUsingSafeArgsCache: CachedValue<List<AndroidFacet>>
@@ -49,14 +49,19 @@
val cachedValuesManager = CachedValuesManager.getManager(project)
val facetManager = ProjectFacetManager.getInstance(project)
- modulesUsingSafeArgsCache = cachedValuesManager.createCachedValue(
- {
- val facets = facetManager.getFacets(AndroidFacet.ID)
- .filter { facet -> facet.isSafeArgsEnabled() }
+ modulesUsingSafeArgsCache =
+ cachedValuesManager.createCachedValue(
+ {
+ val facets =
+ facetManager.getFacets(AndroidFacet.ID).filter { facet -> facet.isSafeArgsEnabled() }
- CachedValueProvider.Result.create(facets, this)
- }, false)
+ CachedValueProvider.Result.create(facets, this)
+ },
+ false
+ )
}
- override fun getModificationCount() = ModuleManager.getInstance(project).modificationCount + project.safeArgsModeTracker.modificationCount
-}
\ No newline at end of file
+ override fun getModificationCount() =
+ ModuleManager.getInstance(project).modificationCount +
+ project.safeArgsModeTracker.modificationCount
+}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/SafeArgsModeTrackerProjectService.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/SafeArgsModeTrackerProjectService.kt
index d8178ff..40e9f1f 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/SafeArgsModeTrackerProjectService.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/project/SafeArgsModeTrackerProjectService.kt
@@ -28,28 +28,29 @@
* Component that owns a project-wide tracker which gets updated whenever any module's
* `safeArgsMode` is updated.
*
- * See also: [SafeArgsModeModuleService]
- * See also: [safeArgsModeTracker]
+ * See also: [SafeArgsModeModuleService] See also: [safeArgsModeTracker]
*/
@ThreadSafe
@Service(Service.Level.PROJECT)
-class SafeArgsModeTrackerProjectService(project: Project) : ModificationTracker, Disposable.Default {
+class SafeArgsModeTrackerProjectService(project: Project) :
+ ModificationTracker, Disposable.Default {
companion object {
- fun getInstance(project: Project) = project.getService(SafeArgsModeTrackerProjectService::class.java)!!
+ fun getInstance(project: Project) =
+ project.getService(SafeArgsModeTrackerProjectService::class.java)!!
}
init {
- project.messageBus.connect(this).subscribe(
- SafeArgsModeModuleService.MODE_CHANGED,
- SafeArgsModeModuleService.SafeArgsModeChangedListener { _, _ ->
- tracker.incModificationCount()
- }
- )
+ project.messageBus
+ .connect(this)
+ .subscribe(
+ SafeArgsModeModuleService.MODE_CHANGED,
+ SafeArgsModeModuleService.SafeArgsModeChangedListener { _, _ ->
+ tracker.incModificationCount()
+ }
+ )
}
- /**
- * A thread-safe modification tracker that should get updated
- */
+ /** A thread-safe modification tracker that should get updated */
private val tracker = SimpleModificationTracker()
override fun getModificationCount(): Long = tracker.modificationCount
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/ArgumentUtils.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/ArgumentUtils.kt
index bd3fe06..50852a5 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/ArgumentUtils.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/ArgumentUtils.kt
@@ -1,4 +1,5 @@
-// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the
+// Apache 2.0 license.
package com.android.tools.idea.nav.safeargs.psi
import com.android.tools.idea.nav.safeargs.index.NavActionData
@@ -34,18 +35,20 @@
val argsFromTargetDestination = action.getTargetDestination(data)?.arguments.orEmpty()
- val resolvedArguments = (action.arguments + argsFromTargetDestination)
- .groupBy { it.name }
- .map { entry ->
- if (entry.value.size > 1) checkArguments(entry, modulePackage)
- entry.value.first()
- }
+ val resolvedArguments =
+ (action.arguments + argsFromTargetDestination)
+ .groupBy { it.name }
+ .map { entry ->
+ if (entry.value.size > 1) checkArguments(entry, modulePackage)
+ entry.value.first()
+ }
- val adjustedArguments = if (adjustArgumentsWithDefaults) {
- resolvedArguments.sortedBy { it.defaultValue != null }
- } else {
- resolvedArguments
- }
+ val adjustedArguments =
+ if (adjustArgumentsWithDefaults) {
+ resolvedArguments.sortedBy { it.defaultValue != null }
+ } else {
+ resolvedArguments
+ }
return@mapNotNull object : NavActionData by action {
override val arguments: List<NavArgumentData> = adjustedArguments
@@ -53,15 +56,21 @@
}
/**
- * Warn if incompatible types of argument exist. We still provide best results though it fails to compile.
+ * Warn if incompatible types of argument exist. We still provide best results though it fails to
+ * compile.
*/
- private fun checkArguments(entry: Map.Entry<String, List<NavArgumentData>>, modulePackage: String) {
- val types = entry.value
- .asSequence()
- .map { arg -> getPsiTypeStr(modulePackage, arg.type, arg.defaultValue) }
- .toSet()
+ private fun checkArguments(
+ entry: Map.Entry<String, List<NavArgumentData>>,
+ modulePackage: String
+ ) {
+ val types =
+ entry.value
+ .asSequence()
+ .map { arg -> getPsiTypeStr(modulePackage, arg.type, arg.defaultValue) }
+ .toSet()
- if (types.size > 1) LOG.warn("Incompatible types of argument ${entry.key}: ${types.joinToString(", ")}.")
+ if (types.size > 1)
+ LOG.warn("Incompatible types of argument ${entry.key}: ${types.joinToString(", ")}.")
}
private val LOG = thisLogger()
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/SafeArgsFeatureVersions.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/SafeArgsFeatureVersions.kt
index 6c26c45..d43ae8e 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/SafeArgsFeatureVersions.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/SafeArgsFeatureVersions.kt
@@ -37,8 +37,8 @@
*/
internal fun AndroidFacet.findNavigationVersion(): Version {
return module
- .getModuleSystem()
- .getResolvedDependency(GoogleMavenArtifactId.ANDROIDX_NAVIGATION_COMMON.getCoordinate("+"))
- ?.lowerBoundVersion
- ?: GRADLE_VERSION_ZERO
+ .getModuleSystem()
+ .getResolvedDependency(GoogleMavenArtifactId.ANDROIDX_NAVIGATION_COMMON.getCoordinate("+"))
+ ?.lowerBoundVersion
+ ?: GRADLE_VERSION_ZERO
}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightActionBuilderClass.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightActionBuilderClass.kt
index 6fcecc6..f11a882 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightActionBuilderClass.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightActionBuilderClass.kt
@@ -21,7 +21,6 @@
import com.android.tools.idea.nav.safeargs.psi.xml.findChildTagElementByNameAttr
import com.android.tools.idea.nav.safeargs.psi.xml.findFirstMatchingElementByTraversingUp
import com.android.tools.idea.nav.safeargs.psi.xml.findXmlTagById
-import com.android.utils.usLocaleCapitalize
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
@@ -33,7 +32,6 @@
import com.intellij.psi.xml.XmlFile
import com.intellij.psi.xml.XmlTag
import org.jetbrains.android.augment.AndroidLightClassBase
-import org.jetbrains.android.facet.AndroidFacet
/**
* Inner class that is generated inside a Directions class, which helps build actions.
@@ -46,17 +44,28 @@
private val directionsClass: LightDirectionsClass,
private val action: NavActionData,
private val backingResourceFile: XmlFile?,
-) : AndroidLightClassBase(PsiManager.getInstance(navInfo.facet.module.project), setOf(PsiModifier.PUBLIC, PsiModifier.STATIC)) {
+) :
+ AndroidLightClassBase(
+ PsiManager.getInstance(navInfo.facet.module.project),
+ setOf(PsiModifier.PUBLIC, PsiModifier.STATIC)
+ ) {
private val NAV_DIRECTIONS_FQCN = "androidx.navigation.NavDirections"
private val name: String = className
private val qualifiedName: String = "${directionsClass.qualifiedName}.$name"
private val _constructors by lazy { computeConstructors() }
private val _methods by lazy { computeMethods() }
private val _fields by lazy { computeFields() }
- private val navDirectionsType by lazy { PsiType.getTypeByName(NAV_DIRECTIONS_FQCN, project, this.resolveScope) }
- private val navDirectionsClass by lazy { JavaPsiFacade.getInstance(project).findClass(NAV_DIRECTIONS_FQCN, this.resolveScope) }
+ private val navDirectionsType by lazy {
+ PsiType.getTypeByName(NAV_DIRECTIONS_FQCN, project, this.resolveScope)
+ }
+ private val navDirectionsClass by lazy {
+ JavaPsiFacade.getInstance(project).findClass(NAV_DIRECTIONS_FQCN, this.resolveScope)
+ }
private val _navigationElement by lazy {
- (directionsClass.navigationElement as? XmlTag)?.findFirstMatchingElementByTraversingUp(SdkConstants.TAG_ACTION, action.id)
+ (directionsClass.navigationElement as? XmlTag)?.findFirstMatchingElementByTraversingUp(
+ SdkConstants.TAG_ACTION,
+ action.id
+ )
}
override fun getName() = name
@@ -81,32 +90,41 @@
private fun computeMethods(): Array<PsiMethod> {
val thisType = PsiTypesUtil.getClassType(this)
- return action.arguments.flatMap { arg ->
- // Create a getter and setter per argument
- val argType = arg.parsePsiType(navInfo.packageName, this)
- val setter = createMethod(name = "set${arg.name.toUpperCamelCase()}",
- navigationElement = getFieldNavigationElementByName(arg.name),
- returnType = annotateNullability(thisType))
- .addParameter(arg.name.toCamelCase(), argType)
+ return action.arguments
+ .flatMap { arg ->
+ // Create a getter and setter per argument
+ val argType = arg.parsePsiType(navInfo.packageName, this)
+ val setter =
+ createMethod(
+ name = "set${arg.name.toUpperCamelCase()}",
+ navigationElement = getFieldNavigationElementByName(arg.name),
+ returnType = annotateNullability(thisType)
+ )
+ .addParameter(arg.name.toCamelCase(), argType)
- val getter = createMethod(name = "get${arg.name.toUpperCamelCase()}",
- navigationElement = getFieldNavigationElementByName(arg.name),
- returnType = annotateNullability(argType, arg.isNonNull()))
+ val getter =
+ createMethod(
+ name = "get${arg.name.toUpperCamelCase()}",
+ navigationElement = getFieldNavigationElementByName(arg.name),
+ returnType = annotateNullability(argType, arg.isNonNull())
+ )
- listOf(setter, getter)
- }.toTypedArray()
+ listOf(setter, getter)
+ }
+ .toTypedArray()
}
private fun computeConstructors(): Array<PsiMethod> {
- val privateConstructor = createConstructor().apply {
- action.arguments.forEach { arg ->
- if (arg.defaultValue == null) {
- val argType = arg.parsePsiType(navInfo.packageName, this)
- this.addParameter(arg.name.toCamelCase(), argType)
+ val privateConstructor =
+ createConstructor().apply {
+ action.arguments.forEach { arg ->
+ if (arg.defaultValue == null) {
+ val argType = arg.parsePsiType(navInfo.packageName, this)
+ this.addParameter(arg.name.toCamelCase(), argType)
+ }
}
+ this.setModifiers(PsiModifier.PRIVATE)
}
- this.setModifiers(PsiModifier.PRIVATE)
- }
return arrayOf(privateConstructor)
}
@@ -117,10 +135,15 @@
return action.arguments
.asSequence()
.map { arg ->
- // Since we support args overrides, we first try to locate argument tag within current action. If not found,
+ // Since we support args overrides, we first try to locate argument tag within current
+ // action. If not found,
// we search in the target destination tag.
- val targetArgumentTag = _navigationElement?.findChildTagElementByNameAttr(SdkConstants.TAG_ARGUMENT, arg.name)
- ?: targetDestinationTag?.findChildTagElementByNameAttr(SdkConstants.TAG_ARGUMENT, arg.name)
+ val targetArgumentTag =
+ _navigationElement?.findChildTagElementByNameAttr(SdkConstants.TAG_ARGUMENT, arg.name)
+ ?: targetDestinationTag?.findChildTagElementByNameAttr(
+ SdkConstants.TAG_ARGUMENT,
+ arg.name
+ )
createField(arg, navInfo.packageName, targetArgumentTag)
}
.toList()
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsBuilderClass.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsBuilderClass.kt
index d68e1f2..ba0df98 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsBuilderClass.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsBuilderClass.kt
@@ -21,13 +21,11 @@
import com.intellij.psi.PsiModifier
import com.intellij.psi.util.PsiTypesUtil
import org.jetbrains.android.augment.AndroidLightClassBase
-import org.jetbrains.android.facet.AndroidFacet
/**
* Light class for Args.Builder classes generated from navigation xml files.
*
* For example, if you had the following "nav.xml":
- *
* ```
* <action id="@+id/sendMessage" destination="@+id/editorFragment">
* <argument name="message" argType="string" />
@@ -36,7 +34,6 @@
* ```
*
* This would generate a builder class like the following:
- *
* ```
* class EditorFragmentArgs {
* static class Builder {
@@ -57,7 +54,11 @@
class LightArgsBuilderClass(
private val navInfo: NavInfo,
private val argsClass: LightArgsClass,
-) : AndroidLightClassBase(PsiManager.getInstance(navInfo.facet.module.project), setOf(PsiModifier.PUBLIC, PsiModifier.STATIC)) {
+) :
+ AndroidLightClassBase(
+ PsiManager.getInstance(navInfo.facet.module.project),
+ setOf(PsiModifier.PUBLIC, PsiModifier.STATIC)
+ ) {
companion object {
const val BUILDER_NAME = "Builder"
}
@@ -84,16 +85,17 @@
}
private fun computeConstructors(): Array<PsiMethod> {
- val copyConstructor = createConstructor()
- .addParameter("original", PsiTypesUtil.getClassType(argsClass))
+ val copyConstructor =
+ createConstructor().addParameter("original", PsiTypesUtil.getClassType(argsClass))
- val argsConstructor = createConstructor().apply {
- argsClass.destination.arguments.forEach { arg ->
- if (arg.defaultValue == null) {
- this.addParameter(arg.name.toCamelCase(), arg.parsePsiType(navInfo.packageName, this))
+ val argsConstructor =
+ createConstructor().apply {
+ argsClass.destination.arguments.forEach { arg ->
+ if (arg.defaultValue == null) {
+ this.addParameter(arg.name.toCamelCase(), arg.parsePsiType(navInfo.packageName, this))
+ }
}
}
- }
return arrayOf(copyConstructor, argsConstructor)
}
@@ -102,21 +104,34 @@
val thisType = PsiTypesUtil.getClassType(this)
// Create a getter and setter per argument
- val argMethods: Array<PsiMethod> = containingClass.destination.arguments.flatMap { arg ->
- val argType = arg.parsePsiType(navInfo.packageName, this)
- val setter = createMethod(name = "set${arg.name.toUpperCamelCase()}",
- navigationElement = containingClass.getFieldNavigationElementByName(arg.name),
- returnType = annotateNullability(thisType))
- .addParameter(arg.name.toCamelCase(), argType)
+ val argMethods: Array<PsiMethod> =
+ containingClass.destination.arguments
+ .flatMap { arg ->
+ val argType = arg.parsePsiType(navInfo.packageName, this)
+ val setter =
+ createMethod(
+ name = "set${arg.name.toUpperCamelCase()}",
+ navigationElement = containingClass.getFieldNavigationElementByName(arg.name),
+ returnType = annotateNullability(thisType)
+ )
+ .addParameter(arg.name.toCamelCase(), argType)
- val getter = createMethod(name = "get${arg.name.toUpperCamelCase()}",
- navigationElement = containingClass.getFieldNavigationElementByName(arg.name),
- returnType = annotateNullability(argType, arg.isNonNull()))
+ val getter =
+ createMethod(
+ name = "get${arg.name.toUpperCamelCase()}",
+ navigationElement = containingClass.getFieldNavigationElementByName(arg.name),
+ returnType = annotateNullability(argType, arg.isNonNull())
+ )
- listOf(setter, getter)
- }.toTypedArray()
+ listOf(setter, getter)
+ }
+ .toTypedArray()
- val build = createMethod(name = "build", returnType = annotateNullability(PsiTypesUtil.getClassType(argsClass)))
+ val build =
+ createMethod(
+ name = "build",
+ returnType = annotateNullability(PsiTypesUtil.getClassType(argsClass))
+ )
return argMethods + build
}
}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsClass.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsClass.kt
index a785b42..156ac70 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsClass.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsClass.kt
@@ -36,7 +36,6 @@
* An "Arg" represents an argument which can get passed from one destination to another.
*
* For example, if you had the following "nav.xml":
- *
* ```
* <argument
* android:name="message"
@@ -44,7 +43,6 @@
* ```
*
* This would generate a class like the following:
- *
* ```
* class EditorFragmentArgs implements NavArgs {
* static EditorFragmentArgs fromBundle(Bundle bundle);
@@ -66,8 +64,12 @@
private val _fields by lazy { computeFields() }
private val _methods by lazy { computeMethods() }
private val backingXmlTag by lazy { navEntry.backingXmlFile?.findXmlTagById(destination.id) }
- private val navArgsType by lazy { PsiType.getTypeByName(NAV_ARGS_FQCN, project, this.resolveScope) }
- private val navArgsClass by lazy { JavaPsiFacade.getInstance(project).findClass(NAV_ARGS_FQCN, this.resolveScope) }
+ private val navArgsType by lazy {
+ PsiType.getTypeByName(NAV_ARGS_FQCN, project, this.resolveScope)
+ }
+ private val navArgsClass by lazy {
+ JavaPsiFacade.getInstance(project).findClass(NAV_ARGS_FQCN, this.resolveScope)
+ }
override fun getImplementsListTypes() = arrayOf(navArgsType)
override fun getSuperTypes() = arrayOf(navArgsType)
@@ -91,42 +93,56 @@
private fun computeMethods(): Array<PsiMethod> {
val thisType = PsiTypesUtil.getClassType(this)
val bundleType = parsePsiType(navInfo.packageName, "android.os.Bundle", null, this)
- val savedStateHandleType = parsePsiType(navInfo.packageName, "androidx.lifecycle.SavedStateHandle", null, this)
+ val savedStateHandleType =
+ parsePsiType(navInfo.packageName, "androidx.lifecycle.SavedStateHandle", null, this)
val methods = mutableListOf<PsiMethod>()
- methods.addAll(destination.arguments.map { arg ->
- val psiType = arg.parsePsiType(navInfo.packageName, this)
- createMethod(name = "get${arg.name.toUpperCamelCase()}",
- navigationElement = getFieldNavigationElementByName(arg.name),
- returnType = annotateNullability(psiType, arg.isNonNull()))
- })
+ methods.addAll(
+ destination.arguments.map { arg ->
+ val psiType = arg.parsePsiType(navInfo.packageName, this)
+ createMethod(
+ name = "get${arg.name.toUpperCamelCase()}",
+ navigationElement = getFieldNavigationElementByName(arg.name),
+ returnType = annotateNullability(psiType, arg.isNonNull())
+ )
+ }
+ )
- methods.add(createMethod(name = "fromBundle",
- modifiers = MODIFIERS_STATIC_PUBLIC_METHOD,
- returnType = annotateNullability(thisType))
- .addParameter("bundle", bundleType))
+ methods.add(
+ createMethod(
+ name = "fromBundle",
+ modifiers = MODIFIERS_STATIC_PUBLIC_METHOD,
+ returnType = annotateNullability(thisType)
+ )
+ .addParameter("bundle", bundleType)
+ )
- // Add on version specific methods since the navigation library side is keeping introducing new methods.
+ // Add on version specific methods since the navigation library side is keeping introducing new
+ // methods.
if (navInfo.navVersion >= SafeArgsFeatureVersions.FROM_SAVED_STATE_HANDLE) {
methods.add(
createMethod(
- name = "fromSavedStateHandle",
- modifiers = MODIFIERS_STATIC_PUBLIC_METHOD,
- returnType = annotateNullability(thisType)
- ).addParameter("savedStateHandle", savedStateHandleType)
+ name = "fromSavedStateHandle",
+ modifiers = MODIFIERS_STATIC_PUBLIC_METHOD,
+ returnType = annotateNullability(thisType)
+ )
+ .addParameter("savedStateHandle", savedStateHandleType)
)
}
- // Add on version specific methods since the navigation library side is keeping introducing new methods.
+ // Add on version specific methods since the navigation library side is keeping introducing new
+ // methods.
if (navInfo.navVersion >= SafeArgsFeatureVersions.TO_SAVED_STATE_HANDLE) {
- methods.add(createMethod(name = "toSavedStateHandle", returnType = annotateNullability(savedStateHandleType)))
+ methods.add(
+ createMethod(
+ name = "toSavedStateHandle",
+ returnType = annotateNullability(savedStateHandleType)
+ )
+ )
}
- methods.add(createMethod(
- name = "toBundle",
- returnType = annotateNullability(bundleType)
- ))
+ methods.add(createMethod(name = "toBundle", returnType = annotateNullability(bundleType)))
return methods.toTypedArray()
}
@@ -135,7 +151,8 @@
return destination.arguments
.asSequence()
.map { arg ->
- val targetArgumentTag = backingXmlTag?.findChildTagElementByNameAttr(SdkConstants.TAG_ARGUMENT, arg.name)
+ val targetArgumentTag =
+ backingXmlTag?.findChildTagElementByNameAttr(SdkConstants.TAG_ARGUMENT, arg.name)
createField(arg, navInfo.packageName, targetArgumentTag)
}
.toList()
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightDirectionsClass.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightDirectionsClass.kt
index 55872ca..eb53895 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightDirectionsClass.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightDirectionsClass.kt
@@ -35,7 +35,6 @@
* A "Direction" represents functionality that takes you away from one destination to another.
*
* For example, if you had the following "nav.xml":
- *
* ```
* <navigation>
* <fragment id="@+id/mainMenu">
@@ -54,7 +53,6 @@
* ```
*
* This would generate a class like the following:
- *
* ```
* class MainMenuDirections {
* static NavDirections actionToOptions();
@@ -71,16 +69,16 @@
*
* ```
*/
-class LightDirectionsClass(
- navInfo: NavInfo,
- navEntry: NavEntry,
- destination: NavDestinationData
-) : SafeArgsLightBaseClass(navInfo, navEntry, destination, "Directions") {
- private val LOG get() = Logger.getInstance(LightDirectionsClass::class.java)
+class LightDirectionsClass(navInfo: NavInfo, navEntry: NavEntry, destination: NavDestinationData) :
+ SafeArgsLightBaseClass(navInfo, navEntry, destination, "Directions") {
+ private val LOG
+ get() = Logger.getInstance(LightDirectionsClass::class.java)
private val actionClasses by lazy { computeInnerClasses() }
private val _methods by lazy { computeMethods() }
private val _navigationElement by lazy { navEntry.backingXmlFile?.findXmlTagById(destination.id) }
- private val _actions by lazy { destination.getActionsWithResolvedArguments(navEntry.data, navInfo.packageName) }
+ private val _actions by lazy {
+ destination.getActionsWithResolvedArguments(navEntry.data, navInfo.packageName)
+ }
override fun getMethods() = _methods
override fun getAllMethods() = methods
@@ -99,18 +97,27 @@
}
private fun computeMethods(): Array<PsiMethod> {
- val navDirectionsType = parsePsiType(navInfo.packageName, "androidx.navigation.NavDirections", null, this)
+ val navDirectionsType =
+ parsePsiType(navInfo.packageName, "androidx.navigation.NavDirections", null, this)
return _actions
.map { action ->
val methodName = action.id.toCamelCase()
- val resolvedNavigationElement = _navigationElement?.findFirstMatchingElementByTraversingUp(SdkConstants.TAG_ACTION, action.id)
- val resolvedNavDirectionsType = actionClasses.find { it.name!!.usLocaleDecapitalize() == methodName }
- ?.let { PsiTypesUtil.getClassType(it) }
- ?: navDirectionsType
- createMethod(name = methodName,
- navigationElement = resolvedNavigationElement,
- modifiers = MODIFIERS_STATIC_PUBLIC_METHOD,
- returnType = annotateNullability(resolvedNavDirectionsType))
+ val resolvedNavigationElement =
+ _navigationElement?.findFirstMatchingElementByTraversingUp(
+ SdkConstants.TAG_ACTION,
+ action.id
+ )
+ val resolvedNavDirectionsType =
+ actionClasses
+ .find { it.name!!.usLocaleDecapitalize() == methodName }
+ ?.let { PsiTypesUtil.getClassType(it) }
+ ?: navDirectionsType
+ createMethod(
+ name = methodName,
+ navigationElement = resolvedNavigationElement,
+ modifiers = MODIFIERS_STATIC_PUBLIC_METHOD,
+ returnType = annotateNullability(resolvedNavDirectionsType)
+ )
.apply {
action.arguments.forEach { arg ->
if (arg.defaultValue == null) {
@@ -119,8 +126,8 @@
}
}
}
-
- }.toTypedArray()
+ }
+ .toTypedArray()
}
private fun computeInnerClasses(): Array<PsiClass> {
@@ -133,6 +140,4 @@
}
.toTypedArray()
}
-
}
-
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/SafeArgsLightBaseClass.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/SafeArgsLightBaseClass.kt
index 70cbbc5..b1e38cf 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/SafeArgsLightBaseClass.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/SafeArgsLightBaseClass.kt
@@ -15,11 +15,9 @@
*/
package com.android.tools.idea.nav.safeargs.psi.java
-import com.android.ide.common.resources.ResourceItem
import com.android.tools.idea.nav.safeargs.index.NavDestinationData
import com.android.tools.idea.nav.safeargs.module.NavEntry
import com.android.tools.idea.nav.safeargs.module.NavInfo
-import com.android.tools.idea.res.getSourceAsVirtualFile
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
@@ -27,19 +25,19 @@
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiModifier
-import com.intellij.psi.xml.XmlFile
import org.jetbrains.android.augment.AndroidLightClassBase
-import org.jetbrains.android.facet.AndroidFacet
-/**
- * Common functionality for all safe args light classes.
- */
+/** Common functionality for all safe args light classes. */
abstract class SafeArgsLightBaseClass(
protected val navInfo: NavInfo,
protected val navEntry: NavEntry,
val destination: NavDestinationData,
suffix: String,
-) : AndroidLightClassBase(PsiManager.getInstance(navInfo.facet.module.project), setOf(PsiModifier.PUBLIC, PsiModifier.FINAL)) {
+) :
+ AndroidLightClassBase(
+ PsiManager.getInstance(navInfo.facet.module.project),
+ setOf(PsiModifier.PUBLIC, PsiModifier.FINAL)
+ ) {
private val name: String
private val qualifiedName: String
@@ -49,15 +47,20 @@
super.setModuleInfo(navInfo.facet.module, false)
val fileFactory = PsiFileFactory.getInstance(project)
- qualifiedName = destination.name.let { name ->
- val nameWithoutSuffix = if (!name.startsWith('.')) name else "${navInfo.packageName}$name"
- "$nameWithoutSuffix$suffix"
- }
+ qualifiedName =
+ destination.name.let { name ->
+ val nameWithoutSuffix = if (!name.startsWith('.')) name else "${navInfo.packageName}$name"
+ "$nameWithoutSuffix$suffix"
+ }
name = qualifiedName.substringAfterLast('.')
// Create a placeholder backing file to represent this light class
- backingFile = fileFactory.createFileFromText("${name}.java", JavaFileType.INSTANCE,
- "// This class is generated on-the-fly by the IDE.") as PsiJavaFile
+ backingFile =
+ fileFactory.createFileFromText(
+ "${name}.java",
+ JavaFileType.INSTANCE,
+ "// This class is generated on-the-fly by the IDE."
+ ) as PsiJavaFile
backingFile.packageName = (qualifiedName.substringBeforeLast('.'))
}
@@ -69,4 +72,4 @@
override fun getNavigationElement(): PsiElement {
return navEntry.backingXmlFile ?: return super.getNavigationElement()
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/SafeArgsLightClassUtils.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/SafeArgsLightClassUtils.kt
index 2360259..9ce53fc 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/SafeArgsLightClassUtils.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/java/SafeArgsLightClassUtils.kt
@@ -41,38 +41,42 @@
private const val INT_ARRAY = "int[]"
private const val FALLBACK_TYPE = STRING_FQCN
-private val NAV_TO_JAVA_TYPE_MAP = mapOf(
- "string" to STRING_FQCN,
- "string[]" to STRING_FQCN_ARRAY,
- "integer" to PsiTypes.intType().name,
- "integer[]" to INT_ARRAY,
- "reference" to PsiTypes.intType().name,
- "reference[]" to INT_ARRAY
-)
+private val NAV_TO_JAVA_TYPE_MAP =
+ mapOf(
+ "string" to STRING_FQCN,
+ "string[]" to STRING_FQCN_ARRAY,
+ "integer" to PsiTypes.intType().name,
+ "integer[]" to INT_ARRAY,
+ "reference" to PsiTypes.intType().name,
+ "reference[]" to INT_ARRAY
+ )
/**
- * Given type strings we pull out of navigation xml files, generate a corresponding [PsiType]
- * for them.
+ * Given type strings we pull out of navigation xml files, generate a corresponding [PsiType] for
+ * them.
*
* @param modulePackage The current package that safe args are being generated into. This will be
- * used if `typeStr` is specified with a relative path name (i.e. if it starts with '.')
+ * used if `typeStr` is specified with a relative path name (i.e. if it starts with '.')
* @param context The [PsiElement] context we are in when creating this [PsiType] -- this is needed
- * for IntelliJ machinery.
+ * for IntelliJ machinery.
* @param typeStr A String of the type we want to create, e.g. "com.example.SomeClass". This value
- * can start with a '.', e.g. ".util.SomeClass", at which point it will be placed within the
- * current module package. This value can also be a special type as documented here:
- * https://developer.android.com/guide/navigation/navigation-pass-data#supported_argument_types
- * If null, `defaultValue` will be used to infer the type.
+ * can start with a '.', e.g. ".util.SomeClass", at which point it will be placed within the
+ * current module package. This value can also be a special type as documented here:
+ * https://developer.android.com/guide/navigation/navigation-pass-data#supported_argument_types If
+ * null, `defaultValue` will be used to infer the type.
* @param defaultValue The default value specified for this type. This is used as a fallback if
- * `typeStr` itself is not specified.
- *
+ * `typeStr` itself is not specified.
*/
-fun parsePsiType(modulePackage: String, typeStr: String?, defaultValue: String?, context: PsiElement): PsiType {
+fun parsePsiType(
+ modulePackage: String,
+ typeStr: String?,
+ defaultValue: String?,
+ context: PsiElement
+): PsiType {
val psiTypeStr = getPsiTypeStr(modulePackage, typeStr, defaultValue)
return try {
PsiElementFactory.getInstance(context.project).createTypeFromText(psiTypeStr, context)
- }
- catch (e: IncorrectOperationException) {
+ } catch (e: IncorrectOperationException) {
PsiElementFactory.getInstance(context.project).createTypeFromText(FALLBACK_TYPE, context)
}
}
@@ -81,7 +85,8 @@
parsePsiType(modulePackage, type, defaultValue, context)
fun getPsiTypeStr(modulePackage: String, typeStr: String?, defaultValue: String?): String {
- // When specified as inputs to safe args, inner classes in XML should use the Java syntax (e.g. "Outer$Inner"), but IntelliJ resolves
+ // When specified as inputs to safe args, inner classes in XML should use the Java syntax (e.g.
+ // "Outer$Inner"), but IntelliJ resolves
// the type using dot syntax ("Outer.Inner")
var psiTypeStr = typeStr?.replace('$', '.')
@@ -144,8 +149,7 @@
try {
Integer.parseUnsignedInt(this.substring(2), 16)
return PsiTypes.intType().name
- }
- catch (ignore: NumberFormatException) {
+ } catch (ignore: NumberFormatException) {
return null
}
}
@@ -169,22 +173,30 @@
val fallback = this.navigationElement
return LightMethodBuilder(this, JavaLanguage.INSTANCE)
.setConstructor(true)
- .addModifiers(*modifiers).apply {
- this.navigationElement = navigationElement ?: fallback
- }
+ .addModifiers(*modifiers)
+ .apply { this.navigationElement = navigationElement ?: fallback }
}
-internal fun PsiClass.createField(arg: NavArgumentData, modulePackage: String, xmlTag: XmlTag?): LightFieldBuilder {
+internal fun PsiClass.createField(
+ arg: NavArgumentData,
+ modulePackage: String,
+ xmlTag: XmlTag?
+): LightFieldBuilder {
val psiType = arg.parsePsiType(modulePackage, this)
val nonNull = psiType is PsiPrimitiveType || arg.isNonNull()
- return NullabilityLightFieldBuilder(manager, arg.name, psiType, nonNull, PsiModifier.PUBLIC, PsiModifier.FINAL).apply {
- this.navigationElement = xmlTag ?: this.navigationElement
- }
+ return NullabilityLightFieldBuilder(
+ manager,
+ arg.name,
+ psiType,
+ nonNull,
+ PsiModifier.PUBLIC,
+ PsiModifier.FINAL
+ )
+ .apply { this.navigationElement = xmlTag ?: this.navigationElement }
}
/**
- * Annotate the target type with the proper nullability based on the <argument> nullable
- * attribute.
+ * Annotate the target type with the proper nullability based on the <argument> nullable attribute.
*/
internal fun PsiClass.annotateNullability(psiType: PsiType, isNonNull: Boolean = true): PsiType {
val nonNull = psiType is PsiPrimitiveType || isNonNull
@@ -201,10 +213,13 @@
return LightMethodBuilder(manager, JavaLanguage.INSTANCE, name)
.setContainingClass(this)
.setModifiers(*modifiers)
- .setMethodReturnType(returnType).apply {
- this.navigationElement = navigationElement ?: this@createMethod.navigationElement
- }
+ .setMethodReturnType(returnType)
+ .apply { this.navigationElement = navigationElement ?: this@createMethod.navigationElement }
}
-fun String.toCamelCase() = this.split("_").mapIndexed { index, s -> if (index > 0) s.usLocaleCapitalize() else s }.joinToString("")
-fun String.toUpperCamelCase() = this.toCamelCase().usLocaleCapitalize()
\ No newline at end of file
+fun String.toCamelCase() =
+ this.split("_")
+ .mapIndexed { index, s -> if (index > 0) s.usLocaleCapitalize() else s }
+ .joinToString("")
+
+fun String.toUpperCamelCase() = this.toCamelCase().usLocaleCapitalize()
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/xml/SafeArgsXmlTag.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/xml/SafeArgsXmlTag.kt
index 3a3d544..45280e7 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/xml/SafeArgsXmlTag.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/xml/SafeArgsXmlTag.kt
@@ -51,8 +51,8 @@
override fun isEquivalentTo(another: PsiElement?): Boolean {
val anotherSafeArgsXmlTag = another as? SafeArgsXmlTag ?: return false
- return xmlTag.isEquivalentTo(xmlTag)
- && name == anotherSafeArgsXmlTag.name
- && containerIdentifier == anotherSafeArgsXmlTag.containerIdentifier
+ return xmlTag.isEquivalentTo(xmlTag) &&
+ name == anotherSafeArgsXmlTag.name &&
+ containerIdentifier == anotherSafeArgsXmlTag.containerIdentifier
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/xml/XmlUtils.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/xml/XmlUtils.kt
index c172fc3..9c9c263 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/xml/XmlUtils.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/psi/xml/XmlUtils.kt
@@ -23,16 +23,17 @@
fun XmlFile.findXmlTagById(attrId: String): XmlTag? {
var resultTag: XmlTag? = null
- val visitor = object : XmlRecursiveElementWalkingVisitor() {
- override fun visitXmlTag(tag: XmlTag) {
- super.visitXmlTag(tag)
- // unique resource id in the same xml file
- if (tag.isTagIdEqualTo(attrId)) {
- resultTag = tag
- stopWalking()
+ val visitor =
+ object : XmlRecursiveElementWalkingVisitor() {
+ override fun visitXmlTag(tag: XmlTag) {
+ super.visitXmlTag(tag)
+ // unique resource id in the same xml file
+ if (tag.isTagIdEqualTo(attrId)) {
+ resultTag = tag
+ stopWalking()
+ }
}
}
- }
this.accept(visitor)
return resultTag
}
diff --git a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/tracker/SafeArgsTracker.kt b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/tracker/SafeArgsTracker.kt
index b5341b8..1937903 100644
--- a/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/tracker/SafeArgsTracker.kt
+++ b/nav/safeargs/common/src/com/android/tools/idea/nav/safeargs/tracker/SafeArgsTracker.kt
@@ -16,18 +16,16 @@
package com.android.tools.idea.nav.safeargs.tracker
import com.android.tools.analytics.UsageTracker
+import com.android.tools.analytics.withProjectId
import com.android.tools.idea.nav.safeargs.SafeArgsMode
import com.android.tools.idea.nav.safeargs.safeArgsMode
import com.android.tools.idea.projectsystem.getAndroidFacets
-import com.android.tools.analytics.withProjectId
import com.google.wireless.android.sdk.stats.AndroidStudioEvent
import com.google.wireless.android.sdk.stats.NavSafeArgsEvent
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
-/**
- * A service which allows tracking safe args related metrics.
- */
+/** A service which allows tracking safe args related metrics. */
abstract class SafeArgsTracker(private val project: Project) {
companion object {
@JvmStatic
@@ -43,22 +41,25 @@
val kotlinPluginFacets = allFacets.count { it.safeArgsMode == SafeArgsMode.KOTLIN }
if (javaPluginFacets + kotlinPluginFacets == 0) return@runSlowWork
- val safeArgsEvent = NavSafeArgsEvent.newBuilder()
- .setEventContext(context)
- .setProjectMetadata(NavSafeArgsEvent.ProjectMetadata.newBuilder()
- .setModuleCount(allFacets.size)
- .setJavaPluginCount(javaPluginFacets)
- .setKotlinPluginCount(kotlinPluginFacets))
+ val safeArgsEvent =
+ NavSafeArgsEvent.newBuilder()
+ .setEventContext(context)
+ .setProjectMetadata(
+ NavSafeArgsEvent.ProjectMetadata.newBuilder()
+ .setModuleCount(allFacets.size)
+ .setJavaPluginCount(javaPluginFacets)
+ .setKotlinPluginCount(kotlinPluginFacets)
+ )
track(safeArgsEvent)
}
}
private fun track(safeArgsEvent: NavSafeArgsEvent.Builder) {
- val studioEvent = AndroidStudioEvent
- .newBuilder()
- .setKind(AndroidStudioEvent.EventKind.NAV_SAFE_ARGS_EVENT)
- .setNavSafeArgsEvent(safeArgsEvent)
+ val studioEvent =
+ AndroidStudioEvent.newBuilder()
+ .setKind(AndroidStudioEvent.EventKind.NAV_SAFE_ARGS_EVENT)
+ .setNavSafeArgsEvent(safeArgsEvent)
UsageTracker.log(studioEvent.withProjectId(project))
}
@@ -75,4 +76,4 @@
override fun runSlowWork(block: () -> Unit) {
ApplicationManager.getApplication().executeOnPooledThread { block() }
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KotlinTypeUtils.kt b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KotlinTypeUtils.kt
index 26b409b..a2e0c52 100644
--- a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KotlinTypeUtils.kt
+++ b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KotlinTypeUtils.kt
@@ -42,34 +42,28 @@
moduleDescriptor: ModuleDescriptor,
isNonNull: Boolean = true
): KotlinType {
- val modulePackageName = moduleDescriptor.module.toModule()?.getModuleSystem()?.getPackageName() ?: ""
+ val modulePackageName =
+ moduleDescriptor.module.toModule()?.getModuleSystem()?.getPackageName() ?: ""
val resolvedTypeStr = getPsiTypeStr(modulePackageName, typeStr, defaultValue)
// array type
if (resolvedTypeStr.endsWith("[]")) {
val type = resolvedTypeStr.removeSuffix("[]")
- val arrayType = try {
- JvmPrimitiveType.get(type).primitiveType.let {
- getPrimitiveArrayKotlinType(it)
+ val arrayType =
+ try {
+ JvmPrimitiveType.get(type).primitiveType.let { getPrimitiveArrayKotlinType(it) }
+ } catch (e: AssertionError) {
+ this.getArrayType(Variance.INVARIANT, getKotlinClassType(FqName(type), moduleDescriptor))
}
- }
- catch (e: AssertionError) {
- this.getArrayType(Variance.INVARIANT, getKotlinClassType(FqName(type), moduleDescriptor))
- }
- if (isNonNull) return arrayType
- else return arrayType.makeNullable()
+ if (isNonNull) return arrayType else return arrayType.makeNullable()
}
return try {
- JvmPrimitiveType.get(resolvedTypeStr).primitiveType.let {
- getPrimitiveKotlinType(it)
- }
- }
- catch (e: AssertionError) {
+ JvmPrimitiveType.get(resolvedTypeStr).primitiveType.let { getPrimitiveKotlinType(it) }
+ } catch (e: AssertionError) {
val rawType = getKotlinClassType(FqName(resolvedTypeStr), moduleDescriptor)
- if (isNonNull) return rawType
- else return rawType.makeNullable()
+ if (isNonNull) return rawType else return rawType.makeNullable()
}
}
@@ -78,13 +72,16 @@
moduleDescriptor: ModuleDescriptor
): KotlinType {
val classId = JavaToKotlinClassMap.mapJavaToKotlin(fqName)
- val classDescriptor = if (classId != null) getBuiltInClassByFqName(classId.asSingleFqName()) else null
+ val classDescriptor =
+ if (classId != null) getBuiltInClassByFqName(classId.asSingleFqName()) else null
return classDescriptor?.defaultType
- ?: ClassId.topLevel(fqName).let { moduleDescriptor.findClassAcrossModuleDependencies(it)?.defaultType }
- ?: fqName.getUnresolvedType()
+ ?: ClassId.topLevel(fqName).let {
+ moduleDescriptor.findClassAcrossModuleDependencies(it)?.defaultType
+ }
+ ?: fqName.getUnresolvedType()
}
private fun FqName.getUnresolvedType(): KotlinType {
val presentableName = this.toString()
return ErrorUtils.createErrorType(ErrorTypeKind.UNRESOLVED_TYPE, presentableName)
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KtArgsPackageDescriptor.kt b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KtArgsPackageDescriptor.kt
index 59a29db..608999d 100644
--- a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KtArgsPackageDescriptor.kt
+++ b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KtArgsPackageDescriptor.kt
@@ -34,7 +34,8 @@
import org.jetbrains.kotlin.utils.alwaysTrue
/**
- * Args Kt package descriptor, which wraps and indirectly exposes a [LightArgsKtClass] class descriptor
+ * Args Kt package descriptor, which wraps and indirectly exposes a [LightArgsKtClass] class
+ * descriptor
*/
class KtArgsPackageDescriptor(
private val containingNavFileInfo: SafeArgsNavFileInfo,
@@ -56,24 +57,29 @@
private val safeArgsPackageDescriptor = this@KtArgsPackageDescriptor
private inner class SafeArgsModuleScope : MemberScopeImpl() {
- private val classes = storageManager.createLazyValue {
- val argsClass = LightArgsKtClass(
- containingNavFileInfo.navInfo,
- className,
- destination,
- superTypesProvider(safeArgsPackageDescriptor),
- sourceElement,
- safeArgsPackageDescriptor,
- storageManager
- )
- listOfNotNull(argsClass)
- }
+ private val classes =
+ storageManager.createLazyValue {
+ val argsClass =
+ LightArgsKtClass(
+ containingNavFileInfo.navInfo,
+ className,
+ destination,
+ superTypesProvider(safeArgsPackageDescriptor),
+ sourceElement,
+ safeArgsPackageDescriptor,
+ storageManager
+ )
+ listOfNotNull(argsClass)
+ }
override fun getContributedDescriptors(
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
): Collection<DeclarationDescriptor> {
- return classes().filter { kindFilter.acceptsKinds(DescriptorKindFilter.NON_SINGLETON_CLASSIFIERS_MASK) && nameFilter(it.name) }
+ return classes().filter {
+ kindFilter.acceptsKinds(DescriptorKindFilter.NON_SINGLETON_CLASSIFIERS_MASK) &&
+ nameFilter(it.name)
+ }
}
override fun getClassifierNames(): Set<Name> {
@@ -82,7 +88,10 @@
.mapTo(mutableSetOf()) { it.name }
}
- override fun getContributedClassifier(name: Name, location: LookupLocation): ClassifierDescriptor? {
+ override fun getContributedClassifier(
+ name: Name,
+ location: LookupLocation
+ ): ClassifierDescriptor? {
return classes().firstOrNull { it.name == name }
}
@@ -90,4 +99,4 @@
p.println(this::class.java.simpleName)
}
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KtDescriptorCacheModuleService.kt b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KtDescriptorCacheModuleService.kt
index 5330566..655be75 100644
--- a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KtDescriptorCacheModuleService.kt
+++ b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KtDescriptorCacheModuleService.kt
@@ -44,19 +44,24 @@
/**
* A module service which stores safe args kt package descriptors([KtArgsPackageDescriptor]s and
* [KtDirectionsPackageDescriptor]s) by querying from [NavXmlIndex].
- *
*/
class KtDescriptorCacheModuleService(private val module: Module) : Disposable.Default {
private val fetcher = NavInfoFetcher(this, module, SafeArgsMode.KOTLIN)
- private data class QualifiedDescriptor(val fqName: FqName, val descriptor: PackageFragmentDescriptor)
+ private data class QualifiedDescriptor(
+ val fqName: FqName,
+ val descriptor: PackageFragmentDescriptor
+ )
companion object {
@JvmStatic
- fun getInstance(module: Module) = module.getService(KtDescriptorCacheModuleService::class.java)!!
+ fun getInstance(module: Module) =
+ module.getService(KtDescriptorCacheModuleService::class.java)!!
}
- fun getDescriptors(moduleDescriptor: ModuleDescriptor): Map<FqName, List<PackageFragmentDescriptor>> {
+ fun getDescriptors(
+ moduleDescriptor: ModuleDescriptor
+ ): Map<FqName, List<PackageFragmentDescriptor>> {
ProgressManager.checkCanceled()
val navInfo = fetcher.getCurrentNavInfo() ?: return emptyMap()
@@ -64,12 +69,13 @@
return navInfo.entries
.asSequence()
.flatMap { navEntry ->
- val sourceElement = navEntry.backingXmlFile?.let { XmlSourceElement(it) } ?: SourceElement.NO_SOURCE
+ val sourceElement =
+ navEntry.backingXmlFile?.let { XmlSourceElement(it) } ?: SourceElement.NO_SOURCE
val navFileInfo = SafeArgsNavFileInfo(moduleDescriptor, module, navInfo, navEntry)
val packages =
createArgsPackages(navFileInfo, sourceElement) +
- createDirectionsPackages(navFileInfo, sourceElement)
+ createDirectionsPackages(navFileInfo, sourceElement)
packages.asSequence()
}
@@ -85,36 +91,38 @@
.asSequence()
.filter { destination -> destination.actions.isNotEmpty() }
.mapNotNull { destination ->
- val fqName = destination.name.let { name ->
- val resolvedName = if (!name.startsWith('.')) name else "${navFileInfo.navInfo.packageName}$name"
- resolvedName + "Directions"
- }
+ val fqName =
+ destination.name.let { name ->
+ val resolvedName =
+ if (!name.startsWith('.')) name else "${navFileInfo.navInfo.packageName}$name"
+ resolvedName + "Directions"
+ }
val className = fqName.substringAfterLast('.').let { Name.identifier(it) }
val packageName = FqName(fqName.substringBeforeLast('.'))
- val resolvedSourceElement = (sourceElement.getPsi() as? XmlFile)
- ?.findXmlTagById(destination.id)
- ?.let {
- XmlSourceElement(
- SafeArgsXmlTag(
- it as XmlTagImpl,
- IconManager.getInstance().getPlatformIcon(PlatformIcons.Class),
- className.asString(),
- packageName.asString()
- )
- )
- }
- ?: sourceElement
+ val resolvedSourceElement =
+ (sourceElement.getPsi() as? XmlFile)?.findXmlTagById(destination.id)?.let {
+ XmlSourceElement(
+ SafeArgsXmlTag(
+ it as XmlTagImpl,
+ IconManager.getInstance().getPlatformIcon(PlatformIcons.Class),
+ className.asString(),
+ packageName.asString()
+ )
+ )
+ }
+ ?: sourceElement
- val packageDescriptor = KtDirectionsPackageDescriptor(
- navFileInfo,
- packageName,
- className,
- destination,
- resolvedSourceElement,
- storageManager
- )
+ val packageDescriptor =
+ KtDirectionsPackageDescriptor(
+ navFileInfo,
+ packageName,
+ className,
+ destination,
+ resolvedSourceElement,
+ storageManager
+ )
QualifiedDescriptor(packageName, packageDescriptor)
}
@@ -130,43 +138,49 @@
.asSequence()
.filter { destination -> destination.arguments.isNotEmpty() }
.mapNotNull { destination ->
-
- val fqName = destination.name.let { name ->
- val resolvedName = if (!name.startsWith('.')) name else "${navFileInfo.navInfo.packageName}$name"
- resolvedName + "Args"
- }
+ val fqName =
+ destination.name.let { name ->
+ val resolvedName =
+ if (!name.startsWith('.')) name else "${navFileInfo.navInfo.packageName}$name"
+ resolvedName + "Args"
+ }
val className = fqName.substringAfterLast('.').let { Name.identifier(it) }
val packageName = FqName(fqName.substringBeforeLast('.'))
- val resolvedSourceElement = (sourceElement.getPsi() as? XmlFile)
- ?.findXmlTagById(destination.id)
- ?.let {
- XmlSourceElement(
- SafeArgsXmlTag(
- it as XmlTagImpl,
- IconManager.getInstance().getPlatformIcon(PlatformIcons.Class),
- className.asString(),
- packageName.asString()
- )
- )
- }
- ?: sourceElement
+ val resolvedSourceElement =
+ (sourceElement.getPsi() as? XmlFile)?.findXmlTagById(destination.id)?.let {
+ XmlSourceElement(
+ SafeArgsXmlTag(
+ it as XmlTagImpl,
+ IconManager.getInstance().getPlatformIcon(PlatformIcons.Class),
+ className.asString(),
+ packageName.asString()
+ )
+ )
+ }
+ ?: sourceElement
val superTypesProvider = { packageDescriptor: PackageFragmentDescriptorImpl ->
- val ktType = packageDescriptor.builtIns.getKotlinType("androidx.navigation.NavArgs", null, packageDescriptor.module)
+ val ktType =
+ packageDescriptor.builtIns.getKotlinType(
+ "androidx.navigation.NavArgs",
+ null,
+ packageDescriptor.module
+ )
listOf(ktType)
}
- val packageDescriptor = KtArgsPackageDescriptor(
- navFileInfo,
- packageName,
- className,
- destination,
- superTypesProvider,
- resolvedSourceElement,
- storageManager
- )
+ val packageDescriptor =
+ KtArgsPackageDescriptor(
+ navFileInfo,
+ packageName,
+ className,
+ destination,
+ superTypesProvider,
+ resolvedSourceElement,
+ storageManager
+ )
QualifiedDescriptor(packageName, packageDescriptor)
}
diff --git a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KtDirectionsPackageDescriptor.kt b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KtDirectionsPackageDescriptor.kt
index dc802d9..e6ead23 100644
--- a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KtDirectionsPackageDescriptor.kt
+++ b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KtDirectionsPackageDescriptor.kt
@@ -33,7 +33,8 @@
import org.jetbrains.kotlin.utils.alwaysTrue
/**
- * Directions Kt package descriptor, which wraps and indirectly exposes a [LightDirectionsKtClass] class descriptor
+ * Directions Kt package descriptor, which wraps and indirectly exposes a [LightDirectionsKtClass]
+ * class descriptor
*/
class KtDirectionsPackageDescriptor(
private val containingNavFileInfo: SafeArgsNavFileInfo,
@@ -54,24 +55,29 @@
private val safeArgsPackageDescriptor = this@KtDirectionsPackageDescriptor
private inner class SafeArgsModuleScope : MemberScopeImpl() {
- private val classes = storageManager.createLazyValue {
- val directionsClass = LightDirectionsKtClass(
- containingNavFileInfo.navInfo,
- containingNavFileInfo.navEntry,
- className,
- destination,
- sourceElement,
- safeArgsPackageDescriptor,
- storageManager
- )
- listOfNotNull(directionsClass)
- }
+ private val classes =
+ storageManager.createLazyValue {
+ val directionsClass =
+ LightDirectionsKtClass(
+ containingNavFileInfo.navInfo,
+ containingNavFileInfo.navEntry,
+ className,
+ destination,
+ sourceElement,
+ safeArgsPackageDescriptor,
+ storageManager
+ )
+ listOfNotNull(directionsClass)
+ }
override fun getContributedDescriptors(
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
): Collection<DeclarationDescriptor> {
- return classes().filter { kindFilter.acceptsKinds(DescriptorKindFilter.NON_SINGLETON_CLASSIFIERS_MASK) && nameFilter(it.name) }
+ return classes().filter {
+ kindFilter.acceptsKinds(DescriptorKindFilter.NON_SINGLETON_CLASSIFIERS_MASK) &&
+ nameFilter(it.name)
+ }
}
override fun getClassifierNames(): Set<Name> {
@@ -80,7 +86,10 @@
.mapTo(mutableSetOf()) { it.name }
}
- override fun getContributedClassifier(name: Name, location: LookupLocation): ClassifierDescriptor? {
+ override fun getContributedClassifier(
+ name: Name,
+ location: LookupLocation
+ ): ClassifierDescriptor? {
return classes().firstOrNull { it.name == name }
}
@@ -88,4 +97,4 @@
p.println(this::class.java.simpleName)
}
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/LightArgsKtClass.kt b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/LightArgsKtClass.kt
index dd6eae5..9f91b4b 100644
--- a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/LightArgsKtClass.kt
+++ b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/LightArgsKtClass.kt
@@ -16,9 +16,7 @@
package com.android.tools.idea.nav.safeargs.kotlin.k1
import com.android.SdkConstants
-import com.android.ide.common.gradle.Version
import com.android.tools.idea.nav.safeargs.index.NavDestinationData
-import com.android.tools.idea.nav.safeargs.module.NavEntry
import com.android.tools.idea.nav.safeargs.module.NavInfo
import com.android.tools.idea.nav.safeargs.psi.SafeArgsFeatureVersions
import com.android.tools.idea.nav.safeargs.psi.java.toCamelCase
@@ -62,7 +60,6 @@
* An "Arg" represents an argument which can get passed from one destination to another.
*
* For example, if you had the following "nav.xml":
- *
* ```
* <argument
* android:name="message"
@@ -70,7 +67,6 @@
* ```
*
* This would generate a class like the following:
- *
* ```
* data class FirstFragmentArgs( val message: String) : NavArgs {
* fun toBundle(): Bundle
@@ -91,14 +87,25 @@
sourceElement: SourceElement,
containingDescriptor: DeclarationDescriptor,
private val storageManager: StorageManager
-) : ClassDescriptorImpl(containingDescriptor, name, Modality.FINAL, ClassKind.CLASS, superTypes, sourceElement, false, storageManager) {
+) :
+ ClassDescriptorImpl(
+ containingDescriptor,
+ name,
+ Modality.FINAL,
+ ClassKind.CLASS,
+ superTypes,
+ sourceElement,
+ false,
+ storageManager
+ ) {
private val _primaryConstructor = storageManager.createLazyValue { computePrimaryConstructor() }
private val _companionObject = storageManager.createLazyValue { computeCompanionObject() }
private val scope = storageManager.createLazyValue { ArgsClassScope() }
override fun getUnsubstitutedMemberScope(): MemberScope = scope()
- override fun getUnsubstitutedMemberScope(kotlinTypeRefiner: KotlinTypeRefiner): MemberScope = unsubstitutedMemberScope
+ override fun getUnsubstitutedMemberScope(kotlinTypeRefiner: KotlinTypeRefiner): MemberScope =
+ unsubstitutedMemberScope
override fun getConstructors() = listOf(_primaryConstructor())
override fun getUnsubstitutedPrimaryConstructor() = _primaryConstructor()
@@ -106,20 +113,37 @@
private fun computePrimaryConstructor(): ClassConstructorDescriptor {
val valueParametersProvider = { constructor: ClassConstructorDescriptor ->
- val resolvedArguments = if (navInfo.navVersion >= SafeArgsFeatureVersions.ADJUST_PARAMS_WITH_DEFAULTS)
- destination.arguments.sortedBy { it.defaultValue != null }
- else
- destination.arguments
+ val resolvedArguments =
+ if (navInfo.navVersion >= SafeArgsFeatureVersions.ADJUST_PARAMS_WITH_DEFAULTS)
+ destination.arguments.sortedBy { it.defaultValue != null }
+ else destination.arguments
var index = 0
resolvedArguments
.asSequence()
.map { arg ->
val pName = Name.identifier(arg.name.toCamelCase())
- val pType = this.builtIns.getKotlinType(arg.type, arg.defaultValue, containingDeclaration.module, arg.isNonNull())
+ val pType =
+ this.builtIns.getKotlinType(
+ arg.type,
+ arg.defaultValue,
+ containingDeclaration.module,
+ arg.isNonNull()
+ )
val hasDefaultValue = arg.defaultValue != null
- ValueParameterDescriptorImpl(constructor, null, index++, Annotations.EMPTY, pName, pType, hasDefaultValue,
- false, false, null, SourceElement.NO_SOURCE)
+ ValueParameterDescriptorImpl(
+ constructor,
+ null,
+ index++,
+ Annotations.EMPTY,
+ pName,
+ pType,
+ hasDefaultValue,
+ false,
+ false,
+ null,
+ SourceElement.NO_SOURCE
+ )
}
.toList()
}
@@ -128,8 +152,17 @@
private fun computeCompanionObject(): ClassDescriptor {
val argsClassDescriptor = this@LightArgsKtClass
- return object : ClassDescriptorImpl(argsClassDescriptor, Name.identifier("Companion"), Modality.FINAL,
- ClassKind.OBJECT, emptyList(), argsClassDescriptor.source, false, storageManager) {
+ return object :
+ ClassDescriptorImpl(
+ argsClassDescriptor,
+ Name.identifier("Companion"),
+ Modality.FINAL,
+ ClassKind.OBJECT,
+ emptyList(),
+ argsClassDescriptor.source,
+ false,
+ storageManager
+ ) {
private val companionObjectScope = storageManager.createLazyValue { CompanionScope() }
private val companionObject = this
@@ -137,54 +170,96 @@
override fun getUnsubstitutedPrimaryConstructor(): ClassConstructorDescriptor? = null
override fun getConstructors(): Collection<ClassConstructorDescriptor> = emptyList()
override fun getUnsubstitutedMemberScope() = companionObjectScope()
- override fun getUnsubstitutedMemberScope(kotlinTypeRefiner: KotlinTypeRefiner): MemberScope = unsubstitutedMemberScope
+ override fun getUnsubstitutedMemberScope(kotlinTypeRefiner: KotlinTypeRefiner): MemberScope =
+ unsubstitutedMemberScope
private inner class CompanionScope : MemberScopeImpl() {
- private val companionMethods = storageManager.createLazyValue {
- val methods = mutableListOf<SimpleFunctionDescriptor>()
+ private val companionMethods =
+ storageManager.createLazyValue {
+ val methods = mutableListOf<SimpleFunctionDescriptor>()
- val fromBundleParametersProvider = { method: SimpleFunctionDescriptorImpl ->
- val bundleType = argsClassDescriptor.builtIns.getKotlinType("android.os.Bundle", null, argsClassDescriptor.module)
- val bundleParam = ValueParameterDescriptorImpl(
- method, null, 0, Annotations.EMPTY, Name.identifier("bundle"), bundleType,
- false, false, false, null, SourceElement.NO_SOURCE
- )
- listOf(bundleParam)
- }
-
- methods.add(companionObject.createMethod(
- name = "fromBundle",
- returnType = argsClassDescriptor.getDefaultType(),
- valueParametersProvider = fromBundleParametersProvider
- ))
-
- if (navInfo.navVersion >= SafeArgsFeatureVersions.FROM_SAVED_STATE_HANDLE) {
- val fromSavedStateHandleParametersProvider = { method: SimpleFunctionDescriptorImpl ->
- val handleType =
- argsClassDescriptor.builtIns.getKotlinType("androidx.lifecycle.SavedStateHandle", null, argsClassDescriptor.module)
- val handleParam = ValueParameterDescriptorImpl(
- method, null, 0, Annotations.EMPTY, Name.identifier("savedStateHandle"), handleType,
- false, false, false, null, SourceElement.NO_SOURCE
- )
- listOf(handleParam)
+ val fromBundleParametersProvider = { method: SimpleFunctionDescriptorImpl ->
+ val bundleType =
+ argsClassDescriptor.builtIns.getKotlinType(
+ "android.os.Bundle",
+ null,
+ argsClassDescriptor.module
+ )
+ val bundleParam =
+ ValueParameterDescriptorImpl(
+ method,
+ null,
+ 0,
+ Annotations.EMPTY,
+ Name.identifier("bundle"),
+ bundleType,
+ false,
+ false,
+ false,
+ null,
+ SourceElement.NO_SOURCE
+ )
+ listOf(bundleParam)
}
- methods.add(companionObject.createMethod(
- name = "fromSavedStateHandle",
- returnType = argsClassDescriptor.getDefaultType(),
- valueParametersProvider = fromSavedStateHandleParametersProvider
- ))
+ methods.add(
+ companionObject.createMethod(
+ name = "fromBundle",
+ returnType = argsClassDescriptor.getDefaultType(),
+ valueParametersProvider = fromBundleParametersProvider
+ )
+ )
+
+ if (navInfo.navVersion >= SafeArgsFeatureVersions.FROM_SAVED_STATE_HANDLE) {
+ val fromSavedStateHandleParametersProvider = { method: SimpleFunctionDescriptorImpl ->
+ val handleType =
+ argsClassDescriptor.builtIns.getKotlinType(
+ "androidx.lifecycle.SavedStateHandle",
+ null,
+ argsClassDescriptor.module
+ )
+ val handleParam =
+ ValueParameterDescriptorImpl(
+ method,
+ null,
+ 0,
+ Annotations.EMPTY,
+ Name.identifier("savedStateHandle"),
+ handleType,
+ false,
+ false,
+ false,
+ null,
+ SourceElement.NO_SOURCE
+ )
+ listOf(handleParam)
+ }
+
+ methods.add(
+ companionObject.createMethod(
+ name = "fromSavedStateHandle",
+ returnType = argsClassDescriptor.getDefaultType(),
+ valueParametersProvider = fromSavedStateHandleParametersProvider
+ )
+ )
+ }
+
+ methods
}
- methods
+ override fun getContributedDescriptors(
+ kindFilter: DescriptorKindFilter,
+ nameFilter: (Name) -> Boolean
+ ): Collection<DeclarationDescriptor> {
+ return companionMethods().filter {
+ kindFilter.acceptsKinds(DescriptorKindFilter.FUNCTIONS_MASK) && nameFilter(it.name)
+ }
}
- override fun getContributedDescriptors(kindFilter: DescriptorKindFilter,
- nameFilter: (Name) -> Boolean): Collection<DeclarationDescriptor> {
- return companionMethods().filter { kindFilter.acceptsKinds(DescriptorKindFilter.FUNCTIONS_MASK) && nameFilter(it.name) }
- }
-
- override fun getContributedFunctions(name: Name, location: LookupLocation): Collection<SimpleFunctionDescriptor> {
+ override fun getContributedFunctions(
+ name: Name,
+ location: LookupLocation
+ ): Collection<SimpleFunctionDescriptor> {
return companionMethods().filter { it.name == name }
}
@@ -197,98 +272,123 @@
private inner class ArgsClassScope : MemberScopeImpl() {
private val argsClassDescriptor = this@LightArgsKtClass
- private val methods = storageManager.createLazyValue {
- val methods = mutableListOf<SimpleFunctionDescriptor>()
+ private val methods =
+ storageManager.createLazyValue {
+ val methods = mutableListOf<SimpleFunctionDescriptor>()
- val bundleType = argsClassDescriptor.builtIns.getKotlinType("android.os.Bundle", null, argsClassDescriptor.module)
- val savedStateHandleType = argsClassDescriptor.builtIns.getKotlinType(
- "androidx.lifecycle.SavedStateHandle",
- null,
- argsClassDescriptor.module
- )
-
- // Add toBundle method.
- methods.add(
- argsClassDescriptor.createMethod(
- name = "toBundle",
- returnType = bundleType,
- )
- )
-
- // Add copy method.
- methods.add(
- argsClassDescriptor.createMethod(
- name = "copy",
- returnType = argsClassDescriptor.getDefaultType(),
- valueParametersProvider = { argsClassDescriptor.unsubstitutedPrimaryConstructor.valueParameters }
- )
- )
-
- // Add component functions.
- var index = 1
- destination.arguments
- .asSequence()
- .map { arg ->
- val methodName = "component" + index++
- val returnType = argsClassDescriptor.builtIns
- .getKotlinType(arg.type, arg.defaultValue, argsClassDescriptor.module, arg.isNonNull())
- val xmlTag = argsClassDescriptor.source.getPsi() as? XmlTag
- val resolvedSourceElement = xmlTag?.findChildTagElementByNameAttr(SdkConstants.TAG_ARGUMENT, arg.name)?.let {
- XmlSourceElement(
- SafeArgsXmlTag(
- it as XmlTagImpl,
- IconManager.getInstance().getPlatformIcon(PlatformIcons.Function),
- methodName,
- argsClassDescriptor.fqNameSafe.asString()
- )
- )
- } ?: argsClassDescriptor.source
-
- argsClassDescriptor.createMethod(
- name = methodName,
- returnType = returnType,
- isOperator = true,
- sourceElement = resolvedSourceElement
+ val bundleType =
+ argsClassDescriptor.builtIns.getKotlinType(
+ "android.os.Bundle",
+ null,
+ argsClassDescriptor.module
)
- }
- .map { methods.add(it) }
- .toList()
+ val savedStateHandleType =
+ argsClassDescriptor.builtIns.getKotlinType(
+ "androidx.lifecycle.SavedStateHandle",
+ null,
+ argsClassDescriptor.module
+ )
- // Add on version specific methods since the navigation library side is keeping introducing new methods.
- if (navInfo.navVersion >= SafeArgsFeatureVersions.TO_SAVED_STATE_HANDLE) {
+ // Add toBundle method.
methods.add(
argsClassDescriptor.createMethod(
- name = "toSavedStateHandle",
- returnType = savedStateHandleType
+ name = "toBundle",
+ returnType = bundleType,
)
)
+
+ // Add copy method.
+ methods.add(
+ argsClassDescriptor.createMethod(
+ name = "copy",
+ returnType = argsClassDescriptor.getDefaultType(),
+ valueParametersProvider = {
+ argsClassDescriptor.unsubstitutedPrimaryConstructor.valueParameters
+ }
+ )
+ )
+
+ // Add component functions.
+ var index = 1
+ destination.arguments
+ .asSequence()
+ .map { arg ->
+ val methodName = "component" + index++
+ val returnType =
+ argsClassDescriptor.builtIns.getKotlinType(
+ arg.type,
+ arg.defaultValue,
+ argsClassDescriptor.module,
+ arg.isNonNull()
+ )
+ val xmlTag = argsClassDescriptor.source.getPsi() as? XmlTag
+ val resolvedSourceElement =
+ xmlTag?.findChildTagElementByNameAttr(SdkConstants.TAG_ARGUMENT, arg.name)?.let {
+ XmlSourceElement(
+ SafeArgsXmlTag(
+ it as XmlTagImpl,
+ IconManager.getInstance().getPlatformIcon(PlatformIcons.Function),
+ methodName,
+ argsClassDescriptor.fqNameSafe.asString()
+ )
+ )
+ }
+ ?: argsClassDescriptor.source
+
+ argsClassDescriptor.createMethod(
+ name = methodName,
+ returnType = returnType,
+ isOperator = true,
+ sourceElement = resolvedSourceElement
+ )
+ }
+ .map { methods.add(it) }
+ .toList()
+
+ // Add on version specific methods since the navigation library side is keeping introducing
+ // new methods.
+ if (navInfo.navVersion >= SafeArgsFeatureVersions.TO_SAVED_STATE_HANDLE) {
+ methods.add(
+ argsClassDescriptor.createMethod(
+ name = "toSavedStateHandle",
+ returnType = savedStateHandleType
+ )
+ )
+ }
+
+ methods
}
- methods
- }
-
- private val properties = storageManager.createLazyValue {
- destination.arguments
- .asSequence()
- .map { arg ->
- val pName = arg.name.toCamelCase()
- val pType = argsClassDescriptor.builtIns
- .getKotlinType(arg.type, arg.defaultValue, argsClassDescriptor.module, arg.isNonNull())
- val xmlTag = argsClassDescriptor.source.getPsi() as? XmlTag
- val resolvedSourceElement = xmlTag?.findChildTagElementByNameAttr(SdkConstants.TAG_ARGUMENT, arg.name)?.let {
- XmlSourceElement(
- SafeArgsXmlTag(
- it as XmlTagImpl,
- KotlinIcons.FIELD_VAL,
- arg.name,
- argsClassDescriptor.fqNameSafe.asString()
+ private val properties =
+ storageManager.createLazyValue {
+ destination.arguments
+ .asSequence()
+ .map { arg ->
+ val pName = arg.name.toCamelCase()
+ val pType =
+ argsClassDescriptor.builtIns.getKotlinType(
+ arg.type,
+ arg.defaultValue,
+ argsClassDescriptor.module,
+ arg.isNonNull()
)
- )
- } ?: argsClassDescriptor.source
- argsClassDescriptor.createProperty(pName, pType, resolvedSourceElement)
- }
- .toList()
- }
+ val xmlTag = argsClassDescriptor.source.getPsi() as? XmlTag
+ val resolvedSourceElement =
+ xmlTag?.findChildTagElementByNameAttr(SdkConstants.TAG_ARGUMENT, arg.name)?.let {
+ XmlSourceElement(
+ SafeArgsXmlTag(
+ it as XmlTagImpl,
+ KotlinIcons.FIELD_VAL,
+ arg.name,
+ argsClassDescriptor.fqNameSafe.asString()
+ )
+ )
+ }
+ ?: argsClassDescriptor.source
+ argsClassDescriptor.createProperty(pName, pType, resolvedSourceElement)
+ }
+ .toList()
+ }
private val classifiers = storageManager.createLazyValue { listOf(companionObjectDescriptor) }
@@ -296,20 +396,36 @@
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
): Collection<DeclarationDescriptor> {
- return methods().filter { kindFilter.acceptsKinds(DescriptorKindFilter.FUNCTIONS_MASK) && nameFilter(it.name) } +
- properties().filter { kindFilter.acceptsKinds(DescriptorKindFilter.VARIABLES_MASK) && nameFilter(it.name) } +
- classifiers().filter { kindFilter.acceptsKinds(DescriptorKindFilter.SINGLETON_CLASSIFIERS_MASK) && nameFilter(it.name) }
+ return methods().filter {
+ kindFilter.acceptsKinds(DescriptorKindFilter.FUNCTIONS_MASK) && nameFilter(it.name)
+ } +
+ properties().filter {
+ kindFilter.acceptsKinds(DescriptorKindFilter.VARIABLES_MASK) && nameFilter(it.name)
+ } +
+ classifiers().filter {
+ kindFilter.acceptsKinds(DescriptorKindFilter.SINGLETON_CLASSIFIERS_MASK) &&
+ nameFilter(it.name)
+ }
}
- override fun getContributedClassifier(name: Name, location: LookupLocation): ClassifierDescriptor? {
+ override fun getContributedClassifier(
+ name: Name,
+ location: LookupLocation
+ ): ClassifierDescriptor? {
return classifiers().firstOrNull { it.name == name }
}
- override fun getContributedFunctions(name: Name, location: LookupLocation): Collection<SimpleFunctionDescriptor> {
+ override fun getContributedFunctions(
+ name: Name,
+ location: LookupLocation
+ ): Collection<SimpleFunctionDescriptor> {
return methods().filter { it.name == name }
}
- override fun getContributedVariables(name: Name, location: LookupLocation): List<PropertyDescriptor> {
+ override fun getContributedVariables(
+ name: Name,
+ location: LookupLocation
+ ): List<PropertyDescriptor> {
return properties().filter { it.name == name }
}
diff --git a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/LightDirectionsKtClass.kt b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/LightDirectionsKtClass.kt
index 6827ce6..05e2555 100644
--- a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/LightDirectionsKtClass.kt
+++ b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/LightDirectionsKtClass.kt
@@ -60,7 +60,6 @@
* A "Direction" represents functionality that takes you away from one destination to another.
*
* For example, if you had the following "nav.xml":
- *
* ```
* <navigation>
* <fragment id="@+id/mainMenu">
@@ -79,7 +78,6 @@
* ```
*
* This would generate a class like the following:
- *
* ```
* class MainMenuDirections {
* companion object {
@@ -103,14 +101,26 @@
sourceElement: SourceElement,
containingDescriptor: DeclarationDescriptor,
private val storageManager: StorageManager
-) : ClassDescriptorImpl(containingDescriptor, name, Modality.FINAL, ClassKind.CLASS, emptyList(), sourceElement, false, storageManager) {
+) :
+ ClassDescriptorImpl(
+ containingDescriptor,
+ name,
+ Modality.FINAL,
+ ClassKind.CLASS,
+ emptyList(),
+ sourceElement,
+ false,
+ storageManager
+ ) {
- private val LOG get() = Logger.getInstance(LightDirectionsKtClass::class.java)
+ private val LOG
+ get() = Logger.getInstance(LightDirectionsKtClass::class.java)
private val _companionObject = storageManager.createLazyValue { computeCompanionObject() }
private val scope = storageManager.createLazyValue { DirectionsClassScope() }
override fun getUnsubstitutedMemberScope(): MemberScope = scope()
- override fun getUnsubstitutedMemberScope(kotlinTypeRefiner: KotlinTypeRefiner): MemberScope = unsubstitutedMemberScope
+ override fun getUnsubstitutedMemberScope(kotlinTypeRefiner: KotlinTypeRefiner): MemberScope =
+ unsubstitutedMemberScope
override fun getConstructors(): Collection<ClassConstructorDescriptor> = emptyList()
override fun getUnsubstitutedPrimaryConstructor(): ClassConstructorDescriptor? = null
@@ -118,82 +128,115 @@
private fun computeCompanionObject(): ClassDescriptor {
val directionsClassDescriptor = this@LightDirectionsKtClass
- return object : ClassDescriptorImpl(
- directionsClassDescriptor,
- Name.identifier("Companion"),
- Modality.FINAL,
- ClassKind.OBJECT,
- emptyList(),
- directionsClassDescriptor.source, false, storageManager
- ) {
+ return object :
+ ClassDescriptorImpl(
+ directionsClassDescriptor,
+ Name.identifier("Companion"),
+ Modality.FINAL,
+ ClassKind.OBJECT,
+ emptyList(),
+ directionsClassDescriptor.source,
+ false,
+ storageManager
+ ) {
private val companionScope = storageManager.createLazyValue { CompanionObjectScope() }
private val companionObject = this
override fun isCompanionObject() = true
override fun getUnsubstitutedPrimaryConstructor(): ClassConstructorDescriptor? = null
override fun getConstructors(): Collection<ClassConstructorDescriptor> = emptyList()
override fun getUnsubstitutedMemberScope() = companionScope()
- override fun getUnsubstitutedMemberScope(kotlinTypeRefiner: KotlinTypeRefiner): MemberScope = unsubstitutedMemberScope
+ override fun getUnsubstitutedMemberScope(kotlinTypeRefiner: KotlinTypeRefiner): MemberScope =
+ unsubstitutedMemberScope
private inner class CompanionObjectScope : MemberScopeImpl() {
- private val companionMethods = storageManager.createLazyValue {
- // action methods
- val navDirectionType = directionsClassDescriptor.builtIns.getKotlinType("androidx.navigation.NavDirections", null,
- directionsClassDescriptor.module)
- destination.getActionsWithResolvedArguments(
- navEntry.data,
- navInfo.packageName,
- adjustArgumentsWithDefaults = (navInfo.navVersion >= SafeArgsFeatureVersions.ADJUST_PARAMS_WITH_DEFAULTS))
- .asSequence()
- .mapNotNull { action ->
- val valueParametersProvider = { method: SimpleFunctionDescriptorImpl ->
- var index = 0
- action.arguments
- .asSequence()
- .map { arg ->
- val pName = Name.identifier(arg.name.toCamelCase())
- val pType = directionsClassDescriptor.builtIns
- .getKotlinType(arg.type, arg.defaultValue, directionsClassDescriptor.module, arg.isNonNull())
- val hasDefaultValue = arg.defaultValue != null
- ValueParameterDescriptorImpl(method, null, index++, Annotations.EMPTY, pName, pType,
- hasDefaultValue, false, false, null,
- SourceElement.NO_SOURCE)
- }
- .toList()
- }
-
- val methodName = action.id.toCamelCase()
- val resolvedSourceElement = (directionsClassDescriptor.source.getPsi() as? XmlTag)
- ?.findFirstMatchingElementByTraversingUp(SdkConstants.TAG_ACTION, action.id)
- ?.let {
- XmlSourceElement(
- SafeArgsXmlTag(
- it as XmlTagImpl,
- IconManager.getInstance().getPlatformIcon(PlatformIcons.Function),
- methodName,
- companionObject.fqNameSafe.asString()
- )
- )
- }
- ?: directionsClassDescriptor.source
-
- companionObject.createMethod(
- name = methodName,
- returnType = navDirectionType,
- valueParametersProvider = valueParametersProvider,
- sourceElement = resolvedSourceElement
+ private val companionMethods =
+ storageManager.createLazyValue {
+ // action methods
+ val navDirectionType =
+ directionsClassDescriptor.builtIns.getKotlinType(
+ "androidx.navigation.NavDirections",
+ null,
+ directionsClassDescriptor.module
)
- }
- .toList()
- }
+ destination
+ .getActionsWithResolvedArguments(
+ navEntry.data,
+ navInfo.packageName,
+ adjustArgumentsWithDefaults =
+ (navInfo.navVersion >= SafeArgsFeatureVersions.ADJUST_PARAMS_WITH_DEFAULTS)
+ )
+ .asSequence()
+ .mapNotNull { action ->
+ val valueParametersProvider = { method: SimpleFunctionDescriptorImpl ->
+ var index = 0
+ action.arguments
+ .asSequence()
+ .map { arg ->
+ val pName = Name.identifier(arg.name.toCamelCase())
+ val pType =
+ directionsClassDescriptor.builtIns.getKotlinType(
+ arg.type,
+ arg.defaultValue,
+ directionsClassDescriptor.module,
+ arg.isNonNull()
+ )
+ val hasDefaultValue = arg.defaultValue != null
+ ValueParameterDescriptorImpl(
+ method,
+ null,
+ index++,
+ Annotations.EMPTY,
+ pName,
+ pType,
+ hasDefaultValue,
+ false,
+ false,
+ null,
+ SourceElement.NO_SOURCE
+ )
+ }
+ .toList()
+ }
+
+ val methodName = action.id.toCamelCase()
+ val resolvedSourceElement =
+ (directionsClassDescriptor.source.getPsi() as? XmlTag)
+ ?.findFirstMatchingElementByTraversingUp(SdkConstants.TAG_ACTION, action.id)
+ ?.let {
+ XmlSourceElement(
+ SafeArgsXmlTag(
+ it as XmlTagImpl,
+ IconManager.getInstance().getPlatformIcon(PlatformIcons.Function),
+ methodName,
+ companionObject.fqNameSafe.asString()
+ )
+ )
+ }
+ ?: directionsClassDescriptor.source
+
+ companionObject.createMethod(
+ name = methodName,
+ returnType = navDirectionType,
+ valueParametersProvider = valueParametersProvider,
+ sourceElement = resolvedSourceElement
+ )
+ }
+ .toList()
+ }
override fun getContributedDescriptors(
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
): Collection<DeclarationDescriptor> {
- return companionMethods().filter { kindFilter.acceptsKinds(DescriptorKindFilter.FUNCTIONS_MASK) && nameFilter(it.name) }
+ return companionMethods().filter {
+ kindFilter.acceptsKinds(DescriptorKindFilter.FUNCTIONS_MASK) && nameFilter(it.name)
+ }
}
- override fun getContributedFunctions(name: Name, location: LookupLocation): Collection<SimpleFunctionDescriptor> {
+ override fun getContributedFunctions(
+ name: Name,
+ location: LookupLocation
+ ): Collection<SimpleFunctionDescriptor> {
return companionMethods().filter { it.name == name }
}
@@ -211,10 +254,16 @@
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
): Collection<DeclarationDescriptor> {
- return classifiers().filter { kindFilter.acceptsKinds(DescriptorKindFilter.SINGLETON_CLASSIFIERS_MASK) && nameFilter(it.name) }
+ return classifiers().filter {
+ kindFilter.acceptsKinds(DescriptorKindFilter.SINGLETON_CLASSIFIERS_MASK) &&
+ nameFilter(it.name)
+ }
}
- override fun getContributedClassifier(name: Name, location: LookupLocation): ClassifierDescriptor? {
+ override fun getContributedClassifier(
+ name: Name,
+ location: LookupLocation
+ ): ClassifierDescriptor? {
return classifiers().firstOrNull { it.name == name }
}
@@ -222,4 +271,4 @@
p.println(this::class.java.simpleName)
}
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgSyntheticDescriptorGenerator.kt b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgSyntheticDescriptorGenerator.kt
index 339b105..33ee58c 100644
--- a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgSyntheticDescriptorGenerator.kt
+++ b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgSyntheticDescriptorGenerator.kt
@@ -33,41 +33,58 @@
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.KotlinType
-/**
- * Class, method and property descriptors generator
- */
+/** Class, method and property descriptors generator */
internal fun ClassDescriptorImpl.createMethod(
name: String,
returnType: KotlinType,
isOperator: Boolean = false,
- valueParametersProvider: (SimpleFunctionDescriptorImpl) -> List<ValueParameterDescriptor> = { emptyList() },
+ valueParametersProvider: (SimpleFunctionDescriptorImpl) -> List<ValueParameterDescriptor> = {
+ emptyList()
+ },
sourceElement: SourceElement = this.source.withFunctionIcon(name, this.fqNameSafe.asString())
): SimpleFunctionDescriptorImpl {
- val method = object : SimpleFunctionDescriptorImpl(
- this,
- null,
- Annotations.EMPTY,
- Name.identifier(name),
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- sourceElement
- ) {
- override fun isOperator(): Boolean {
- return isOperator
+ val method =
+ object :
+ SimpleFunctionDescriptorImpl(
+ this,
+ null,
+ Annotations.EMPTY,
+ Name.identifier(name),
+ CallableMemberDescriptor.Kind.SYNTHESIZED,
+ sourceElement
+ ) {
+ override fun isOperator(): Boolean {
+ return isOperator
+ }
}
- }
- return method.initialize(null, this.thisAsReceiverParameter, emptyList(),
- valueParametersProvider(method), returnType, Modality.FINAL, DescriptorVisibilities.PUBLIC)
+ return method.initialize(
+ null,
+ this.thisAsReceiverParameter,
+ emptyList(),
+ valueParametersProvider(method),
+ returnType,
+ Modality.FINAL,
+ DescriptorVisibilities.PUBLIC
+ )
}
internal fun ClassDescriptorImpl.createConstructor(
- valueParameterProvider: (ClassConstructorDescriptor) -> List<ValueParameterDescriptor> = { emptyList() }
-): ClassConstructorDescriptor {
- return ClassConstructorDescriptorImpl.createSynthesized(this, Annotations.EMPTY, true, this.source).apply {
- this.initialize(valueParameterProvider(this), DescriptorVisibilities.PUBLIC)
- this.returnType = this@createConstructor.defaultType
+ valueParameterProvider: (ClassConstructorDescriptor) -> List<ValueParameterDescriptor> = {
+ emptyList()
}
+): ClassConstructorDescriptor {
+ return ClassConstructorDescriptorImpl.createSynthesized(
+ this,
+ Annotations.EMPTY,
+ true,
+ this.source
+ )
+ .apply {
+ this.initialize(valueParameterProvider(this), DescriptorVisibilities.PUBLIC)
+ this.returnType = this@createConstructor.defaultType
+ }
}
internal fun ClassDescriptorImpl.createProperty(
@@ -75,39 +92,43 @@
type: KotlinType,
sourceElement: SourceElement = SourceElement.NO_SOURCE
): PropertyDescriptor {
- val property = object : PropertyDescriptorImpl(
- this,
- null,
- Annotations.EMPTY,
- Modality.FINAL,
- DescriptorVisibilities.PUBLIC,
- false,
- Name.identifier(name),
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- sourceElement, // todo: refined navigation element
- false,
- false,
- false,
- false,
- false,
- false) {}
+ val property =
+ object :
+ PropertyDescriptorImpl(
+ this,
+ null,
+ Annotations.EMPTY,
+ Modality.FINAL,
+ DescriptorVisibilities.PUBLIC,
+ false,
+ Name.identifier(name),
+ CallableMemberDescriptor.Kind.SYNTHESIZED,
+ sourceElement, // todo: refined navigation element
+ false,
+ false,
+ false,
+ false,
+ false,
+ false
+ ) {}
property.setType(type, emptyList<TypeParameterDescriptor>(), this.thisAsReceiverParameter, null)
- val getter = PropertyGetterDescriptorImpl(
- property,
- Annotations.EMPTY,
- Modality.FINAL,
- DescriptorVisibilities.PUBLIC,
- false,
- false,
- false,
- CallableMemberDescriptor.Kind.SYNTHESIZED,
- null,
- sourceElement
- )
+ val getter =
+ PropertyGetterDescriptorImpl(
+ property,
+ Annotations.EMPTY,
+ Modality.FINAL,
+ DescriptorVisibilities.PUBLIC,
+ false,
+ false,
+ false,
+ CallableMemberDescriptor.Kind.SYNTHESIZED,
+ null,
+ sourceElement
+ )
getter.initialize(type)
property.initialize(getter, null)
return property
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsImportKtResolver.kt b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsImportKtResolver.kt
index 24a9319..3b57d70 100644
--- a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsImportKtResolver.kt
+++ b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsImportKtResolver.kt
@@ -31,6 +31,9 @@
import com.intellij.psi.PsiFile
import com.intellij.ui.popup.list.ListPopupImpl
import com.intellij.ui.popup.list.PopupListElementRenderer
+import java.awt.BorderLayout
+import javax.swing.JPanel
+import javax.swing.ListCellRenderer
import org.jetbrains.android.util.AndroidBundle
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
@@ -54,12 +57,10 @@
import org.jetbrains.kotlin.resolve.asImportedFromObject
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
-import java.awt.BorderLayout
-import javax.swing.JPanel
-import javax.swing.ListCellRenderer
/**
- * Registers an unresolved reference resolver in Kotlin files which recognizes classes from Safe Args kotlin classes
+ * Registers an unresolved reference resolver in Kotlin files which recognizes classes from Safe
+ * Args kotlin classes
*/
class SafeArgsImportKtResolver : QuickFixContributor {
override fun registerQuickFixes(quickFixes: QuickFixes) {
@@ -112,38 +113,47 @@
// Copied from KotlinAddImportAction
object : ListPopupImpl(project, getVariantSelectionPopup(project, file, suggestions)) {
- override fun getListElementRenderer(): ListCellRenderer<AutoImportVariant> {
- val baseRenderer = super.getListElementRenderer() as PopupListElementRenderer<AutoImportVariant>
- val psiRenderer = SafeArgsPsiElementCellRenderer()
- return ListCellRenderer { list, value, index, isSelected, cellHasFocus ->
- JPanel(BorderLayout()).apply {
- baseRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus)
- add(baseRenderer.nextStepLabel, BorderLayout.EAST)
- add(
- psiRenderer.getListCellRendererComponent(
+ override fun getListElementRenderer(): ListCellRenderer<AutoImportVariant> {
+ val baseRenderer =
+ super.getListElementRenderer() as PopupListElementRenderer<AutoImportVariant>
+ val psiRenderer = SafeArgsPsiElementCellRenderer()
+ return ListCellRenderer { list, value, index, isSelected, cellHasFocus ->
+ JPanel(BorderLayout()).apply {
+ baseRenderer.getListCellRendererComponent(
list,
- value.declarationToImport(project),
+ value,
index,
isSelected,
cellHasFocus
)
- )
+ add(baseRenderer.nextStepLabel, BorderLayout.EAST)
+ add(
+ psiRenderer.getListCellRendererComponent(
+ list,
+ value.declarationToImport(project),
+ index,
+ isSelected,
+ cellHasFocus
+ )
+ )
+ }
}
}
}
- }.showInBestPositionFor(editor)
+ .showInBestPositionFor(editor)
}
private fun collectSuggestions(file: PsiFile): List<AutoImportVariant> {
val module = ModuleUtil.findModuleForFile(file) ?: return emptyList()
val nameIdentifier = Name.identifier(referenceName)
- return module.getDescriptorsByModulesWithDependencies()
+ return module
+ .getDescriptorsByModulesWithDependencies()
.values
.flatten()
.asSequence()
.flatMap { descriptor ->
descriptor.findVisibleClassesBySimpleName(nameIdentifier) +
- descriptor.findVisibleFunctionsBySimpleName(nameIdentifier)
+ descriptor.findVisibleFunctionsBySimpleName(nameIdentifier)
}
.map { AutoImportVariant(it) }
.filter { it.importFqName != null }
@@ -151,21 +161,33 @@
.toList()
}
- private fun PackageFragmentDescriptor.findVisibleClassesBySimpleName(name: Name): Sequence<DeclarationDescriptor> {
- return getMemberScope().getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS) { it == name }.asSequence() +
- getMemberScope().getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS).asSequence()
- .filterIsInstance<ClassDescriptor>()
- .mapNotNull { it.companionObjectDescriptor }
- .filter { it.name == name }
+ private fun PackageFragmentDescriptor.findVisibleClassesBySimpleName(
+ name: Name
+ ): Sequence<DeclarationDescriptor> {
+ return getMemberScope()
+ .getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS) { it == name }
+ .asSequence() +
+ getMemberScope()
+ .getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS)
+ .asSequence()
+ .filterIsInstance<ClassDescriptor>()
+ .mapNotNull { it.companionObjectDescriptor }
+ .filter { it.name == name }
}
- private fun PackageFragmentDescriptor.findVisibleFunctionsBySimpleName(name: Name): Sequence<DeclarationDescriptor> {
- return getMemberScope().getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS).asSequence()
+ private fun PackageFragmentDescriptor.findVisibleFunctionsBySimpleName(
+ name: Name
+ ): Sequence<DeclarationDescriptor> {
+ return getMemberScope()
+ .getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS)
+ .asSequence()
.filterIsInstance<ClassDescriptor>()
.mapNotNull { it.companionObjectDescriptor }
.flatMap {
- ProgressManager.checkCanceled();
- it.unsubstitutedMemberScope.getContributedDescriptors(DescriptorKindFilter.FUNCTIONS).asSequence()
+ ProgressManager.checkCanceled()
+ it.unsubstitutedMemberScope
+ .getContributedDescriptors(DescriptorKindFilter.FUNCTIONS)
+ .asSequence()
}
.filterIsInstance<FunctionDescriptor>()
.map { it.asImportedFromObject() }
@@ -174,7 +196,8 @@
private fun addImport(project: Project, file: KtFile, import: FqName) {
project.executeWriteCommand(QuickFixBundle.message("add.import")) {
- val descriptor = file.resolveImportReference(import).firstOrNull() ?: return@executeWriteCommand
+ val descriptor =
+ file.resolveImportReference(import).firstOrNull() ?: return@executeWriteCommand
ImportInsertHelper.getInstance(project).importDescriptor(file, descriptor)
}
}
@@ -184,12 +207,20 @@
file: KtFile,
suggestions: List<AutoImportVariant>
): BaseListPopupStep<AutoImportVariant> {
- return object : BaseListPopupStep<AutoImportVariant>(KotlinBundle.message("action.add.import.chooser.title"), suggestions) {
+ return object :
+ BaseListPopupStep<AutoImportVariant>(
+ KotlinBundle.message("action.add.import.chooser.title"),
+ suggestions
+ ) {
override fun isAutoSelectionEnabled() = false
override fun isSpeedSearchEnabled() = true
- override fun onChosen(selectedValue: AutoImportVariant?, finalChoice: Boolean): PopupStep<String>? {
- if (selectedValue == null || project.isDisposed || selectedValue.importFqName == null) return null
+ override fun onChosen(
+ selectedValue: AutoImportVariant?,
+ finalChoice: Boolean
+ ): PopupStep<String>? {
+ if (selectedValue == null || project.isDisposed || selectedValue.importFqName == null)
+ return null
if (finalChoice) {
addImport(project, file, selectedValue.importFqName)
@@ -210,4 +241,4 @@
return if (addOnText.isEmpty()) baseText else "(in $addOnText) $baseText"
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKtCompletionContributor.kt b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKtCompletionContributor.kt
index db11d4b..70c5ea9 100644
--- a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKtCompletionContributor.kt
+++ b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKtCompletionContributor.kt
@@ -44,70 +44,96 @@
import org.jetbrains.kotlin.resolve.source.getPsi
/**
- * This provides completions for generated [LightArgsKtClass] and [LightDirectionsKtClass] from modules with dependencies.
+ * This provides completions for generated [LightArgsKtClass] and [LightDirectionsKtClass] from
+ * modules with dependencies.
*
* This comes after [KotlinCompletionContributor]
*/
class SafeArgsKtCompletionContributor : CompletionContributor() {
init {
- extend(CompletionType.BASIC, PlatformPatterns.psiElement(), object : CompletionProvider<CompletionParameters>() {
- override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
+ extend(
+ CompletionType.BASIC,
+ PlatformPatterns.psiElement(),
+ object : CompletionProvider<CompletionParameters>() {
+ override fun addCompletions(
+ parameters: CompletionParameters,
+ context: ProcessingContext,
+ result: CompletionResultSet
+ ) {
- val position = parameters.position
- val facet = position.androidFacet ?: return
+ val position = parameters.position
+ val facet = position.androidFacet ?: return
- val element = position.parent as? KtSimpleNameExpression ?: return
- if (element.isImportDirectiveExpression()) return
- if (element.getReceiverExpression() != null) return
+ val element = position.parent as? KtSimpleNameExpression ?: return
+ if (element.isImportDirectiveExpression()) return
+ if (element.getReceiverExpression() != null) return
- val importedDirectives = getImportedDirectives(element)
+ val importedDirectives = getImportedDirectives(element)
- val lookupElements = facet.module.getDescriptorsByModulesWithDependencies().values.asSequence()
- .flatten()
- .map { ProgressManager.checkCanceled(); it }
- .filter { element.containingKtFile.packageFqName != it.fqName }
- .mapNotNull { it.getMemberScope().getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS) { true }.firstOrNull() }
- .filterIsInstance<ClassDescriptor>()
- .filter { it.importableFqName != null }
- .filter { descriptor ->
- // Classes in imported packages are already autocompleted, and we don't want to add duplicate results.
- importedDirectives.none { importPath -> descriptor.importableFqName!!.isImported(importPath) }
- }
- .mapNotNull { createLookUpElement(it) }
- .toList()
+ val lookupElements =
+ facet.module
+ .getDescriptorsByModulesWithDependencies()
+ .values
+ .asSequence()
+ .flatten()
+ .map {
+ ProgressManager.checkCanceled()
+ it
+ }
+ .filter { element.containingKtFile.packageFqName != it.fqName }
+ .mapNotNull {
+ it
+ .getMemberScope()
+ .getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS) { true }
+ .firstOrNull()
+ }
+ .filterIsInstance<ClassDescriptor>()
+ .filter { it.importableFqName != null }
+ .filter { descriptor ->
+ // Classes in imported packages are already autocompleted, and we don't want to add
+ // duplicate results.
+ importedDirectives.none { importPath ->
+ descriptor.importableFqName!!.isImported(importPath)
+ }
+ }
+ .mapNotNull { createLookUpElement(it) }
+ .toList()
- result.addAllElements(lookupElements)
- }
-
- private fun createLookUpElement(classDescriptor: ClassDescriptor): LookupElement? {
- val lookupObject = object : DeclarationLookupObjectImpl(classDescriptor) {
- override val psiElement = classDescriptor.source.getPsi()
- override fun getIcon(flags: Int) = KotlinDescriptorIconProvider.getIcon(classDescriptor, psiElement, flags)
+ result.addAllElements(lookupElements)
}
- var element = LookupElementBuilder.create(lookupObject, classDescriptor.name.asString())
- .withInsertHandler(KotlinClassifierInsertHandler)
+ private fun createLookUpElement(classDescriptor: ClassDescriptor): LookupElement? {
+ val lookupObject =
+ object : DeclarationLookupObjectImpl(classDescriptor) {
+ override val psiElement = classDescriptor.source.getPsi()
+ override fun getIcon(flags: Int) =
+ KotlinDescriptorIconProvider.getIcon(classDescriptor, psiElement, flags)
+ }
- val classFqName = classDescriptor.fqNameSafe.takeUnless { it.isRoot } ?: return null
+ var element =
+ LookupElementBuilder.create(lookupObject, classDescriptor.name.asString())
+ .withInsertHandler(KotlinClassifierInsertHandler)
- val containerName = classFqName.parent()
- element = element.appendTailText(" ($containerName)", true)
- return element.withIconFromLookupObject()
+ val classFqName = classDescriptor.fqNameSafe.takeUnless { it.isRoot } ?: return null
+
+ val containerName = classFqName.parent()
+ element = element.appendTailText(" ($containerName)", true)
+ return element.withIconFromLookupObject()
+ }
+
+ private fun getImportedDirectives(element: KtSimpleNameExpression): Set<ImportPath> {
+ return element.containingKtFile.importDirectives.mapNotNull { it.importPath }.toSet()
+ }
}
-
- private fun getImportedDirectives(element: KtSimpleNameExpression): Set<ImportPath> {
- return element.containingKtFile.importDirectives
- .mapNotNull { it.importPath }
- .toSet()
- }
- })
+ )
}
}
// Copy from BasicLookupElementFactory
-private fun LookupElement.withIconFromLookupObject(): LookupElement = object : LookupElementDecorator<LookupElement>(this) {
- override fun renderElement(presentation: LookupElementPresentation) {
- super.renderElement(presentation)
- presentation.icon = DefaultLookupItemRenderer.getRawIcon(this@withIconFromLookupObject)
+private fun LookupElement.withIconFromLookupObject(): LookupElement =
+ object : LookupElementDecorator<LookupElement>(this) {
+ override fun renderElement(presentation: LookupElementPresentation) {
+ super.renderElement(presentation)
+ presentation.icon = DefaultLookupItemRenderer.getRawIcon(this@withIconFromLookupObject)
+ }
}
-}
\ No newline at end of file
diff --git a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKtDescriptorUtils.kt b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKtDescriptorUtils.kt
index 1d30ec5..f1c69b5 100644
--- a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKtDescriptorUtils.kt
+++ b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKtDescriptorUtils.kt
@@ -33,18 +33,25 @@
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
import org.jetbrains.kotlin.resolve.source.getPsi
-internal fun Module.getDescriptorsByModulesWithDependencies(): Map<FqName, List<PackageFragmentDescriptor>> {
+internal fun Module.getDescriptorsByModulesWithDependencies():
+ Map<FqName, List<PackageFragmentDescriptor>> {
val moduleDescriptor = this.toDescriptor() ?: return emptyMap()
- val descriptorsFromThisModule = KtDescriptorCacheModuleService.getInstance(this).getDescriptors(moduleDescriptor).toMutableMap()
- return ModuleRootManager.getInstance(this).getDependencies(false)
+ val descriptorsFromThisModule =
+ KtDescriptorCacheModuleService.getInstance(this).getDescriptors(moduleDescriptor).toMutableMap()
+ return ModuleRootManager.getInstance(this)
+ .getDependencies(false)
.asSequence()
- .map { ProgressManager.checkCanceled(); it }
+ .map {
+ ProgressManager.checkCanceled()
+ it
+ }
.mapNotNull {
val descriptor = it?.toDescriptor() ?: return@mapNotNull null
KtDescriptorCacheModuleService.getInstance(it).getDescriptors(descriptor)
}
.fold(descriptorsFromThisModule) { acc, curr ->
- // TODO(b/159954452): duplications(e.g Same fragment class declared across multiple nav resource files) need to be
+ // TODO(b/159954452): duplications(e.g Same fragment class declared across multiple nav
+ // resource files) need to be
// resolved.
curr.entries.forEach { entry -> acc.merge(entry.key, entry.value) { old, new -> old + new } }
acc
@@ -61,8 +68,19 @@
class XmlSourceElement(override val psi: PsiElement) : PsiSourceElement
-internal fun SourceElement.withFunctionIcon(name: String, containingClassName: String): SourceElement {
+internal fun SourceElement.withFunctionIcon(
+ name: String,
+ containingClassName: String
+): SourceElement {
return (this.getPsi() as? SafeArgsXmlTag)?.let {
- XmlSourceElement(SafeArgsXmlTag(it.getOriginal(), IconManager.getInstance().getPlatformIcon(com.intellij.ui.PlatformIcons.Function), name, containingClassName))
- } ?: this
+ XmlSourceElement(
+ SafeArgsXmlTag(
+ it.getOriginal(),
+ IconManager.getInstance().getPlatformIcon(com.intellij.ui.PlatformIcons.Function),
+ name,
+ containingClassName
+ )
+ )
+ }
+ ?: this
}
diff --git a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKtPackageProviderExtension.kt b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKtPackageProviderExtension.kt
index 337d337..bbf8c99a 100644
--- a/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKtPackageProviderExtension.kt
+++ b/nav/safeargs/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKtPackageProviderExtension.kt
@@ -31,21 +31,24 @@
import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension
import org.jetbrains.kotlin.storage.StorageManager
-/**
- * Safe Args Kotlin Synthetic module package provider
- */
+/** Safe Args Kotlin Synthetic module package provider */
class SafeArgsKtPackageProviderExtension(val project: Project) : PackageFragmentProviderExtension {
- override fun getPackageFragmentProvider(project: Project,
- module: ModuleDescriptor,
- storageManager: StorageManager,
- trace: BindingTrace,
- moduleInfo: ModuleInfo?,
- lookupTracker: LookupTracker): PackageFragmentProvider? {
+ override fun getPackageFragmentProvider(
+ project: Project,
+ module: ModuleDescriptor,
+ storageManager: StorageManager,
+ trace: BindingTrace,
+ moduleInfo: ModuleInfo?,
+ lookupTracker: LookupTracker
+ ): PackageFragmentProvider? {
val facet = moduleInfo?.toModule()?.let { AndroidFacet.getInstance(it) } ?: return null
if (facet.safeArgsMode != SafeArgsMode.KOTLIN) return null
- val packageDescriptors = KtDescriptorCacheModuleService.getInstance(facet.module).getDescriptors(module).takeIf { it.isNotEmpty() }
- ?: return null
+ val packageDescriptors =
+ KtDescriptorCacheModuleService.getInstance(facet.module).getDescriptors(module).takeIf {
+ it.isNotEmpty()
+ }
+ ?: return null
return SafeArgsSyntheticPackageProvider(packageDescriptors)
}
}
@@ -53,7 +56,10 @@
class SafeArgsSyntheticPackageProvider(
private val packageDescriptorProvider: Map<FqName, List<PackageFragmentDescriptor>>
) : PackageFragmentProviderOptimized {
- override fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection<PackageFragmentDescriptor>) {
+ override fun collectPackageFragments(
+ fqName: FqName,
+ packageFragments: MutableCollection<PackageFragmentDescriptor>
+ ) {
val descriptors = packageDescriptorProvider[fqName] ?: return
packageFragments.addAll(descriptors)
}
@@ -63,7 +69,8 @@
}
override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): List<FqName> {
- return packageDescriptorProvider.asSequence()
+ return packageDescriptorProvider
+ .asSequence()
.filter { (k, _) -> !k.isRoot && k.parent() == fqName }
.mapTo(mutableListOf()) { it.key }
}
@@ -71,4 +78,4 @@
override fun isEmpty(fqName: FqName): Boolean {
return packageDescriptorProvider[fqName].isNullOrEmpty()
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/testData/projects/SimpleKotlinProject/app/src/main/java/com/example/myapplication/FirstFragment.kt b/nav/safeargs/testData/projects/SimpleKotlinProject/app/src/main/java/com/example/myapplication/FirstFragment.kt
index f3e604d..01d8df5 100644
--- a/nav/safeargs/testData/projects/SimpleKotlinProject/app/src/main/java/com/example/myapplication/FirstFragment.kt
+++ b/nav/safeargs/testData/projects/SimpleKotlinProject/app/src/main/java/com/example/myapplication/FirstFragment.kt
@@ -17,4 +17,4 @@
import androidx.fragment.app.Fragment
-class FirstFragment : Fragment()
\ No newline at end of file
+class FirstFragment : Fragment()
diff --git a/nav/safeargs/testData/projects/SimpleKotlinProject/app/src/main/java/com/example/myapplication/SecondFragment.kt b/nav/safeargs/testData/projects/SimpleKotlinProject/app/src/main/java/com/example/myapplication/SecondFragment.kt
index 77410f0..dac09c9 100644
--- a/nav/safeargs/testData/projects/SimpleKotlinProject/app/src/main/java/com/example/myapplication/SecondFragment.kt
+++ b/nav/safeargs/testData/projects/SimpleKotlinProject/app/src/main/java/com/example/myapplication/SecondFragment.kt
@@ -17,4 +17,4 @@
import androidx.fragment.app.Fragment
-class SecondFragment : Fragment()
\ No newline at end of file
+class SecondFragment : Fragment()
diff --git a/nav/safeargs/testData/projects/SimpleKotlinProject/mylibrary/src/main/java/com/example/mylibrary/FirstFragment.kt b/nav/safeargs/testData/projects/SimpleKotlinProject/mylibrary/src/main/java/com/example/mylibrary/FirstFragment.kt
index 1277755..d49f55c 100644
--- a/nav/safeargs/testData/projects/SimpleKotlinProject/mylibrary/src/main/java/com/example/mylibrary/FirstFragment.kt
+++ b/nav/safeargs/testData/projects/SimpleKotlinProject/mylibrary/src/main/java/com/example/mylibrary/FirstFragment.kt
@@ -17,4 +17,4 @@
import androidx.fragment.app.Fragment
-class FirstFragment : Fragment()
\ No newline at end of file
+class FirstFragment : Fragment()
diff --git a/nav/safeargs/testData/projects/SimpleKotlinProject/mylibrary/src/main/java/com/example/mylibrary/SecondFragment.kt b/nav/safeargs/testData/projects/SimpleKotlinProject/mylibrary/src/main/java/com/example/mylibrary/SecondFragment.kt
index 21dab25..d1c8076 100644
--- a/nav/safeargs/testData/projects/SimpleKotlinProject/mylibrary/src/main/java/com/example/mylibrary/SecondFragment.kt
+++ b/nav/safeargs/testData/projects/SimpleKotlinProject/mylibrary/src/main/java/com/example/mylibrary/SecondFragment.kt
@@ -17,4 +17,4 @@
import androidx.fragment.app.Fragment
-class SecondFragment : Fragment()
\ No newline at end of file
+class SecondFragment : Fragment()
diff --git a/nav/safeargs/testData/projects/SimpleKotlinProject/mylibraryexcluded/src/main/java/com/example/mylibrary/FirstFragment.kt b/nav/safeargs/testData/projects/SimpleKotlinProject/mylibraryexcluded/src/main/java/com/example/mylibrary/FirstFragment.kt
index 1277755..d49f55c 100644
--- a/nav/safeargs/testData/projects/SimpleKotlinProject/mylibraryexcluded/src/main/java/com/example/mylibrary/FirstFragment.kt
+++ b/nav/safeargs/testData/projects/SimpleKotlinProject/mylibraryexcluded/src/main/java/com/example/mylibrary/FirstFragment.kt
@@ -17,4 +17,4 @@
import androidx.fragment.app.Fragment
-class FirstFragment : Fragment()
\ No newline at end of file
+class FirstFragment : Fragment()
diff --git a/nav/safeargs/testData/projects/SimpleKotlinProject/mylibraryexcluded/src/main/java/com/example/mylibrary/SecondFragment.kt b/nav/safeargs/testData/projects/SimpleKotlinProject/mylibraryexcluded/src/main/java/com/example/mylibrary/SecondFragment.kt
index 21dab25..d1c8076 100644
--- a/nav/safeargs/testData/projects/SimpleKotlinProject/mylibraryexcluded/src/main/java/com/example/mylibrary/SecondFragment.kt
+++ b/nav/safeargs/testData/projects/SimpleKotlinProject/mylibraryexcluded/src/main/java/com/example/mylibrary/SecondFragment.kt
@@ -17,4 +17,4 @@
import androidx.fragment.app.Fragment
-class SecondFragment : Fragment()
\ No newline at end of file
+class SecondFragment : Fragment()
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/SafeArgsRule.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/SafeArgsRule.kt
index a35f52b..8dd2e5e 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/SafeArgsRule.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/SafeArgsRule.kt
@@ -58,34 +58,49 @@
override fun before() {
fixture.testDataPath = TestDataPaths.TEST_DATA_ROOT
- fixture.addFileToProject("AndroidManifest.xml", """
+ fixture.addFileToProject(
+ "AndroidManifest.xml",
+ """
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="test.safeargs">
<application />
</manifest>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
androidFacet.safeArgsMode = mode
// Add fake "NavArgs" interface to this project so the args class can resolve its interface
- with(fixture.addFileToProject("src/androidx/navigation/NavArgs.java",
- // language=java
- """
+ with(
+ fixture.addFileToProject(
+ "src/androidx/navigation/NavArgs.java",
+ // language=java
+ """
package androidx.navigation;
public interface NavArgs {}
- """.trimIndent())) {
+ """
+ .trimIndent()
+ )
+ ) {
fixture.allowTreeAccessForFile(this.virtualFile)
}
- // Add fake "NavDirections" interface to this project so the args class can resolve its interface
- with(fixture.addFileToProject("src/androidx/navigation/NavDirections.java",
- // language=java
- """
+ // Add fake "NavDirections" interface to this project so the args class can resolve its
+ // interface
+ with(
+ fixture.addFileToProject(
+ "src/androidx/navigation/NavDirections.java",
+ // language=java
+ """
package androidx.navigation;
public interface NavDirections {}
- """.trimIndent())) {
+ """
+ .trimIndent()
+ )
+ ) {
fixture.allowTreeAccessForFile(this.virtualFile)
}
}
@@ -93,7 +108,9 @@
override fun apply(base: Statement, description: Description): Statement {
// We want to run tests on the EDT thread, but we also need to make sure the project rule is not
// initialized on the EDT.
- return RuleChain.outerRule(projectRule).around(EdtRule()).apply(super.apply(base, description), description)
+ return RuleChain.outerRule(projectRule)
+ .around(EdtRule())
+ .apply(super.apply(base, description), description)
}
/**
@@ -106,7 +123,11 @@
*/
fun addFakeNavigationDependency(version: Version) {
val projectSystem = TestProjectSystem(module.project)
- projectSystem.addDependency(GoogleMavenArtifactId.ANDROIDX_NAVIGATION_COMMON, module, GradleVersion.parse(version.toString()))
+ projectSystem.addDependency(
+ GoogleMavenArtifactId.ANDROIDX_NAVIGATION_COMMON,
+ module,
+ GradleVersion.parse(version.toString())
+ )
projectSystem.useInTests()
}
}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/TestDataPaths.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/TestDataPaths.kt
index 4e706a5..9dba789 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/TestDataPaths.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/TestDataPaths.kt
@@ -17,11 +17,10 @@
import com.android.testutils.TestUtils.resolveWorkspacePath
-/**
- * Constants for safe args test project paths.
- */
+/** Constants for safe args test project paths. */
object TestDataPaths {
- val TEST_DATA_ROOT: String = resolveWorkspacePath("tools/adt/idea/nav/safeargs/testData").toString()
+ val TEST_DATA_ROOT: String =
+ resolveWorkspacePath("tools/adt/idea/nav/safeargs/testData").toString()
const val PROJECT_WITHOUT_SAFE_ARGS = "projects/projectWithoutSafeArgs"
const val PROJECT_USING_JAVA_PLUGIN = "projects/safeArgsWithJavaPlugin"
@@ -29,4 +28,4 @@
const val MULTI_MODULE_PROJECT = "projects/safeArgsMultiModuleProject"
const val SIMPLE_JAVA_PROJECT = "projects/SimpleJavaProject"
const val SIMPLE_KOTLIN_PROJECT = "projects/SimpleKotlinProject"
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/cache/gradle/ShortNamesCacheTestMultiJavaModules.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/cache/gradle/ShortNamesCacheTestMultiJavaModules.kt
index e39653f..46090cc 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/cache/gradle/ShortNamesCacheTestMultiJavaModules.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/cache/gradle/ShortNamesCacheTestMultiJavaModules.kt
@@ -34,10 +34,10 @@
private val projectRule = AndroidGradleProjectRule()
// The tests need to run on the EDT thread but we must initialize the project rule off of it
- @get:Rule
- val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
+ @get:Rule val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
- private val fixture get() = projectRule.fixture as JavaCodeInsightTestFixture
+ private val fixture
+ get() = projectRule.fixture as JavaCodeInsightTestFixture
@Before
fun setUp() {
@@ -47,10 +47,11 @@
}
/**
- * Project structure:
- * base app module --> lib1 dep module(safe arg mode is off) --> lib2 dep module(safe arg mode is on)
+ * Project structure: base app module --> lib1 dep module(safe arg mode is off) --> lib2 dep
+ * module(safe arg mode is on)
*
- * So light classes from lib2 module should be exposed, but light classes from lib1 should not be exposed.
+ * So light classes from lib2 module should be exposed, but light classes from lib1 should not be
+ * exposed.
*/
@Test
fun multiModuleTest() {
@@ -58,34 +59,33 @@
val cache = PsiShortNamesCache.getInstance(fixture.project)
// Check light arg classes
- assertThat(cache.getContents("FirstFragmentArgs", fixture.project)).containsExactly(
- "com.example.myapplication.FirstFragmentArgs",
- "com.example.mylibrary2.FirstFragmentArgs"
- )
+ assertThat(cache.getContents("FirstFragmentArgs", fixture.project))
+ .containsExactly(
+ "com.example.myapplication.FirstFragmentArgs",
+ "com.example.mylibrary2.FirstFragmentArgs"
+ )
- assertThat(cache.getContents("SecondFragmentArgs", fixture.project)).containsExactly(
- "com.example.myapplication.SecondFragmentArgs"
- )
-
+ assertThat(cache.getContents("SecondFragmentArgs", fixture.project))
+ .containsExactly("com.example.myapplication.SecondFragmentArgs")
// Check light direction classes
- assertThat(cache.getContents("FirstFragmentDirections", fixture.project)).containsExactly(
- "com.example.myapplication.FirstFragmentDirections",
- "com.example.mylibrary2.FirstFragmentDirections"
- )
+ assertThat(cache.getContents("FirstFragmentDirections", fixture.project))
+ .containsExactly(
+ "com.example.myapplication.FirstFragmentDirections",
+ "com.example.mylibrary2.FirstFragmentDirections"
+ )
- assertThat(cache.getContents("SecondFragmentDirections", fixture.project)).containsExactly(
- "com.example.myapplication.SecondFragmentDirections"
- )
+ assertThat(cache.getContents("SecondFragmentDirections", fixture.project))
+ .containsExactly("com.example.myapplication.SecondFragmentDirections")
// Check light builder classes
- assertThat(cache.getContents("Builder", fixture.project)).containsAllOf(
- "com.example.myapplication.FirstFragmentArgs.Builder",
- "com.example.mylibrary2.FirstFragmentArgs.Builder"
- )
+ assertThat(cache.getContents("Builder", fixture.project))
+ .containsAllOf(
+ "com.example.myapplication.FirstFragmentArgs.Builder",
+ "com.example.mylibrary2.FirstFragmentArgs.Builder"
+ )
- assertThat(cache.getContents("Builder", fixture.project)).contains(
- "com.example.myapplication.SecondFragmentArgs.Builder"
- )
+ assertThat(cache.getContents("Builder", fixture.project))
+ .contains("com.example.myapplication.SecondFragmentArgs.Builder")
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/cache/gradle/ShortNamesCacheTestSingleJavaModule.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/cache/gradle/ShortNamesCacheTestSingleJavaModule.kt
index 59d3366..e465b49 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/cache/gradle/ShortNamesCacheTestSingleJavaModule.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/cache/gradle/ShortNamesCacheTestSingleJavaModule.kt
@@ -25,14 +25,13 @@
@RunsInEdt
class ShortNamesCacheTestSingleJavaModule {
- @get:Rule
- val safeArgsRule = SafeArgsRule()
+ @get:Rule val safeArgsRule = SafeArgsRule()
@Test
fun getShortNamesCache() {
val project = safeArgsRule.project
val xmlContent =
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -53,23 +52,20 @@
app:destination="@id/main" />
</fragment>
</navigation>
- """.trimIndent()
+ """
+ .trimIndent()
safeArgsRule.fixture.addFileToProject("res/navigation/main.xml", xmlContent)
val cache = PsiShortNamesCache.getInstance(project)
// Check light arg classes
- assertThat(cache.getContents("FragmentArgs", project)).containsExactly(
- "test.safeargs.FragmentArgs"
- )
+ assertThat(cache.getContents("FragmentArgs", project))
+ .containsExactly("test.safeargs.FragmentArgs")
// Check light direction classes
- assertThat(cache.getContents("FragmentDirections", project)).containsExactly(
- "test.safeargs.FragmentDirections"
- )
+ assertThat(cache.getContents("FragmentDirections", project))
+ .containsExactly("test.safeargs.FragmentDirections")
// Check light builder classes
- assertThat(cache.getContents("Builder", project)).contains(
- "test.safeargs.FragmentArgs.Builder"
- )
+ assertThat(cache.getContents("Builder", project)).contains("test.safeargs.FragmentArgs.Builder")
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/codegen/gradle/SafeArgsGeneratedJavaCodeMatchTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/codegen/gradle/SafeArgsGeneratedJavaCodeMatchTest.kt
index 0192a5c..fbbca1d 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/codegen/gradle/SafeArgsGeneratedJavaCodeMatchTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/codegen/gradle/SafeArgsGeneratedJavaCodeMatchTest.kt
@@ -36,6 +36,7 @@
import com.intellij.testFramework.EdtRule
import com.intellij.testFramework.RunsInEdt
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
+import java.io.File
import org.jetbrains.uast.UClass
import org.jetbrains.uast.toUElement
import org.jetbrains.uast.visitor.AbstractUastVisitor
@@ -45,39 +46,42 @@
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TemporaryFolder
-import java.io.File
@RunsInEdt
class SafeArgsGeneratedJavaCodeMatchTest {
private val moduleName = "javaapp"
private val projectRule = AndroidGradleProjectRule()
- private val fixture get() = projectRule.fixture as JavaCodeInsightTestFixture
- //TODO (b/162520387): Do not ignore these methods when testing.
- private val IGNORED_METHODS = setOf("equals", "hashCode", "toString", "getActionId", "getArguments")
+ private val fixture
+ get() = projectRule.fixture as JavaCodeInsightTestFixture
+ // TODO (b/162520387): Do not ignore these methods when testing.
+ private val IGNORED_METHODS =
+ setOf("equals", "hashCode", "toString", "getActionId", "getArguments")
- @get:Rule
- val expect: Expect = Expect.create()
+ @get:Rule val expect: Expect = Expect.create()
- @get:Rule
- val temporaryFolder = TemporaryFolder()
+ @get:Rule val temporaryFolder = TemporaryFolder()
- @get:Rule
- val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
+ @get:Rule val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
@Before
fun initProject() {
// to be able to change the project before import, we copy it into a temp folder
- val testSrc = resolveWorkspacePath("tools/adt/idea/nav/safeargs/testData/projects/SafeArgsTestApp")
+ val testSrc =
+ resolveWorkspacePath("tools/adt/idea/nav/safeargs/testData/projects/SafeArgsTestApp")
val container = temporaryFolder.newFile("TestApp")
testSrc.toFile().copyRecursively(container, overwrite = true)
- val settingsFile = container.resolve("settings.gradle").also {
- assertWithMessage("settings file should exist").that(it.exists()).isTrue()
- }
+ val settingsFile =
+ container.resolve("settings.gradle").also {
+ assertWithMessage("settings file should exist").that(it.exists()).isTrue()
+ }
// update settings to only include the desired module
- settingsFile.writeText("""
+ settingsFile.writeText(
+ """
include ':$moduleName'
- """.trimIndent())
+ """
+ .trimIndent()
+ )
projectRule.fixture.testDataPath = temporaryFolder.root.absolutePath
projectRule.load("TestApp") { projectRoot ->
@@ -89,7 +93,9 @@
// language=java
"""
class FooClass
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
@@ -101,9 +107,12 @@
assertThat(assembleDebug.isBuildSuccessful).isTrue()
LocalFileSystem.getInstance().refresh(false)
- val codeOutDir = File(projectRule.project.basePath, "$moduleName/$PLUGIN_OUT_DIR").also {
- assertWithMessage("should be able to find generated navigation code").that(it.exists()).isTrue()
- }
+ val codeOutDir =
+ File(projectRule.project.basePath, "$moduleName/$PLUGIN_OUT_DIR").also {
+ assertWithMessage("should be able to find generated navigation code")
+ .that(it.exists())
+ .isTrue()
+ }
// parse generated code
val allGeneratedCode = listOf(codeOutDir).flatMap(::loadClasses).toSet()
// delete generated code
@@ -127,14 +136,24 @@
val psiClass = psiFacade.findClass(generated.qualifiedName, scope)!!
val psiDescription = psiClass.toDescription()
- expect.withMessage(generated.qualifiedName).that(psiDescription.qualifiedName).isEqualTo(generated.qualifiedName)
- expect.withMessage(generated.qualifiedName).that(psiDescription.methods).containsExactlyElementsIn(generated.methods)
- expect.withMessage(generated.qualifiedName).that(psiDescription.fields).containsExactlyElementsIn(generated.fields)
+ expect
+ .withMessage(generated.qualifiedName)
+ .that(psiDescription.qualifiedName)
+ .isEqualTo(generated.qualifiedName)
+ expect
+ .withMessage(generated.qualifiedName)
+ .that(psiDescription.methods)
+ .containsExactlyElementsIn(generated.methods)
+ expect
+ .withMessage(generated.qualifiedName)
+ .that(psiDescription.fields)
+ .containsExactlyElementsIn(generated.fields)
}
}
private fun loadClasses(classesOut: File): List<ClassDescription> {
- return classesOut.walkTopDown()
+ return classesOut
+ .walkTopDown()
.filter { it.name.endsWith(".java") }
.toList()
.flatMap { generatedSourceFile -> generatedSourceFile.loadClassesDescriptions() }
@@ -143,58 +162,67 @@
private fun File.loadClassesDescriptions(): List<ClassDescription> {
val descriptions = mutableListOf<ClassDescription>()
- val virtual = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(this) ?: throw IllegalArgumentException("cannot find $this")
+ val virtual =
+ LocalFileSystem.getInstance().refreshAndFindFileByIoFile(this)
+ ?: throw IllegalArgumentException("cannot find $this")
val psi = PsiManager.getInstance(projectRule.project).findFile(virtual)
val uast = psi.toUElement()!!
- uast.accept(object : AbstractUastVisitor() {
- override fun visitClass(node: UClass): Boolean {
- node.javaPsi.takeIf { it.modifierSet().contains(JvmModifier.PUBLIC) }
- ?.toDescription()
- ?.let { descriptions.add(it) }
- return super.visitClass(node)
+ uast.accept(
+ object : AbstractUastVisitor() {
+ override fun visitClass(node: UClass): Boolean {
+ node.javaPsi
+ .takeIf { it.modifierSet().contains(JvmModifier.PUBLIC) }
+ ?.toDescription()
+ ?.let { descriptions.add(it) }
+ return super.visitClass(node)
+ }
}
- })
+ )
return descriptions
}
- private fun PsiClass.toDescription() = ClassDescription(
- qualifiedName = this.qualifiedName!!,
- methods = (methods + constructors)
- .filter { it.modifierSet().contains(JvmModifier.PUBLIC) }
- .map { it.toDescription() }
- .filter { !IGNORED_METHODS.contains(it.name) }
- .sortedBy { it.name }
- .toSet(),
- fields = fields
- .filter { it.modifierSet().contains(JvmModifier.PUBLIC) }
- .map { it.toDescription() }
- .sortedBy { it.name }
- .toSet()
- )
+ private fun PsiClass.toDescription() =
+ ClassDescription(
+ qualifiedName = this.qualifiedName!!,
+ methods =
+ (methods + constructors)
+ .filter { it.modifierSet().contains(JvmModifier.PUBLIC) }
+ .map { it.toDescription() }
+ .filter { !IGNORED_METHODS.contains(it.name) }
+ .sortedBy { it.name }
+ .toSet(),
+ fields =
+ fields
+ .filter { it.modifierSet().contains(JvmModifier.PUBLIC) }
+ .map { it.toDescription() }
+ .sortedBy { it.name }
+ .toSet()
+ )
- private fun PsiMethod.toDescription() = MethodDescription(
- name = this.name,
- type = this.returnType?.toDescription(),
- params = this.parameters
- .filterIsInstance<PsiParameter>()
- .map { it.toDescription() }
- .toSet(),
- modifiers = this.modifierSet()
- )
+ private fun PsiMethod.toDescription() =
+ MethodDescription(
+ name = this.name,
+ type = this.returnType?.toDescription(),
+ params = this.parameters.filterIsInstance<PsiParameter>().map { it.toDescription() }.toSet(),
+ modifiers = this.modifierSet()
+ )
- private fun PsiParameter.toDescription() = ParamDescription(
- name = this.name,
- type = this.type.toDescription(),
- modifiers = this.modifierSet().filter { it != JvmModifier.PACKAGE_LOCAL }.toSet()
- )
+ private fun PsiParameter.toDescription() =
+ ParamDescription(
+ name = this.name,
+ type = this.type.toDescription(),
+ modifiers = this.modifierSet().filter { it != JvmModifier.PACKAGE_LOCAL }.toSet()
+ )
- private fun PsiField.toDescription() = FieldDescription(
- name = this.name,
- type = this.type.toDescription(),
- modifiers = this.modifierSet()
- )
+ private fun PsiField.toDescription() =
+ FieldDescription(
+ name = this.name,
+ type = this.type.toDescription(),
+ modifiers = this.modifierSet()
+ )
- private fun PsiType.toDescription() = this.canonicalText.substringAfterLast('.').substringAfterLast('$')
+ private fun PsiType.toDescription() =
+ this.canonicalText.substringAfterLast('.').substringAfterLast('$')
private data class ClassDescription(
val qualifiedName: String,
@@ -206,22 +234,26 @@
val name: String,
val type: String?,
val modifiers: Set<JvmModifier>,
- val params: Set<ParamDescription>)
+ val params: Set<ParamDescription>
+ )
private data class FieldDescription(
val name: String,
val type: String,
- val modifiers: Set<JvmModifier>)
+ val modifiers: Set<JvmModifier>
+ )
private data class ParamDescription(
val name: String,
val type: String,
- val modifiers: Set<JvmModifier>)
+ val modifiers: Set<JvmModifier>
+ )
- private fun PsiModifierListOwner.modifierSet() = JvmModifier.values().filter { hasModifier(it) }.toSet()
+ private fun PsiModifierListOwner.modifierSet() =
+ JvmModifier.values().filter { hasModifier(it) }.toSet()
companion object {
const val PLUGIN_OUT_DIR = "build/generated/source/navigation-args/debug"
const val GENERATE_TASK = "generateSafeArgsDebug"
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/extensions/PsiMethodUtils.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/extensions/PsiMethodUtils.kt
index 037a43c..225effa 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/extensions/PsiMethodUtils.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/extensions/PsiMethodUtils.kt
@@ -25,9 +25,7 @@
import com.intellij.psi.PsiType
import com.intellij.psi.PsiTypes
-/**
- * A simple sanity check helper for [PsiMethod] instances using String checks.
- */
+/** A simple sanity check helper for [PsiMethod] instances using String checks. */
fun PsiMethod.checkSignaturesAndReturnType(
name: String,
returnType: String,
@@ -38,8 +36,7 @@
if (returnType == PsiTypes.nullType().presentableText) {
assertThat(getTypeName(this.returnType)).isNull()
- }
- else {
+ } else {
assertThat(getTypeName(this.returnType)).isEqualTo(returnType)
val nullabilityManager = NullableNotNullManager.getInstance(this.project)
@@ -48,11 +45,13 @@
assertThat(this.parameters.size).isEqualTo(parameters.size)
- this.parameters.map { parameter ->
- val pName = parameter.name!!
- val pType = getTypeName((parameter as PsiParameter).type)!!
- Parameter(pName, pType)
- }.containsAll(parameters)
+ this.parameters
+ .map { parameter ->
+ val pName = parameter.name!!
+ val pType = getTypeName((parameter as PsiParameter).type)!!
+ Parameter(pName, pType)
+ }
+ .containsAll(parameters)
}
private fun getTypeName(type: PsiType?): String? {
@@ -67,8 +66,4 @@
}
}
-data class Parameter(
- val name: String,
- val type: String
-)
-
+data class Parameter(val name: String, val type: String)
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/extensions/PsiShortNamesCacheUtils.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/extensions/PsiShortNamesCacheUtils.kt
index d7e469e..f0767ba 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/extensions/PsiShortNamesCacheUtils.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/extensions/PsiShortNamesCacheUtils.kt
@@ -24,4 +24,4 @@
.asSequence()
.mapNotNull { it.qualifiedName }
.toSortedSet()
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/extensions/VirtualFileUtils.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/extensions/VirtualFileUtils.kt
index 7e01dc6..2f4837b 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/extensions/VirtualFileUtils.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/extensions/VirtualFileUtils.kt
@@ -43,4 +43,4 @@
PsiDocumentManager.getInstance(project).commitDocument(this)
FileDocumentManager.getInstance().saveDocument(this)
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/gradle/SafeArgsModeSyncGradlePluginTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/gradle/SafeArgsModeSyncGradlePluginTest.kt
index 1930276..51b7f3d 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/gradle/SafeArgsModeSyncGradlePluginTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/gradle/SafeArgsModeSyncGradlePluginTest.kt
@@ -34,9 +34,7 @@
import org.junit.runners.Parameterized
import org.mockito.Mockito.verify
-/**
- * Verify that we can sync a Gradle project that applies the safe args plugin.
- */
+/** Verify that we can sync a Gradle project that applies the safe args plugin. */
@RunsInEdt
@RunWith(Parameterized::class)
class SafeArgsModeSyncGradlePluginTest(val params: TestParams) {
@@ -46,19 +44,20 @@
@Suppress("unused") // Accessed via reflection by JUnit
@JvmStatic
@get:Parameterized.Parameters(name = "{0}")
- val parameters = listOf(
- TestParams(TestDataPaths.PROJECT_USING_JAVA_PLUGIN, SafeArgsMode.JAVA),
- TestParams(TestDataPaths.PROJECT_USING_KOTLIN_PLUGIN, SafeArgsMode.KOTLIN)
- )
+ val parameters =
+ listOf(
+ TestParams(TestDataPaths.PROJECT_USING_JAVA_PLUGIN, SafeArgsMode.JAVA),
+ TestParams(TestDataPaths.PROJECT_USING_KOTLIN_PLUGIN, SafeArgsMode.KOTLIN)
+ )
}
private val projectRule = AndroidGradleProjectRule()
// The tests need to run on the EDT thread but we must initialize the project rule off of it
- @get:Rule
- val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
+ @get:Rule val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
- private val fixture get() = projectRule.fixture as JavaCodeInsightTestFixture
+ private val fixture
+ get() = projectRule.fixture as JavaCodeInsightTestFixture
private var modificationCountBaseline = Long.MIN_VALUE
@Before
@@ -71,18 +70,22 @@
@Test
fun verifyExpectedSafeMode() {
val listener = mock<SafeArgsModeModuleService.SafeArgsModeChangedListener>()
- projectRule.project.messageBus.connect(fixture.projectDisposable).subscribe(
- SafeArgsModeModuleService.MODE_CHANGED,
- SafeArgsModeModuleService.SafeArgsModeChangedListener { module, mode ->
- listener.onSafeArgsModeChanged(module, mode)
- })
+ projectRule.project.messageBus
+ .connect(fixture.projectDisposable)
+ .subscribe(
+ SafeArgsModeModuleService.MODE_CHANGED,
+ SafeArgsModeModuleService.SafeArgsModeChangedListener { module, mode ->
+ listener.onSafeArgsModeChanged(module, mode)
+ }
+ )
projectRule.load(params.project)
projectRule.requestSyncAndWait()
val facet = projectRule.androidFacet(":app")
assertThat(facet.safeArgsMode).isEqualTo(params.mode)
- assertThat(projectRule.project.safeArgsModeTracker.modificationCount).isGreaterThan(modificationCountBaseline)
+ assertThat(projectRule.project.safeArgsModeTracker.modificationCount)
+ .isGreaterThan(modificationCountBaseline)
verify(listener).onSafeArgsModeChanged(facet.module, params.mode)
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/index/NavXmlIndexTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/index/NavXmlIndexTest.kt
index 9590086..118cc6a 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/index/NavXmlIndexTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/index/NavXmlIndexTest.kt
@@ -20,26 +20,27 @@
import com.intellij.openapi.application.runReadAction
import com.intellij.util.indexing.FileContentImpl
import com.intellij.util.io.DataExternalizer
-import org.junit.Rule
-import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.DataInputStream
import java.io.DataOutputStream
+import org.junit.Rule
+import org.junit.Test
class NavXmlIndexTest {
- @get:Rule
- val projectRule = AndroidProjectRule.onDisk()
+ @get:Rule val projectRule = AndroidProjectRule.onDisk()
private val fixture by lazy { projectRule.fixture }
private val project by lazy { projectRule.project }
@Test
fun indexNavigationLayout() {
- val file = fixture.addFileToProject(
- "navigation/main.xml",
- //language=XML
- """
+ val file =
+ fixture
+ .addFileToProject(
+ "navigation/main.xml",
+ // language=XML
+ """
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
@@ -133,7 +134,10 @@
</navigation>
</navigation>
- """.trimIndent()).virtualFile
+ """
+ .trimIndent()
+ )
+ .virtualFile
val navXmlIndex = NavXmlIndex()
assertThat(navXmlIndex.inputFilter.acceptInput(file)).isEqualTo(true)
@@ -271,18 +275,29 @@
// Verify all three destinations can be found from the root
assertThat(data.resolvedDestinations.map { it.id })
- .containsExactly("top_level_nav", "activity1", "fragment1", "fragment2", "nested_nav", "activity2", "fragment3", "double_nested_nav",
- "dialog1")
+ .containsExactly(
+ "top_level_nav",
+ "activity1",
+ "fragment1",
+ "fragment2",
+ "nested_nav",
+ "activity2",
+ "fragment3",
+ "double_nested_nav",
+ "dialog1"
+ )
verifySerializationLogic(navXmlIndex.valueExternalizer, data)
}
@Test
fun navigationIdIsOptional() {
- val file = fixture.addFileToProject(
- "navigation/main.xml",
- //language=XML
- """
+ val file =
+ fixture
+ .addFileToProject(
+ "navigation/main.xml",
+ // language=XML
+ """
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
@@ -292,7 +307,10 @@
android:name="test.safeargs.Fragment1"
tools:layout="@layout/fragment1" />
</navigation>
- """.trimIndent()).virtualFile
+ """
+ .trimIndent()
+ )
+ .virtualFile
val navXmlIndex = NavXmlIndex()
val map = navXmlIndex.indexer.map(FileContentImpl.createByFile(file))
@@ -308,10 +326,12 @@
@Test
fun customTagsAreTreatedAsPotentialDestinations() {
- val file = fixture.addFileToProject(
- "navigation/main.xml",
- //language=XML
- """
+ val file =
+ fixture
+ .addFileToProject(
+ "navigation/main.xml",
+ // language=XML
+ """
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
@@ -328,7 +348,10 @@
<unknownTag /> <!-- Probably breaks compiling but shouldn't break indexing -->
</navigation>
- """.trimIndent()).virtualFile
+ """
+ .trimIndent()
+ )
+ .virtualFile
val navXmlIndex = NavXmlIndex()
val map = navXmlIndex.indexer.map(FileContentImpl.createByFile(file))
@@ -337,18 +360,22 @@
val data = map.values.first()
assertThat(data.root.potentialDestinations).hasSize(3)
- // unknownTag, though potential, doesn't meet destination requirements, so it is stripped out at this time
- assertThat(data.root.potentialDestinations.mapNotNull { it.toDestination()?.id }).containsExactly("fragment1", "custom1")
+ // unknownTag, though potential, doesn't meet destination requirements, so it is stripped out at
+ // this time
+ assertThat(data.root.potentialDestinations.mapNotNull { it.toDestination()?.id })
+ .containsExactly("fragment1", "custom1")
verifySerializationLogic(navXmlIndex.valueExternalizer, data)
}
@Test
fun camelCaseIdsInNavigationTagAreSupported() {
- val file = fixture.addFileToProject(
- "navigation/main.xml",
- //language=XML
- """
+ val file =
+ fixture
+ .addFileToProject(
+ "navigation/main.xml",
+ // language=XML
+ """
<!-- Recommend syntax is "camel_case_graph" but "camelCaseGraph" works too -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -361,7 +388,10 @@
tools:layout="@layout/fragment" />
</navigation>
- """.trimIndent()).virtualFile
+ """
+ .trimIndent()
+ )
+ .virtualFile
val navXmlIndex = NavXmlIndex()
val map = navXmlIndex.indexer.map(FileContentImpl.createByFile(file))
@@ -378,16 +408,21 @@
@Test
fun indexRecoversFromUnrelatedXml() {
- val file = fixture.addFileToProject(
- "navigation/main.xml",
- //language=XML
- """
+ val file =
+ fixture
+ .addFileToProject(
+ "navigation/main.xml",
+ // language=XML
+ """
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.nav.safeargs">
<application android:label="Safe Args Test" />
</manifest>
- """.trimIndent()).virtualFile
+ """
+ .trimIndent()
+ )
+ .virtualFile
val navXmlIndex = NavXmlIndex()
assertThat(navXmlIndex.inputFilter.acceptInput(file)).isEqualTo(true)
@@ -399,10 +434,12 @@
@Test
fun xmlWithOpeningCommentIsParsed() {
// Regression test for b/300221546
- val file = fixture.addFileToProject(
- "navigation/main.xml",
- //language=XML
- """
+ val file =
+ fixture
+ .addFileToProject(
+ "navigation/main.xml",
+ // language=XML
+ """
<?xml version="1.0" encoding="utf-8"?>
<!-- Comment `1234567890-=~!@#$%^&*()_+,./;'[]<>?:"{}\| -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -415,7 +452,10 @@
android:name="test.safeargs.Fragment1"
tools:layout="@layout/fragment1" />
</navigation>
- """.trimIndent()).virtualFile
+ """
+ .trimIndent()
+ )
+ .virtualFile
val navXmlIndex = NavXmlIndex()
val map = navXmlIndex.indexer.map(FileContentImpl.createByFile(file))
@@ -432,10 +472,12 @@
@Test
fun navigationFileOutsideResourcesDirIsNotReturnedFromIndex() {
// Valid navigation file in correct resource directory.
- val navigationFile = fixture.addFileToProject(
- "navigation/main.xml",
- //language=XML
- """
+ val navigationFile =
+ fixture
+ .addFileToProject(
+ "navigation/main.xml",
+ // language=XML
+ """
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -447,13 +489,18 @@
android:name="test.safeargs.Fragment1"
tools:layout="@layout/fragment1" />
</navigation>
- """.trimIndent()).virtualFile
+ """
+ .trimIndent()
+ )
+ .virtualFile
// Valid navigation file, but in non-resource directory.
- val otherFile = fixture.addFileToProject(
- "otherDir/main.xml",
- //language=XML
- """
+ val otherFile =
+ fixture
+ .addFileToProject(
+ "otherDir/main.xml",
+ // language=XML
+ """
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -465,7 +512,10 @@
android:name="test.safeargs.Fragment1"
tools:layout="@layout/fragment1" />
</navigation>
- """.trimIndent()).virtualFile
+ """
+ .trimIndent()
+ )
+ .virtualFile
runReadAction {
assertThat(NavXmlIndex.getDataForFile(project, navigationFile)).isNotNull()
@@ -473,7 +523,10 @@
}
}
- private fun verifySerializationLogic(valueExternalizer: DataExternalizer<NavXmlData>, data: NavXmlData) {
+ private fun verifySerializationLogic(
+ valueExternalizer: DataExternalizer<NavXmlData>,
+ data: NavXmlData
+ ) {
val bytesOut = ByteArrayOutputStream()
DataOutputStream(bytesOut).use { valueExternalizer.save(it, data) }
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/module/DumbModeTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/module/DumbModeTest.kt
index bb731a3..f7055cd 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/module/DumbModeTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/module/DumbModeTest.kt
@@ -30,8 +30,7 @@
@RunsInEdt
class DumbModeTest {
- @get:Rule
- val safeArgsRule = SafeArgsRule()
+ @get:Rule val safeArgsRule = SafeArgsRule()
@Before
fun setUp() {
@@ -42,7 +41,7 @@
fun indexWhenDumbMode() {
val project = safeArgsRule.project
val xmlContent =
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -59,7 +58,8 @@
app:argType="string" />
</fragment>
</navigation>
- """.trimIndent()
+ """
+ .trimIndent()
val navFile = safeArgsRule.fixture.addFileToProject("res/navigation/main.xml", xmlContent)
safeArgsRule.waitForResourceRepositoryUpdates()
val moduleCache = SafeArgsCacheModuleService.getInstance(safeArgsRule.androidFacet)
@@ -74,7 +74,8 @@
android:name="arg2"
app:argType="integer" />
</fragment>
- """.trimIndent()
+ """
+ .trimIndent()
WriteCommandAction.runWriteCommandAction(project) {
navFile.virtualFile.replaceWithSaving("</fragment>", replaceXmlContent, project)
}
@@ -86,20 +87,22 @@
assertThat(getNumberOfArgs(moduleCache.args)).isEqualTo(2)
}
- private fun getNumberOfArgs(args: List<LightArgsClass>) = args.sumOf { it.destination.arguments.size }
+ private fun getNumberOfArgs(args: List<LightArgsClass>) =
+ args.sumOf { it.destination.arguments.size }
@Test
fun scopeDoesNotCacheStaleValuesInDumbMode() {
val dumbService = DumbServiceImpl.getInstance(safeArgsRule.project)
assertThat(dumbService.isDumb).isFalse()
- // In dumb mode, add a resource and then request the current scope. In the past, this would cause
+ // In dumb mode, add a resource and then request the current scope. In the past, this would
+ // cause
// the scope enlarger to internally cache stale values (because the service that the enlarger
// queries into aborts early in dumb mode).
dumbService.isDumb = true
safeArgsRule.fixture.addFileToProject(
"res/navigation/nav_main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -116,7 +119,9 @@
app:destination="@id/main" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
safeArgsRule.waitForResourceRepositoryUpdates()
val fragmentClass = safeArgsRule.fixture.addClass("public class MainFragment {}")
@@ -135,4 +140,4 @@
assertThat(PsiSearchScopeUtil.isInScope(enlargedScope, directionsClass)).isTrue()
}
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/module/NavStatusCacheTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/module/NavStatusCacheTest.kt
index 228ef4e..d957a1c 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/module/NavStatusCacheTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/module/NavStatusCacheTest.kt
@@ -1,4 +1,5 @@
-// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the
+// Apache 2.0 license.
package com.android.tools.idea.nav.safeargs.module
import com.android.testutils.MockitoKt.mock
@@ -7,15 +8,16 @@
import com.android.tools.idea.nav.safeargs.SafeArgsRule
import com.android.tools.idea.nav.safeargs.psi.SafeArgsFeatureVersions
import com.google.common.truth.Truth.assertThat
+import java.util.EnumSet
import org.junit.Before
import org.junit.Rule
import org.junit.Test
-import java.util.EnumSet
class NavStatusCacheTest {
@get:Rule val safeArgsRule = SafeArgsRule(SafeArgsMode.KOTLIN)
- private val changeReasons: MutableSet<NavInfoChangeReason> = EnumSet.noneOf(NavInfoChangeReason::class.java)
+ private val changeReasons: MutableSet<NavInfoChangeReason> =
+ EnumSet.noneOf(NavInfoChangeReason::class.java)
private val fetcher = mock<NavInfoFetcherBase>()
private lateinit var invalidate: (NavInfoChangeReason) -> Unit
private lateinit var computeStatus: (NavInfo) -> Any
@@ -24,14 +26,15 @@
@Before
fun setUp() {
whenever(fetcher.isEnabled).thenReturn(true)
- cache = NavStatusCache(
- onCacheInvalidate = { changeReasons.add(it) },
- update = { computeStatus(it) },
- navInfoFetcherFactory = factory@{
- invalidate = it
- return@factory fetcher
- }
- )
+ cache =
+ NavStatusCache(
+ onCacheInvalidate = { changeReasons.add(it) },
+ update = { computeStatus(it) },
+ navInfoFetcherFactory = factory@{
+ invalidate = it
+ return@factory fetcher
+ }
+ )
setNavInfo(0L)
}
@@ -103,15 +106,16 @@
private fun setNavInfo(modificationCount: Long) {
whenever(fetcher.modificationCount).thenReturn(modificationCount)
- whenever(fetcher.getCurrentNavInfo()).thenReturn(
- NavInfo(
- facet = safeArgsRule.androidFacet,
- packageName = "foo.bar",
- entries = emptyList(),
- navVersion = SafeArgsFeatureVersions.TO_SAVED_STATE_HANDLE,
- modificationCount = modificationCount,
+ whenever(fetcher.getCurrentNavInfo())
+ .thenReturn(
+ NavInfo(
+ facet = safeArgsRule.androidFacet,
+ packageName = "foo.bar",
+ entries = emptyList(),
+ navVersion = SafeArgsFeatureVersions.TO_SAVED_STATE_HANDLE,
+ modificationCount = modificationCount,
+ )
)
- )
assertThat(cache.modificationTracker.modificationCount).isEqualTo(modificationCount)
}
}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/module/gradle/NavInfoFetcherTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/module/gradle/NavInfoFetcherTest.kt
index 1535112..1a0a026 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/module/gradle/NavInfoFetcherTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/module/gradle/NavInfoFetcherTest.kt
@@ -1,4 +1,5 @@
-// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the
+// Apache 2.0 license.
package com.android.tools.idea.nav.safeargs.module.gradle
import com.android.tools.idea.nav.safeargs.SafeArgsMode
@@ -19,12 +20,12 @@
import com.intellij.openapi.project.DumbServiceImpl
import com.intellij.testFramework.EdtRule
import com.intellij.testFramework.RunsInEdt
+import java.util.EnumSet
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
-import java.util.EnumSet
@RunsInEdt
class NavInfoFetcherTest {
@@ -32,7 +33,8 @@
@get:Rule val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
- private val changeReasons: MutableSet<NavInfoChangeReason> = EnumSet.noneOf(NavInfoChangeReason::class.java)
+ private val changeReasons: MutableSet<NavInfoChangeReason> =
+ EnumSet.noneOf(NavInfoChangeReason::class.java)
private var baselineModificationCount = 0L
private lateinit var module: Module
private lateinit var fetcher: NavInfoFetcher
@@ -44,9 +46,11 @@
NavigationResourcesModificationListener.ensureSubscribed(projectRule.project)
module = projectRule.getModule("app.main")
- fetcher = NavInfoFetcher(projectRule.fixture.testRootDisposable, module, SafeArgsMode.KOTLIN) { changeReason ->
- changeReasons.add(changeReason)
- }
+ fetcher =
+ NavInfoFetcher(projectRule.fixture.testRootDisposable, module, SafeArgsMode.KOTLIN) {
+ changeReason ->
+ changeReasons.add(changeReason)
+ }
baselineModificationCount = fetcher.modificationCount
changeReasons.clear()
}
@@ -75,12 +79,12 @@
val destinations = potentialDestinations.mapNotNull { it.toDestination() }
assertThat(destinations).hasSize(2)
- val (firstDest, secondDest) = if (destinations[0].id == "FirstFragment") {
- destinations
- }
- else {
- destinations.reversed()
- }
+ val (firstDest, secondDest) =
+ if (destinations[0].id == "FirstFragment") {
+ destinations
+ } else {
+ destinations.reversed()
+ }
firstDest.apply {
assertThat(id).isEqualTo("FirstFragment")
@@ -130,23 +134,18 @@
@Test
fun updatesOnNavFileChange() {
WriteCommandAction.runWriteCommandAction(projectRule.project) {
- module.fileUnderGradleRoot("src/main/res/navigation/nav_graph.xml")!!.replaceWithoutSaving(
- "@+id/FirstFragment",
- "@+id/FirstFragmentChanged",
- module.project
- )
+ module
+ .fileUnderGradleRoot("src/main/res/navigation/nav_graph.xml")!!
+ .replaceWithoutSaving("@+id/FirstFragment", "@+id/FirstFragmentChanged", module.project)
}
assertModified(NavInfoChangeReason.NAVIGATION_RESOURCE_CHANGED)
assertThat(
- fetcher
- .getCurrentNavInfo()!!
- .entries
- .single()
- .data
- .resolvedDestinations
- .singleOrNull { it.id == "FirstFragmentChanged" }
- ).isNotNull()
+ fetcher.getCurrentNavInfo()!!.entries.single().data.resolvedDestinations.singleOrNull {
+ it.id == "FirstFragmentChanged"
+ }
+ )
+ .isNotNull()
}
@Test
@@ -166,7 +165,8 @@
@Test
fun updatesOnProjectSync() {
- module.project.messageBus.syncPublisher(PROJECT_SYSTEM_SYNC_TOPIC)
+ module.project.messageBus
+ .syncPublisher(PROJECT_SYSTEM_SYNC_TOPIC)
.syncEnded(ProjectSystemSyncManager.SyncResult.SUCCESS)
assertModified(NavInfoChangeReason.GRADLE_SYNC)
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/project/NavigationResourcesModificationListenerTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/project/NavigationResourcesModificationListenerTest.kt
index 514ac0f..d80fdb9 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/project/NavigationResourcesModificationListenerTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/project/NavigationResourcesModificationListenerTest.kt
@@ -32,8 +32,7 @@
import org.mockito.Mockito.verifyNoMoreInteractions
class NavigationResourcesModificationListenerTest {
- @get:Rule
- val safeArgsRule = SafeArgsRule()
+ @get:Rule val safeArgsRule = SafeArgsRule()
private lateinit var myModuleNavResourcesTracker: ModuleNavigationResourcesModificationTracker
private lateinit var myProjectNavResourcesTracker: ProjectNavigationResourceModificationTracker
@@ -45,14 +44,17 @@
@Before
fun setUp() {
- // Ensure that the resource repository is initialized before we start testing, since NavigationResourcesModificationListener will only
- // used an already-cached version of the repository and won't trigger creation if it doesn't already exist.
+ // Ensure that the resource repository is initialized before we start testing, since
+ // NavigationResourcesModificationListener will only
+ // used an already-cached version of the repository and won't trigger creation if it doesn't
+ // already exist.
safeArgsRule.fixture.addFileToProject("res/values/strings.xml", "")
safeArgsRule.waitForResourceRepositoryUpdates()
project.messageBus.connect().subscribe(NAVIGATION_RESOURCES_CHANGED, listener)
NavigationResourcesModificationListener.ensureSubscribed(project)
- myModuleNavResourcesTracker = ModuleNavigationResourcesModificationTracker.getInstance(safeArgsRule.module)
+ myModuleNavResourcesTracker =
+ ModuleNavigationResourcesModificationTracker.getInstance(safeArgsRule.module)
myProjectNavResourcesTracker = ProjectNavigationResourceModificationTracker.getInstance(project)
assertThat(myModuleNavResourcesTracker.modificationCount).isEqualTo(0L)
assertThat(myProjectNavResourcesTracker.modificationCount).isEqualTo(0L)
@@ -62,7 +64,7 @@
fun addFilesToNavigationResourcesFolder() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -78,7 +80,9 @@
app:argType="string" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
safeArgsRule.waitForResourceRepositoryUpdates()
// picked up 1 document change and 1 vfs change
@@ -86,7 +90,7 @@
safeArgsRule.fixture.addFileToProject(
"res/navigation/other.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -102,7 +106,9 @@
app:argType="integer" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
safeArgsRule.waitForResourceRepositoryUpdates()
// picked up 1 document change and 1 vfs change
@@ -111,10 +117,11 @@
@Test
fun deleteNavigationResourceFolder() {
- val navFile = safeArgsRule.fixture.addFileToProject(
- "res/navigation/main.xml",
- //language=XML
- """
+ val navFile =
+ safeArgsRule.fixture.addFileToProject(
+ "res/navigation/main.xml",
+ // language=XML
+ """
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/main"
@@ -129,15 +136,15 @@
app:argType="string" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
safeArgsRule.waitForResourceRepositoryUpdates()
// picked up 1 document change and 1 vfs change
verifyModuleChangeEventsFired(2)
- WriteCommandAction.runWriteCommandAction(project) {
- navFile.virtualFile.parent.delete(this)
- }
+ WriteCommandAction.runWriteCommandAction(project) { navFile.virtualFile.parent.delete(this) }
safeArgsRule.waitForResourceRepositoryUpdates()
// picked up 1 vfs change
verifyModuleChangeEventsFired(1)
@@ -145,10 +152,11 @@
@Test
fun modifyNavResourceFile() {
- val navFile = safeArgsRule.fixture.addFileToProject(
- "res/navigation/main.xml",
- //language=XML
- """
+ val navFile =
+ safeArgsRule.fixture.addFileToProject(
+ "res/navigation/main.xml",
+ // language=XML
+ """
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/main"
@@ -163,7 +171,9 @@
app:argType="string" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
safeArgsRule.waitForResourceRepositoryUpdates()
// picked up 1 document change and 1 vfs change
verifyModuleChangeEventsFired(2)
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/project/gradle/ProjectNavigationResourceModificationTrackerTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/project/gradle/ProjectNavigationResourceModificationTrackerTest.kt
index c8097c6..d3b6cd7 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/project/gradle/ProjectNavigationResourceModificationTrackerTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/project/gradle/ProjectNavigationResourceModificationTrackerTest.kt
@@ -38,17 +38,18 @@
/**
* Test that our project-wide modification tracker works across multiple modules.
*
- * This needs to be a gradle test because that's the only way right now we can support multi-module configurations
+ * This needs to be a gradle test because that's the only way right now we can support multi-module
+ * configurations
*/
@RunsInEdt
class ProjectNavigationResourceModificationTrackerTest {
private val projectRule = AndroidGradleProjectRule()
// The tests need to run on the EDT thread but we must initialize the project rule off of it
- @get:Rule
- val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
+ @get:Rule val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
- private val fixture get() = projectRule.fixture as JavaCodeInsightTestFixture
+ private val fixture
+ get() = projectRule.fixture as JavaCodeInsightTestFixture
@Before
fun setUp() {
@@ -58,38 +59,57 @@
}
/**
- * Project structure:
- * base app module --> lib1 dep module(safe arg mode is off) --> lib2 dep module(safe arg mode is on)
+ * Project structure: base app module --> lib1 dep module(safe arg mode is off) --> lib2 dep
+ * module(safe arg mode is on)
*/
@Test
fun multiModuleModificationTrackerTest() {
projectRule.requestSyncAndWait()
- val baseLineNumber = ProjectNavigationResourceModificationTracker.getInstance(fixture.project).modificationCount
+ val baseLineNumber =
+ ProjectNavigationResourceModificationTracker.getInstance(fixture.project).modificationCount
val listener = mock<NavigationResourcesChangeListener>()
projectRule.project.messageBus.connect().subscribe(NAVIGATION_RESOURCES_CHANGED, listener)
- val navFileInBaseAppModule = projectRule.project.baseDir.findFileByRelativePath(
- "app/src/main/res/navigation/nav_graph.xml")!!
+ val navFileInBaseAppModule =
+ projectRule.project.baseDir.findFileByRelativePath(
+ "app/src/main/res/navigation/nav_graph.xml"
+ )!!
val appModule = navFileInBaseAppModule.getModule(projectRule.project)!!
- val navFileInDepModule = projectRule.project.baseDir.findFileByRelativePath(
- "mylibrary2/src/main/res/navigation/libnav_graph.xml")!!
+ val navFileInDepModule =
+ projectRule.project.baseDir.findFileByRelativePath(
+ "mylibrary2/src/main/res/navigation/libnav_graph.xml"
+ )!!
val depModule = navFileInDepModule.getModule(projectRule.project)!!
// modify a nav file in base-app module without saving
WriteCommandAction.runWriteCommandAction(fixture.project) {
- navFileInBaseAppModule.replaceWithoutSaving("FirstFragment", "FirstFragmentChanged", fixture.project)
+ navFileInBaseAppModule.replaceWithoutSaving(
+ "FirstFragment",
+ "FirstFragmentChanged",
+ fixture.project
+ )
}
// picked up 1 document change
- assertThat(ProjectNavigationResourceModificationTracker.getInstance(fixture.project).modificationCount).isEqualTo(baseLineNumber + 1)
+ assertThat(
+ ProjectNavigationResourceModificationTracker.getInstance(fixture.project).modificationCount
+ )
+ .isEqualTo(baseLineNumber + 1)
verify(listener).onNavigationResourcesChanged(appModule)
// modify a nav file in dep module without saving
WriteCommandAction.runWriteCommandAction(fixture.project) {
- navFileInDepModule.replaceWithoutSaving("FirstFragment", "FirstFragmentChanged", fixture.project)
+ navFileInDepModule.replaceWithoutSaving(
+ "FirstFragment",
+ "FirstFragmentChanged",
+ fixture.project
+ )
}
// picked up 1 document change
- assertThat(ProjectNavigationResourceModificationTracker.getInstance(fixture.project).modificationCount).isEqualTo(baseLineNumber + 2)
+ assertThat(
+ ProjectNavigationResourceModificationTracker.getInstance(fixture.project).modificationCount
+ )
+ .isEqualTo(baseLineNumber + 2)
verify(listener).onNavigationResourcesChanged(depModule)
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightActionBuilderClassTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightActionBuilderClassTest.kt
index d1483b5..b7c2136 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightActionBuilderClassTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightActionBuilderClassTest.kt
@@ -28,14 +28,13 @@
@RunsInEdt
class LightActionBuilderClassTest {
- @get:Rule
- val safeArgsRule = SafeArgsRule()
+ @get:Rule val safeArgsRule = SafeArgsRule()
@Test
fun canFindActionBuilderClasses() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -65,17 +64,29 @@
app:destination="@id/main" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment1 {}")
- Truth.assertThat(safeArgsRule.fixture.findClass("test.safeargs.Fragment2Directions.ActionFragment2ToMain", context)).isNull()
+ Truth.assertThat(
+ safeArgsRule.fixture.findClass(
+ "test.safeargs.Fragment2Directions.ActionFragment2ToMain",
+ context
+ )
+ )
+ .isNull()
// Classes can be found with context
- val actionBuilderClass = safeArgsRule.fixture.findClass("test.safeargs.Fragment1Directions.ActionFragment1ToFragment2", context)
+ val actionBuilderClass =
+ safeArgsRule.fixture.findClass(
+ "test.safeargs.Fragment1Directions.ActionFragment1ToFragment2",
+ context
+ )
Truth.assertThat(actionBuilderClass).isInstanceOf(LightActionBuilderClass::class.java)
// Check supers
@@ -91,15 +102,10 @@
methods[0].checkSignaturesAndReturnType(
name = "setArgOne",
returnType = "ActionFragment1ToFragment2",
- parameters = listOf(
- Parameter("arg", "String")
- )
+ parameters = listOf(Parameter("arg", "String"))
)
- methods[1].checkSignaturesAndReturnType(
- name = "getArgOne",
- returnType = "String"
- )
+ methods[1].checkSignaturesAndReturnType(name = "getArgOne", returnType = "String")
}
}
@@ -107,7 +113,7 @@
fun testOverriddenArguments() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -149,7 +155,9 @@
app:destination="@id/main" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -157,10 +165,20 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment1 {}")
// All resolved arguments are with default values, so it falls back to NavDirections.
- Truth.assertThat(safeArgsRule.fixture.findClass("test.safeargs.Fragment2Directions.ActionFragment2ToMain", context)).isNull()
+ Truth.assertThat(
+ safeArgsRule.fixture.findClass(
+ "test.safeargs.Fragment2Directions.ActionFragment2ToMain",
+ context
+ )
+ )
+ .isNull()
// Classes can be found with context
- val actionBuilderClass = safeArgsRule.fixture.findClass("test.safeargs.Fragment1Directions.ActionFragment1ToFragment2", context)
+ val actionBuilderClass =
+ safeArgsRule.fixture.findClass(
+ "test.safeargs.Fragment1Directions.ActionFragment1ToFragment2",
+ context
+ )
Truth.assertThat(actionBuilderClass).isInstanceOf(LightActionBuilderClass::class.java)
// Check supers
@@ -176,22 +194,15 @@
methods[0].checkSignaturesAndReturnType(
name = "setOverriddenArg",
returnType = "ActionFragment1ToFragment2",
- parameters = listOf(
- Parameter("overriddenArg", "String")
- )
+ parameters = listOf(Parameter("overriddenArg", "String"))
)
- methods[1].checkSignaturesAndReturnType(
- name = "getOverriddenArg",
- returnType = "String"
- )
+ methods[1].checkSignaturesAndReturnType(name = "getOverriddenArg", returnType = "String")
methods[2].checkSignaturesAndReturnType(
name = "setOverriddenArgWithDefaultValue",
returnType = "ActionFragment1ToFragment2",
- parameters = listOf(
- Parameter("overriddenArgWithDefaultValue", "int")
- )
+ parameters = listOf(Parameter("overriddenArgWithDefaultValue", "int"))
)
methods[3].checkSignaturesAndReturnType(
@@ -202,15 +213,10 @@
methods[4].checkSignaturesAndReturnType(
name = "setArg",
returnType = "ActionFragment1ToFragment2",
- parameters = listOf(
- Parameter("arg", "String")
- )
+ parameters = listOf(Parameter("arg", "String"))
)
- methods[5].checkSignaturesAndReturnType(
- name = "getArg",
- returnType = "String"
- )
+ methods[5].checkSignaturesAndReturnType(name = "getArg", returnType = "String")
}
// Check private constructor
@@ -219,11 +225,8 @@
constructors[0].checkSignaturesAndReturnType(
name = "ActionFragment1ToFragment2",
returnType = PsiTypes.nullType().presentableText,
- parameters = listOf(
- Parameter("overriddenArg", "String"),
- Parameter("arg", "String")
- )
+ parameters = listOf(Parameter("overriddenArg", "String"), Parameter("arg", "String"))
)
}
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsAndBuilderClassInferredTypeTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsAndBuilderClassInferredTypeTest.kt
index 42314ab..c5e056b 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsAndBuilderClassInferredTypeTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsAndBuilderClassInferredTypeTest.kt
@@ -22,7 +22,6 @@
import com.android.tools.idea.res.StudioResourceRepositoryManager
import com.android.tools.idea.testing.findClass
import com.google.common.truth.Truth.assertThat
-import com.intellij.psi.PsiType
import com.intellij.psi.PsiTypes
import com.intellij.testFramework.RunsInEdt
import org.junit.Rule
@@ -31,39 +30,39 @@
import org.junit.runners.Parameterized
/**
- * Tests that would normally go in [LightArgsBuilderClassTest] and [LightArgsClass] but are related to
- * a bunch of arguments types that we want to test with parametrization.
+ * Tests that would normally go in [LightArgsBuilderClassTest] and [LightArgsClass] but are related
+ * to a bunch of arguments types that we want to test with parametrization.
*/
@RunsInEdt
@RunWith(Parameterized::class)
class LightArgsAndBuilderClassInferredTypeTest(
private val defaultValueTypeMapping: DefaultValueTypeMapping
) {
- @get:Rule
- val safeArgsRule = SafeArgsRule()
+ @get:Rule val safeArgsRule = SafeArgsRule()
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
- fun data() = listOf(
- DefaultValueTypeMapping("1", PsiTypes.intType().name),
- DefaultValueTypeMapping("0x21", PsiTypes.intType().name),
- DefaultValueTypeMapping("1f", PsiTypes.floatType().name),
- DefaultValueTypeMapping("1L", PsiTypes.longType().name),
- DefaultValueTypeMapping("true", PsiTypes.booleanType().name),
- DefaultValueTypeMapping("someString", "String"),
- DefaultValueTypeMapping("@null", "String"),
- DefaultValueTypeMapping("@resourceType/resourceName", PsiTypes.intType().name),
- DefaultValueTypeMapping("someCustomType", "String"), // custom type can't be recognized
- DefaultValueTypeMapping("someEnumType", "String") // custom type can't be recognized
- )
+ fun data() =
+ listOf(
+ DefaultValueTypeMapping("1", PsiTypes.intType().name),
+ DefaultValueTypeMapping("0x21", PsiTypes.intType().name),
+ DefaultValueTypeMapping("1f", PsiTypes.floatType().name),
+ DefaultValueTypeMapping("1L", PsiTypes.longType().name),
+ DefaultValueTypeMapping("true", PsiTypes.booleanType().name),
+ DefaultValueTypeMapping("someString", "String"),
+ DefaultValueTypeMapping("@null", "String"),
+ DefaultValueTypeMapping("@resourceType/resourceName", PsiTypes.intType().name),
+ DefaultValueTypeMapping("someCustomType", "String"), // custom type can't be recognized
+ DefaultValueTypeMapping("someEnumType", "String") // custom type can't be recognized
+ )
}
@Test
fun expectedBuilderConstructorsAndMethodsAreCreated_inferredType() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -79,7 +78,9 @@
android:defaultValue="${defaultValueTypeMapping.defaultValue}"/>
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -87,7 +88,9 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment {}")
// Classes can be found with context
- val builderClass = safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs.Builder", context) as LightArgsBuilderClass
+ val builderClass =
+ safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs.Builder", context)
+ as LightArgsBuilderClass
// We expect two constructors - a copy constructor (which is initialized with the parent args
builderClass.constructors.let { constructors ->
@@ -95,9 +98,7 @@
constructors[0].checkSignaturesAndReturnType(
name = "Builder",
returnType = PsiTypes.nullType().presentableText,
- parameters = listOf(
- Parameter("original", "FragmentArgs")
- )
+ parameters = listOf(Parameter("original", "FragmentArgs"))
)
constructors[1].checkSignaturesAndReturnType(
@@ -114,9 +115,7 @@
methods[0].checkSignaturesAndReturnType(
name = "setArg1",
returnType = "Builder",
- parameters = listOf(
- Parameter("arg1", defaultValueTypeMapping.inferredTypeStr)
- )
+ parameters = listOf(Parameter("arg1", defaultValueTypeMapping.inferredTypeStr))
)
methods[1].checkSignaturesAndReturnType(
@@ -125,21 +124,19 @@
isReturnTypeNullable = defaultValueTypeMapping.defaultValue == "@null"
)
- methods[2].checkSignaturesAndReturnType(
- name = "build",
- returnType = "FragmentArgs"
- )
+ methods[2].checkSignaturesAndReturnType(name = "build", returnType = "FragmentArgs")
}
}
@Test
fun expectedMethodsAreCreated_inferredType_fromSavedStateHandle() {
- // Use version [SafeArgsFeatureVersions.FROM_SAVED_STATE_HANDLE] and check the corresponding methods and field.
+ // Use version [SafeArgsFeatureVersions.FROM_SAVED_STATE_HANDLE] and check the corresponding
+ // methods and field.
safeArgsRule.addFakeNavigationDependency(SafeArgsFeatureVersions.FROM_SAVED_STATE_HANDLE)
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -155,7 +152,9 @@
android:defaultValue="${defaultValueTypeMapping.defaultValue}" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -163,7 +162,8 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment {}")
// Classes can be found with context
- val argClass = safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs", context) as LightArgsClass
+ val argClass =
+ safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs", context) as LightArgsClass
argClass.methods.let { methods ->
assertThat(methods.size).isEqualTo(4)
@@ -176,34 +176,28 @@
methods[1].checkSignaturesAndReturnType(
name = "fromBundle",
returnType = "FragmentArgs",
- parameters = listOf(
- Parameter("bundle", "Bundle")
- )
+ parameters = listOf(Parameter("bundle", "Bundle"))
)
methods[2].checkSignaturesAndReturnType(
name = "fromSavedStateHandle",
returnType = "FragmentArgs",
- parameters = listOf(
- Parameter("savedStateHandle", "SavedStateHandle")
- )
+ parameters = listOf(Parameter("savedStateHandle", "SavedStateHandle"))
)
- methods[3].checkSignaturesAndReturnType(
- name = "toBundle",
- returnType = "Bundle"
- )
+ methods[3].checkSignaturesAndReturnType(name = "toBundle", returnType = "Bundle")
}
}
@Test
fun expectedMethodsAreCreated_inferredType_toSavedStateHandle() {
- // Use version [SafeArgsFeatureVersions.TO_SAVED_STATE_HANDLE] and check the corresponding methods and field.
+ // Use version [SafeArgsFeatureVersions.TO_SAVED_STATE_HANDLE] and check the corresponding
+ // methods and field.
safeArgsRule.addFakeNavigationDependency(SafeArgsFeatureVersions.TO_SAVED_STATE_HANDLE)
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -219,7 +213,9 @@
android:defaultValue="${defaultValueTypeMapping.defaultValue}" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -227,7 +223,8 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment {}")
// Classes can be found with context
- val argClass = safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs", context) as LightArgsClass
+ val argClass =
+ safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs", context) as LightArgsClass
argClass.methods.let { methods ->
assertThat(methods.size).isEqualTo(5)
@@ -240,17 +237,13 @@
methods[1].checkSignaturesAndReturnType(
name = "fromBundle",
returnType = "FragmentArgs",
- parameters = listOf(
- Parameter("bundle", "Bundle")
- )
+ parameters = listOf(Parameter("bundle", "Bundle"))
)
methods[2].checkSignaturesAndReturnType(
name = "fromSavedStateHandle",
returnType = "FragmentArgs",
- parameters = listOf(
- Parameter("savedStateHandle", "SavedStateHandle")
- )
+ parameters = listOf(Parameter("savedStateHandle", "SavedStateHandle"))
)
methods[3].checkSignaturesAndReturnType(
@@ -258,12 +251,9 @@
returnType = "SavedStateHandle"
)
- methods[4].checkSignaturesAndReturnType(
- name = "toBundle",
- returnType = "Bundle"
- )
+ methods[4].checkSignaturesAndReturnType(name = "toBundle", returnType = "Bundle")
}
}
}
-data class DefaultValueTypeMapping(val defaultValue: String, val inferredTypeStr: String)
\ No newline at end of file
+data class DefaultValueTypeMapping(val defaultValue: String, val inferredTypeStr: String)
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsAndBuilderClassNullabilityAnnotationTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsAndBuilderClassNullabilityAnnotationTest.kt
index ba25640..e258d5d 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsAndBuilderClassNullabilityAnnotationTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsAndBuilderClassNullabilityAnnotationTest.kt
@@ -29,36 +29,40 @@
import org.junit.runners.Parameterized
/**
- * Tests that would normally go in [LightArgsBuilderClassTest] and [LightArgsClass] but are related to
- * a bunch of arguments types that we want to test with parametrization.
+ * Tests that would normally go in [LightArgsBuilderClassTest] and [LightArgsClass] but are related
+ * to a bunch of arguments types that we want to test with parametrization.
*/
@RunsInEdt
@RunWith(Parameterized::class)
class LightArgsAndBuilderClassNullabilityAnnotationTest(
private val typeNullabilityMapping: TypeNullabilityMapping
) {
- @get:Rule
- val safeArgsRule = SafeArgsRule()
+ @get:Rule val safeArgsRule = SafeArgsRule()
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
- fun data() = listOf(
- TypeNullabilityMapping("integer", PsiTypes.intType().name, false),
- TypeNullabilityMapping(PsiTypes.floatType().name, false),
- TypeNullabilityMapping(PsiTypes.longType().name, false),
- TypeNullabilityMapping(PsiTypes.booleanType().name, false),
- TypeNullabilityMapping("string", "String", true),
- TypeNullabilityMapping("reference", PsiTypes.intType().name, false),
- TypeNullabilityMapping("test.safeargs.MyCustomType", "MyCustomType", true) // e.g Parcelable, Serializable
- )
+ fun data() =
+ listOf(
+ TypeNullabilityMapping("integer", PsiTypes.intType().name, false),
+ TypeNullabilityMapping(PsiTypes.floatType().name, false),
+ TypeNullabilityMapping(PsiTypes.longType().name, false),
+ TypeNullabilityMapping(PsiTypes.booleanType().name, false),
+ TypeNullabilityMapping("string", "String", true),
+ TypeNullabilityMapping("reference", PsiTypes.intType().name, false),
+ TypeNullabilityMapping(
+ "test.safeargs.MyCustomType",
+ "MyCustomType",
+ true
+ ) // e.g Parcelable, Serializable
+ )
}
@Test
fun expectedBuilderConstructorsAndMethodsAreCreated_withNullabilityAnnotations() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -75,7 +79,9 @@
app:nullable="true"/>
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -83,7 +89,9 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment {}")
// Classes can be found with context
- val builderClass = safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs.Builder", context) as LightArgsBuilderClass
+ val builderClass =
+ safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs.Builder", context)
+ as LightArgsBuilderClass
// For the above xml, we expect a getter and setter for each <argument> tag as well as a final
// `build()` method that generates its parent args class.
@@ -93,9 +101,7 @@
methods[0].checkSignaturesAndReturnType(
name = "setArg1",
returnType = "Builder",
- parameters = listOf(
- Parameter("arg1", typeNullabilityMapping.after)
- )
+ parameters = listOf(Parameter("arg1", typeNullabilityMapping.after))
)
methods[1].checkSignaturesAndReturnType(
@@ -104,10 +110,7 @@
isReturnTypeNullable = typeNullabilityMapping.isReturnTypeNullable
)
- methods[2].checkSignaturesAndReturnType(
- name = "build",
- returnType = "FragmentArgs"
- )
+ methods[2].checkSignaturesAndReturnType(name = "build", returnType = "FragmentArgs")
}
}
@@ -115,7 +118,7 @@
fun expectedGettersAndFromBundleMethodsAreCreated_withNullabilityAnnotations() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -132,7 +135,9 @@
app:nullable="true"/>
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -140,7 +145,8 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment {}")
// Classes can be found with context
- val argClass = safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs", context) as LightArgsClass
+ val argClass =
+ safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs", context) as LightArgsClass
argClass.methods.let { methods ->
assertThat(methods.size).isEqualTo(3)
@@ -153,19 +159,21 @@
methods[1].checkSignaturesAndReturnType(
name = "fromBundle",
returnType = "FragmentArgs",
- parameters = listOf(
- Parameter("bundle", "Bundle")
- )
+ parameters = listOf(Parameter("bundle", "Bundle"))
)
- methods[2].checkSignaturesAndReturnType(
- name = "toBundle",
- returnType = "Bundle"
- )
+ methods[2].checkSignaturesAndReturnType(name = "toBundle", returnType = "Bundle")
}
}
}
-data class TypeNullabilityMapping(val before: String, val after: String, val isReturnTypeNullable: Boolean) {
- constructor(beforeAndAfter: String, nullability: Boolean) : this(beforeAndAfter, beforeAndAfter, nullability)
-}
\ No newline at end of file
+data class TypeNullabilityMapping(
+ val before: String,
+ val after: String,
+ val isReturnTypeNullable: Boolean
+) {
+ constructor(
+ beforeAndAfter: String,
+ nullability: Boolean
+ ) : this(beforeAndAfter, beforeAndAfter, nullability)
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsBuilderClassConstructorsAndMethodsTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsBuilderClassConstructorsAndMethodsTest.kt
index 44e9129..5597bac 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsBuilderClassConstructorsAndMethodsTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsBuilderClassConstructorsAndMethodsTest.kt
@@ -29,36 +29,36 @@
import org.junit.runners.Parameterized
/**
- * Tests that would normally go in [LightArgsBuilderClassTest] but are related to
- * a bunch of arguments types that we want to test with parametrization.
+ * Tests that would normally go in [LightArgsBuilderClassTest] but are related to a bunch of
+ * arguments types that we want to test with parametrization.
*/
@RunsInEdt
@RunWith(Parameterized::class)
class LightArgsBuilderClassConstructorsAndMethodsTest(private val typeMapping: TypeMapping) {
- @get:Rule
- val safeArgsRule = SafeArgsRule()
+ @get:Rule val safeArgsRule = SafeArgsRule()
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
- fun data() = listOf(
- TypeMapping("integer", PsiTypes.intType().name),
- TypeMapping(PsiTypes.floatType().name),
- TypeMapping(PsiTypes.longType().name),
- TypeMapping(PsiTypes.booleanType().name),
- TypeMapping("string", "String"),
- TypeMapping("reference", PsiTypes.intType().name),
- TypeMapping("test.safeargs.MyCustomType", "MyCustomType"), // e.g Parcelable, Serializable
- TypeMapping("test.safeargs.MyEnum", "MyEnum"),
- TypeMapping("test.safeargs.Outer\$Inner", "Inner"),
- )
+ fun data() =
+ listOf(
+ TypeMapping("integer", PsiTypes.intType().name),
+ TypeMapping(PsiTypes.floatType().name),
+ TypeMapping(PsiTypes.longType().name),
+ TypeMapping(PsiTypes.booleanType().name),
+ TypeMapping("string", "String"),
+ TypeMapping("reference", PsiTypes.intType().name),
+ TypeMapping("test.safeargs.MyCustomType", "MyCustomType"), // e.g Parcelable, Serializable
+ TypeMapping("test.safeargs.MyEnum", "MyEnum"),
+ TypeMapping("test.safeargs.Outer\$Inner", "Inner"),
+ )
}
@Test
fun expectedBuilderConstructorsAndMethodsAreCreated() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -86,7 +86,9 @@
android:defaultValue="@null"/>
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -94,7 +96,9 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment {}")
// Classes can be found with context
- val builderClass = safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs.Builder", context) as LightArgsBuilderClass
+ val builderClass =
+ safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs.Builder", context)
+ as LightArgsBuilderClass
// We expect two constructors - a copy constructor (which is initialized with the parent args
builderClass.constructors.let { constructors ->
@@ -102,18 +106,17 @@
constructors[0].checkSignaturesAndReturnType(
name = "Builder",
returnType = PsiTypes.nullType().presentableText,
- parameters = listOf(
- Parameter("original", "FragmentArgs")
- )
+ parameters = listOf(Parameter("original", "FragmentArgs"))
)
constructors[1].checkSignaturesAndReturnType(
name = "Builder",
returnType = PsiTypes.nullType().presentableText,
- parameters = listOf(
- Parameter("argOne", typeMapping.after),
- Parameter("argThree", "${typeMapping.after}[]")
- )
+ parameters =
+ listOf(
+ Parameter("argOne", typeMapping.after),
+ Parameter("argThree", "${typeMapping.after}[]")
+ )
)
}
@@ -125,35 +128,23 @@
methods[0].checkSignaturesAndReturnType(
name = "setArgOne",
returnType = "Builder",
- parameters = listOf(
- Parameter("argOne", typeMapping.after)
- )
+ parameters = listOf(Parameter("argOne", typeMapping.after))
)
- methods[1].checkSignaturesAndReturnType(
- name = "getArgOne",
- returnType = typeMapping.after
- )
+ methods[1].checkSignaturesAndReturnType(name = "getArgOne", returnType = typeMapping.after)
methods[2].checkSignaturesAndReturnType(
name = "setArgTwo",
returnType = "Builder",
- parameters = listOf(
- Parameter("argTwo", typeMapping.after)
- )
+ parameters = listOf(Parameter("argTwo", typeMapping.after))
)
- methods[3].checkSignaturesAndReturnType(
- name = "getArgTwo",
- returnType = typeMapping.after
- )
+ methods[3].checkSignaturesAndReturnType(name = "getArgTwo", returnType = typeMapping.after)
methods[4].checkSignaturesAndReturnType(
name = "setArgThree",
returnType = "Builder",
- parameters = listOf(
- Parameter("argThree", "${typeMapping.after}[]")
- )
+ parameters = listOf(Parameter("argThree", "${typeMapping.after}[]"))
)
methods[5].checkSignaturesAndReturnType(
@@ -164,9 +155,7 @@
methods[6].checkSignaturesAndReturnType(
name = "setArgFour",
returnType = "Builder",
- parameters = listOf(
- Parameter("argFour", "${typeMapping.after}[]")
- )
+ parameters = listOf(Parameter("argFour", "${typeMapping.after}[]"))
)
methods[7].checkSignaturesAndReturnType(
@@ -175,10 +164,7 @@
returnType = "${typeMapping.after}[]"
)
- methods[8].checkSignaturesAndReturnType(
- name = "build",
- returnType = "FragmentArgs"
- )
+ methods[8].checkSignaturesAndReturnType(name = "build", returnType = "FragmentArgs")
}
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsBuilderClassTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsBuilderClassTest.kt
index 880d054..b50c05e 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsBuilderClassTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsBuilderClassTest.kt
@@ -25,14 +25,13 @@
@RunsInEdt
class LightArgsBuilderClassTest {
- @get:Rule
- val safeArgsRule = SafeArgsRule()
+ @get:Rule val safeArgsRule = SafeArgsRule()
@Test
fun canFindArgsBuilderClasses() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -56,7 +55,9 @@
app:argType="string" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -64,9 +65,9 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment1 {}")
// Classes can be found with context
- assertThat(safeArgsRule.fixture.findClass("test.safeargs.Fragment1Args.Builder", context)).isInstanceOf(
- LightArgsBuilderClass::class.java)
- assertThat(safeArgsRule.fixture.findClass("test.safeargs.Fragment2Args.Builder", context)).isInstanceOf(
- LightArgsBuilderClass::class.java)
+ assertThat(safeArgsRule.fixture.findClass("test.safeargs.Fragment1Args.Builder", context))
+ .isInstanceOf(LightArgsBuilderClass::class.java)
+ assertThat(safeArgsRule.fixture.findClass("test.safeargs.Fragment2Args.Builder", context))
+ .isInstanceOf(LightArgsBuilderClass::class.java)
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsClassArgMethodsTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsClassArgMethodsTest.kt
index cae7f7d..af00ec5 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsClassArgMethodsTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsClassArgMethodsTest.kt
@@ -30,35 +30,35 @@
import org.junit.runners.Parameterized
/**
- * Tests that would normally go in [LightArgsClassTest] but are related to
- * a bunch of arguments types that we want to test with parametrization.
+ * Tests that would normally go in [LightArgsClassTest] but are related to a bunch of arguments
+ * types that we want to test with parametrization.
*/
@RunsInEdt
@RunWith(Parameterized::class)
class LightArgsClassArgMethodsTest(private val typeMapping: TypeMapping) {
- @get:Rule
- val safeArgsRule = SafeArgsRule()
+ @get:Rule val safeArgsRule = SafeArgsRule()
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
- fun data() = listOf(
- TypeMapping("integer", PsiTypes.intType().name),
- TypeMapping(PsiTypes.floatType().name),
- TypeMapping(PsiTypes.longType().name),
- TypeMapping(PsiTypes.booleanType().name),
- TypeMapping("string", "String"),
- TypeMapping("reference", PsiTypes.intType().name),
- TypeMapping("test.safeargs.MyCustomType", "MyCustomType"), // e.g Parcelable, Serializable
- TypeMapping("test.safeargs.MyEnum", "MyEnum")
- )
+ fun data() =
+ listOf(
+ TypeMapping("integer", PsiTypes.intType().name),
+ TypeMapping(PsiTypes.floatType().name),
+ TypeMapping(PsiTypes.longType().name),
+ TypeMapping(PsiTypes.booleanType().name),
+ TypeMapping("string", "String"),
+ TypeMapping("reference", PsiTypes.intType().name),
+ TypeMapping("test.safeargs.MyCustomType", "MyCustomType"), // e.g Parcelable, Serializable
+ TypeMapping("test.safeargs.MyEnum", "MyEnum")
+ )
}
@Test
fun expectedMethodsAreCreated() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -77,7 +77,9 @@
app:argType="${typeMapping.before}[]" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -85,7 +87,8 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment {}")
// Classes can be found with context
- val argClass = safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs", context) as LightArgsClass
+ val argClass =
+ safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs", context) as LightArgsClass
// Check supers
argClass.supers.asList().let {
@@ -96,10 +99,7 @@
// Check methods
argClass.methods.let { methods ->
assertThat(methods.size).isEqualTo(4)
- methods[0].checkSignaturesAndReturnType(
- name = "getArgOne",
- returnType = typeMapping.after
- )
+ methods[0].checkSignaturesAndReturnType(name = "getArgOne", returnType = typeMapping.after)
methods[1].checkSignaturesAndReturnType(
name = "getArgTwo",
@@ -109,15 +109,10 @@
methods[2].checkSignaturesAndReturnType(
name = "fromBundle",
returnType = "FragmentArgs",
- parameters = listOf(
- Parameter("bundle", "Bundle")
- )
+ parameters = listOf(Parameter("bundle", "Bundle"))
)
- methods[3].checkSignaturesAndReturnType(
- name = "toBundle",
- returnType = "Bundle"
- )
+ methods[3].checkSignaturesAndReturnType(name = "toBundle", returnType = "Bundle")
}
}
@@ -127,7 +122,7 @@
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -146,7 +141,9 @@
app:argType="${typeMapping.before}[]" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -154,7 +151,8 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment {}")
// Classes can be found with context
- val argClass = safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs", context) as LightArgsClass
+ val argClass =
+ safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs", context) as LightArgsClass
// Check supers
argClass.supers.asList().let {
@@ -165,10 +163,7 @@
// Check methods
argClass.methods.let { methods ->
assertThat(methods.size).isEqualTo(5)
- methods[0].checkSignaturesAndReturnType(
- name = "getArgOne",
- returnType = typeMapping.after
- )
+ methods[0].checkSignaturesAndReturnType(name = "getArgOne", returnType = typeMapping.after)
methods[1].checkSignaturesAndReturnType(
name = "getArgTwo",
@@ -178,23 +173,16 @@
methods[2].checkSignaturesAndReturnType(
name = "fromBundle",
returnType = "FragmentArgs",
- parameters = listOf(
- Parameter("bundle", "Bundle")
- )
+ parameters = listOf(Parameter("bundle", "Bundle"))
)
methods[3].checkSignaturesAndReturnType(
name = "fromSavedStateHandle",
returnType = "FragmentArgs",
- parameters = listOf(
- Parameter("savedStateHandle", "SavedStateHandle")
- )
+ parameters = listOf(Parameter("savedStateHandle", "SavedStateHandle"))
)
- methods[4].checkSignaturesAndReturnType(
- name = "toBundle",
- returnType = "Bundle"
- )
+ methods[4].checkSignaturesAndReturnType(name = "toBundle", returnType = "Bundle")
}
}
@@ -204,7 +192,7 @@
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -223,7 +211,9 @@
app:argType="${typeMapping.before}[]" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -231,7 +221,8 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment {}")
// Classes can be found with context
- val argClass = safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs", context) as LightArgsClass
+ val argClass =
+ safeArgsRule.fixture.findClass("test.safeargs.FragmentArgs", context) as LightArgsClass
// Check supers
argClass.supers.asList().let {
@@ -242,10 +233,7 @@
// Check methods
argClass.methods.let { methods ->
assertThat(methods.size).isEqualTo(6)
- methods[0].checkSignaturesAndReturnType(
- name = "getArgOne",
- returnType = typeMapping.after
- )
+ methods[0].checkSignaturesAndReturnType(name = "getArgOne", returnType = typeMapping.after)
methods[1].checkSignaturesAndReturnType(
name = "getArgTwo",
@@ -255,17 +243,13 @@
methods[2].checkSignaturesAndReturnType(
name = "fromBundle",
returnType = "FragmentArgs",
- parameters = listOf(
- Parameter("bundle", "Bundle")
- )
+ parameters = listOf(Parameter("bundle", "Bundle"))
)
methods[3].checkSignaturesAndReturnType(
name = "fromSavedStateHandle",
returnType = "FragmentArgs",
- parameters = listOf(
- Parameter("savedStateHandle", "SavedStateHandle")
- )
+ parameters = listOf(Parameter("savedStateHandle", "SavedStateHandle"))
)
methods[4].checkSignaturesAndReturnType(
@@ -273,10 +257,7 @@
returnType = "SavedStateHandle"
)
- methods[5].checkSignaturesAndReturnType(
- name = "toBundle",
- returnType = "Bundle"
- )
+ methods[5].checkSignaturesAndReturnType(name = "toBundle", returnType = "Bundle")
}
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsClassTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsClassTest.kt
index 2924eee..1b486ad 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsClassTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightArgsClassTest.kt
@@ -25,14 +25,13 @@
@RunsInEdt
class LightArgsClassTest {
- @get:Rule
- val safeArgsRule = SafeArgsRule()
+ @get:Rule val safeArgsRule = SafeArgsRule()
@Test
fun canFindArgsClasses() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -57,7 +56,9 @@
android:name="test.safeargs.Fragment2"
android:label="Fragment2" />
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -65,10 +66,12 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment1 {}")
// Classes can be found with context
- assertThat(safeArgsRule.fixture.findClass("test.safeargs.MainArgs", context)).isInstanceOf(LightArgsClass::class.java)
- assertThat(safeArgsRule.fixture.findClass("test.safeargs.Fragment1Args", context)).isInstanceOf(LightArgsClass::class.java)
+ assertThat(safeArgsRule.fixture.findClass("test.safeargs.MainArgs", context))
+ .isInstanceOf(LightArgsClass::class.java)
+ assertThat(safeArgsRule.fixture.findClass("test.safeargs.Fragment1Args", context))
+ .isInstanceOf(LightArgsClass::class.java)
// ... but not generated if no arguments
assertThat(safeArgsRule.fixture.findClass("test.safeargs.Fragment2Args", context)).isNull()
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightDirectionsClassTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightDirectionsClassTest.kt
index c0bc66c..a39b36f 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightDirectionsClassTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/LightDirectionsClassTest.kt
@@ -29,14 +29,13 @@
@RunsInEdt
class LightDirectionsClassTest {
- @get:Rule
- val safeArgsRule = SafeArgsRule()
+ @get:Rule val safeArgsRule = SafeArgsRule()
@Test
fun canFindDirectionsClasses() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -69,7 +68,9 @@
<!-- Sample action -->
<action android:id="@+id/action_without_destination" />
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -77,16 +78,19 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment1 {}")
// Classes can be found with context
- assertThat(safeArgsRule.fixture.findClass("test.safeargs.Fragment1Directions", context)).isInstanceOf(LightDirectionsClass::class.java)
- assertThat(safeArgsRule.fixture.findClass("test.safeargs.Fragment2Directions", context)).isInstanceOf(LightDirectionsClass::class.java)
- assertThat(safeArgsRule.fixture.findClass("test.safeargs.MainDirections", context)).isInstanceOf(LightDirectionsClass::class.java)
+ assertThat(safeArgsRule.fixture.findClass("test.safeargs.Fragment1Directions", context))
+ .isInstanceOf(LightDirectionsClass::class.java)
+ assertThat(safeArgsRule.fixture.findClass("test.safeargs.Fragment2Directions", context))
+ .isInstanceOf(LightDirectionsClass::class.java)
+ assertThat(safeArgsRule.fixture.findClass("test.safeargs.MainDirections", context))
+ .isInstanceOf(LightDirectionsClass::class.java)
}
@Test
fun actionMethodsGeneratedWithArgs() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -136,15 +140,21 @@
</navigation>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment1 {}")
- val mainDirections = safeArgsRule.fixture.findClass("test.safeargs.MainDirections", context) as LightDirectionsClass
- val fragment1directions = safeArgsRule.fixture.findClass("test.safeargs.Fragment1Directions", context) as LightDirectionsClass
+ val mainDirections =
+ safeArgsRule.fixture.findClass("test.safeargs.MainDirections", context)
+ as LightDirectionsClass
+ val fragment1directions =
+ safeArgsRule.fixture.findClass("test.safeargs.Fragment1Directions", context)
+ as LightDirectionsClass
mainDirections.findMethodsByName("actionToNested").first().let { action ->
(action as PsiMethod).checkSignaturesAndReturnType(
@@ -157,19 +167,15 @@
(action as PsiMethod).checkSignaturesAndReturnType(
name = "actionFragment1ToFragment2",
returnType = "ActionFragment1ToFragment2",
- parameters = listOf(
- Parameter("argOne", "String"),
- Parameter("argTwo", PsiTypes.floatType().name)
- )
+ parameters =
+ listOf(Parameter("argOne", "String"), Parameter("argTwo", PsiTypes.floatType().name))
)
}
fragment1directions.findMethodsByName("actionFragment1ToFragment3").first().let { action ->
(action as PsiMethod).checkSignaturesAndReturnType(
name = "actionFragment1ToFragment3",
returnType = "ActionFragment1ToFragment3",
- parameters = listOf(
- Parameter("arg", PsiTypes.intType().name)
- )
+ parameters = listOf(Parameter("arg", PsiTypes.intType().name))
)
}
}
@@ -178,7 +184,7 @@
fun testIncludedNavigationCase() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -195,7 +201,9 @@
app:destination="@id/included_graph" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -203,7 +211,9 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment1 {}")
// Class can be found with context
- val fragment2Directions = safeArgsRule.fixture.findClass("test.safeargs.Fragment2Directions", context) as LightDirectionsClass
+ val fragment2Directions =
+ safeArgsRule.fixture.findClass("test.safeargs.Fragment2Directions", context)
+ as LightDirectionsClass
// Check method
fragment2Directions.findMethodsByName("actionFragment2ToIncludedGraph").first().let { action ->
@@ -218,7 +228,7 @@
fun testGlobalActionCase() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -254,7 +264,9 @@
</navigation>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -262,7 +274,9 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment1 {}")
// Class can be found with context
- val fragment2Directions = safeArgsRule.fixture.findClass("test.safeargs.Fragment2Directions", context) as LightDirectionsClass
+ val fragment2Directions =
+ safeArgsRule.fixture.findClass("test.safeargs.Fragment2Directions", context)
+ as LightDirectionsClass
// Check methods of Fragment2Directions
fragment2Directions.methods.let { methods ->
assertThat(methods.size).isEqualTo(3)
@@ -282,8 +296,9 @@
)
}
- val innerNavigationDirections = safeArgsRule.fixture.findClass("test.safeargs.InnerNavigationDirections",
- context) as LightDirectionsClass
+ val innerNavigationDirections =
+ safeArgsRule.fixture.findClass("test.safeargs.InnerNavigationDirections", context)
+ as LightDirectionsClass
// Check methods of InnerNavigationDirections
innerNavigationDirections.methods.let { methods ->
assertThat(methods.size).isEqualTo(2)
@@ -298,7 +313,9 @@
)
}
- val mainDirections = safeArgsRule.fixture.findClass("test.safeargs.MainDirections", context) as LightDirectionsClass
+ val mainDirections =
+ safeArgsRule.fixture.findClass("test.safeargs.MainDirections", context)
+ as LightDirectionsClass
// Check methods of InnerNavigationDirections
mainDirections.methods.let { methods ->
assertThat(methods.size).isEqualTo(1)
@@ -313,7 +330,7 @@
fun testNoDestinationDefined() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -342,7 +359,9 @@
app:popUpTo="@id/fragment1" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -350,21 +369,24 @@
val context = safeArgsRule.fixture.addClass("package test.safeargs; public class Fragment1 {}")
// Classes can be found with context
- val fragment1DirectionsClass = safeArgsRule.fixture.findClass("test.safeargs.Fragment1Directions", context) as LightDirectionsClass
- val fragment2DirectionsClass = safeArgsRule.fixture.findClass("test.safeargs.Fragment2Directions", context) as LightDirectionsClass
+ val fragment1DirectionsClass =
+ safeArgsRule.fixture.findClass("test.safeargs.Fragment1Directions", context)
+ as LightDirectionsClass
+ val fragment2DirectionsClass =
+ safeArgsRule.fixture.findClass("test.safeargs.Fragment2Directions", context)
+ as LightDirectionsClass
- // Because we don't have destination defined, no arguments are supposed to be passed. This means no inner builder
- // action classes are created. NavDirections class(`ActionOnlyNavDirections` as the implementation under the hood)
+ // Because we don't have destination defined, no arguments are supposed to be passed. This means
+ // no inner builder
+ // action classes are created. NavDirections class(`ActionOnlyNavDirections` as the
+ // implementation under the hood)
// is being used here.
assertThat(fragment1DirectionsClass.innerClasses).isEmpty()
assertThat(fragment2DirectionsClass.innerClasses).isEmpty()
fragment1DirectionsClass.methods.let { methods ->
assertThat(methods.size).isEqualTo(1)
- methods[0].checkSignaturesAndReturnType(
- name = "actionToMain",
- returnType = "NavDirections"
- )
+ methods[0].checkSignaturesAndReturnType(name = "actionToMain", returnType = "NavDirections")
}
fragment2DirectionsClass.methods.let { methods ->
@@ -375,4 +397,4 @@
)
}
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/SafeArgNavigationTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/SafeArgNavigationTest.kt
index d144723..319e2ba 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/SafeArgNavigationTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/SafeArgNavigationTest.kt
@@ -27,14 +27,13 @@
@RunsInEdt
class SafeArgNavigationTest {
- @get:Rule
- val safeArgsRule = SafeArgsRule()
+ @get:Rule val safeArgsRule = SafeArgsRule()
@Test
fun canNavigateToXmlTagFromArgClass() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -58,7 +57,9 @@
app:argType="string" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -69,7 +70,8 @@
assertThat(editors.selectedFiles).isEmpty()
// check Fragment1Args class navigation
- val arg1Class = safeArgsRule.fixture.findClass("test.safeargs.Fragment1Args", context) as LightArgsClass
+ val arg1Class =
+ safeArgsRule.fixture.findClass("test.safeargs.Fragment1Args", context) as LightArgsClass
arg1Class.let {
it.navigate(true)
assertThat(editors.selectedFiles[0].name).isEqualTo("main.xml")
@@ -78,7 +80,8 @@
}
// check Fragment2Args class navigation
- val arg2Class = safeArgsRule.fixture.findClass("test.safeargs.Fragment2Args", context) as LightArgsClass
+ val arg2Class =
+ safeArgsRule.fixture.findClass("test.safeargs.Fragment2Args", context) as LightArgsClass
arg2Class.let {
it.navigate(true)
assertThat(editors.selectedFiles[0].name).isEqualTo("main.xml")
@@ -94,14 +97,16 @@
// check getter method
if (it.name == "getArgOne") {
- assertThat(it.navigationElement.text).isEqualTo(
- """
+ assertThat(it.navigationElement.text)
+ .isEqualTo(
+ """
<argument
android:name="arg_one"
app:argType="string" />
- """.trimIndent())
- }
- else {
+ """
+ .trimIndent()
+ )
+ } else {
assertThat(it.navigationElement.text).contains("id=\"@+id/fragment1\"")
}
}
@@ -114,14 +119,16 @@
// check getter method
if (it.name == "getArgTwo") {
- assertThat(it.navigationElement.text).isEqualTo(
- """
+ assertThat(it.navigationElement.text)
+ .isEqualTo(
+ """
<argument
android:name="arg_two"
app:argType="string" />
- """.trimIndent())
- }
- else {
+ """
+ .trimIndent()
+ )
+ } else {
assertThat(it.navigationElement.text).contains("id=\"@+id/fragment2\"")
}
}
@@ -131,7 +138,7 @@
fun canNavigateToXmlTagFromInnerArgsBuilderClass() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -151,7 +158,9 @@
android:name="test.safeargs.Fragment2"
android:label="Fragment2" />
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -162,7 +171,9 @@
assertThat(editors.selectedFiles).isEmpty()
// check class navigation
- val innerBuilderClass = safeArgsRule.fixture.findClass("test.safeargs.Fragment1Args.Builder", context) as LightArgsBuilderClass
+ val innerBuilderClass =
+ safeArgsRule.fixture.findClass("test.safeargs.Fragment1Args.Builder", context)
+ as LightArgsBuilderClass
innerBuilderClass.navigate(true)
assertThat(editors.selectedFiles[0].name).isEqualTo("main.xml")
assertThat(innerBuilderClass.navigationElement).isInstanceOf(XmlTag::class.java)
@@ -184,14 +195,16 @@
// check getter and setter method
if (it.name == "getArgOne" || it.name == "setArgOne") {
- assertThat(it.navigationElement.text).isEqualTo(
- """
+ assertThat(it.navigationElement.text)
+ .isEqualTo(
+ """
<argument
android:name="arg_one"
app:argType="string" />
- """.trimIndent())
- }
- else {
+ """
+ .trimIndent()
+ )
+ } else {
assertThat(it.navigationElement.text).contains("id=\"@+id/fragment1\"")
}
}
@@ -201,7 +214,7 @@
fun canNavigateToXmlTagFromDirectionClass() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -242,7 +255,9 @@
</navigation>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -253,7 +268,9 @@
assertThat(editors.selectedFiles).isEmpty()
// check mainDirections class navigation
- val mainDirections = safeArgsRule.fixture.findClass("test.safeargs.MainDirections", context) as LightDirectionsClass
+ val mainDirections =
+ safeArgsRule.fixture.findClass("test.safeargs.MainDirections", context)
+ as LightDirectionsClass
mainDirections.let {
it.navigate(true)
assertThat(editors.selectedFiles[0].name).isEqualTo("main.xml")
@@ -262,7 +279,9 @@
}
// check fragment1directions class navigation
- val fragment1directions = safeArgsRule.fixture.findClass("test.safeargs.Fragment1Directions", context) as LightDirectionsClass
+ val fragment1directions =
+ safeArgsRule.fixture.findClass("test.safeargs.Fragment1Directions", context)
+ as LightDirectionsClass
fragment1directions.let {
it.navigate(true)
assertThat(editors.selectedFiles[0].name).isEqualTo("main.xml")
@@ -275,12 +294,15 @@
it.navigate(true)
assertThat(editors.selectedFiles[0].name).isEqualTo("main.xml")
assertThat(it.navigationElement).isInstanceOf(XmlTag::class.java)
- assertThat(it.navigationElement.text).isEqualTo(
- """
+ assertThat(it.navigationElement.text)
+ .isEqualTo(
+ """
<action
android:id="@+id/action_to_nested"
app:destination="@id/nested" />
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
// check methods navigation of fragment1directions
@@ -290,20 +312,26 @@
assertThat(it.navigationElement).isInstanceOf(XmlTag::class.java)
when (it.name) {
"actionFragment1ToFragment2" -> {
- assertThat(it.navigationElement.text).isEqualTo(
- """
+ assertThat(it.navigationElement.text)
+ .isEqualTo(
+ """
<action
android:id="@+id/action_fragment1_to_fragment2"
app:destination="@id/fragment2" />
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
"actionFragment1ToFragment3" -> {
- assertThat(it.navigationElement.text).isEqualTo(
- """
+ assertThat(it.navigationElement.text)
+ .isEqualTo(
+ """
<action
android:id="@+id/action_fragment1_to_fragment3"
app:destination="@id/fragment3" />
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
}
@@ -313,7 +341,7 @@
fun canNavigateToXmlTagFromInnerActionsBuilderClass() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -349,7 +377,9 @@
app:argType="string" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
@@ -360,14 +390,18 @@
assertThat(editors.selectedFiles).isEmpty()
// check actionBuilderClass navigation
- val actionBuilderClass = safeArgsRule.fixture
- .findClass("test.safeargs.Fragment1Directions.ActionFragment1ToFragment2", context) as LightActionBuilderClass
+ val actionBuilderClass =
+ safeArgsRule.fixture.findClass(
+ "test.safeargs.Fragment1Directions.ActionFragment1ToFragment2",
+ context
+ ) as LightActionBuilderClass
actionBuilderClass.let {
it.navigate(true)
assertThat(editors.selectedFiles[0].name).isEqualTo("main.xml")
assertThat(it.navigationElement).isInstanceOf(XmlTag::class.java)
- assertThat(it.navigationElement.text).isEqualTo(
- """
+ assertThat(it.navigationElement.text)
+ .isEqualTo(
+ """
<action
android:id="@+id/action_fragment1_to_fragment2"
app:destination="@id/fragment2" >
@@ -379,7 +413,9 @@
android:name="arg_in_action"
app:argType="string" />
</action>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
// check methods navigation of actionBuilderClass
@@ -390,33 +426,45 @@
// check getters and setters
when (it.name) {
- "getArg", "setArg" -> {
- assertThat(it.navigationElement.text).isEqualTo(
- """
+ "getArg",
+ "setArg" -> {
+ assertThat(it.navigationElement.text)
+ .isEqualTo(
+ """
<argument
android:name="arg"
app:argType="string"
android:defaultValue="defaultString" />
- """.trimIndent())
- }
- "getArgInAction", "setArgInAction" -> {
- assertThat(it.navigationElement.text).isEqualTo(
"""
+ .trimIndent()
+ )
+ }
+ "getArgInAction",
+ "setArgInAction" -> {
+ assertThat(it.navigationElement.text)
+ .isEqualTo(
+ """
<argument
android:name="arg_in_action"
app:argType="string" />
- """.trimIndent())
- }
- "getArgInDestination", "setArgInDestination" -> {
- assertThat(it.navigationElement.text).isEqualTo(
"""
+ .trimIndent()
+ )
+ }
+ "getArgInDestination",
+ "setArgInDestination" -> {
+ assertThat(it.navigationElement.text)
+ .isEqualTo(
+ """
<argument
android:name="arg_in_destination"
app:argType="string" />
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
else -> throw IllegalAccessError("This should never happen!")
}
}
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/TypeMapping.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/TypeMapping.kt
index d7e2c92..f25b17c 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/TypeMapping.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/java/TypeMapping.kt
@@ -20,4 +20,4 @@
*/
data class TypeMapping(val before: String, val after: String) {
constructor(beforeAndAfter: String) : this(beforeAndAfter, beforeAndAfter)
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/xml/SafeArgsXmlTagTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/xml/SafeArgsXmlTagTest.kt
index e97ba54..020292e 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/xml/SafeArgsXmlTagTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/psi/xml/SafeArgsXmlTagTest.kt
@@ -25,32 +25,34 @@
import org.junit.Test
class SafeArgsXmlTagTest {
- @get:Rule
- val applicationRule = ApplicationRule()
+ @get:Rule val applicationRule = ApplicationRule()
@Test
fun checkValueEquivalency() {
- val originalTag = object: XmlTagImpl() {
- override fun isPhysical() = true
- }
+ val originalTag =
+ object : XmlTagImpl() {
+ override fun isPhysical() = true
+ }
- val tagA = SafeArgsXmlTag(
- xmlTag = originalTag,
- icon = IconManager.getInstance().getPlatformIcon(PlatformIcons.Class),
- name = "Foo",
- containerIdentifier = "package1"
- )
+ val tagA =
+ SafeArgsXmlTag(
+ xmlTag = originalTag,
+ icon = IconManager.getInstance().getPlatformIcon(PlatformIcons.Class),
+ name = "Foo",
+ containerIdentifier = "package1"
+ )
- val tagB = SafeArgsXmlTag(
- xmlTag = originalTag,
- icon = IconManager.getInstance().getPlatformIcon(PlatformIcons.Class),
- name = "Foo",
- containerIdentifier = "package1"
- )
+ val tagB =
+ SafeArgsXmlTag(
+ xmlTag = originalTag,
+ icon = IconManager.getInstance().getPlatformIcon(PlatformIcons.Class),
+ name = "Foo",
+ containerIdentifier = "package1"
+ )
ApplicationManager.getApplication().runReadAction {
assertThat(tagA.isEquivalentTo(tagB)).isTrue()
assertThat(tagA).isEqualTo(tagB)
}
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/tracker/TestSafeArgsTracker.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/tracker/TestSafeArgsTracker.kt
index 6ffd676..e3207f8 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/tracker/TestSafeArgsTracker.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/tracker/TestSafeArgsTracker.kt
@@ -23,4 +23,4 @@
// test to assert results immediately rather than having to wait on multi-threaded signalling.
block()
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/tracker/gradle/SafeArgsTrackerTest.kt b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/tracker/gradle/SafeArgsTrackerTest.kt
index 9b7059a..bd2b633 100644
--- a/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/tracker/gradle/SafeArgsTrackerTest.kt
+++ b/nav/safeargs/tests/common/src/com/android/tools/idea/nav/safeargs/tracker/gradle/SafeArgsTrackerTest.kt
@@ -32,9 +32,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-/**
- * Verify that we can sync a Gradle project that applies the safe args plugin.
- */
+/** Verify that we can sync a Gradle project that applies the safe args plugin. */
@RunsInEdt
@RunWith(Parameterized::class)
class SafeArgsTrackerTest(private val params: TestParams) {
@@ -48,45 +46,47 @@
@Suppress("unused") // Accessed via reflection by JUnit
@JvmStatic
@get:Parameterized.Parameters(name = "{0}")
- val parameters = listOf(
- TestParams(TestDataPaths.MULTI_MODULE_PROJECT) { event ->
- event!!
- assertThat(event.eventContext).isEqualTo(NavSafeArgsEvent.EventContext.SYNC_EVENT_CONTEXT)
- event.projectMetadata.let { projectMetadata ->
- assertThat(projectMetadata.moduleCount).isEqualTo(3)
- assertThat(projectMetadata.javaPluginCount).isEqualTo(1)
- assertThat(projectMetadata.kotlinPluginCount).isEqualTo(1)
- }
- },
- TestParams(TestDataPaths.PROJECT_USING_JAVA_PLUGIN) { event ->
- event!!
- assertThat(event.eventContext).isEqualTo(NavSafeArgsEvent.EventContext.SYNC_EVENT_CONTEXT)
- event.projectMetadata.let { projectMetadata ->
- assertThat(projectMetadata.moduleCount).isEqualTo(1)
- assertThat(projectMetadata.javaPluginCount).isEqualTo(1)
- assertThat(projectMetadata.kotlinPluginCount).isEqualTo(0)
- }
- },
- TestParams(TestDataPaths.PROJECT_USING_KOTLIN_PLUGIN) { event ->
- event!!
- assertThat(event.eventContext).isEqualTo(NavSafeArgsEvent.EventContext.SYNC_EVENT_CONTEXT)
- event.projectMetadata.let { projectMetadata ->
- assertThat(projectMetadata.moduleCount).isEqualTo(1)
- assertThat(projectMetadata.javaPluginCount).isEqualTo(0)
- assertThat(projectMetadata.kotlinPluginCount).isEqualTo(1)
- }
- },
- // Projects not using safe args don't generate safe args metrics
- TestParams(TestDataPaths.PROJECT_WITHOUT_SAFE_ARGS) { event -> assertThat(event).isNull() })
+ val parameters =
+ listOf(
+ TestParams(TestDataPaths.MULTI_MODULE_PROJECT) { event ->
+ event!!
+ assertThat(event.eventContext).isEqualTo(NavSafeArgsEvent.EventContext.SYNC_EVENT_CONTEXT)
+ event.projectMetadata.let { projectMetadata ->
+ assertThat(projectMetadata.moduleCount).isEqualTo(3)
+ assertThat(projectMetadata.javaPluginCount).isEqualTo(1)
+ assertThat(projectMetadata.kotlinPluginCount).isEqualTo(1)
+ }
+ },
+ TestParams(TestDataPaths.PROJECT_USING_JAVA_PLUGIN) { event ->
+ event!!
+ assertThat(event.eventContext).isEqualTo(NavSafeArgsEvent.EventContext.SYNC_EVENT_CONTEXT)
+ event.projectMetadata.let { projectMetadata ->
+ assertThat(projectMetadata.moduleCount).isEqualTo(1)
+ assertThat(projectMetadata.javaPluginCount).isEqualTo(1)
+ assertThat(projectMetadata.kotlinPluginCount).isEqualTo(0)
+ }
+ },
+ TestParams(TestDataPaths.PROJECT_USING_KOTLIN_PLUGIN) { event ->
+ event!!
+ assertThat(event.eventContext).isEqualTo(NavSafeArgsEvent.EventContext.SYNC_EVENT_CONTEXT)
+ event.projectMetadata.let { projectMetadata ->
+ assertThat(projectMetadata.moduleCount).isEqualTo(1)
+ assertThat(projectMetadata.javaPluginCount).isEqualTo(0)
+ assertThat(projectMetadata.kotlinPluginCount).isEqualTo(1)
+ }
+ },
+ // Projects not using safe args don't generate safe args metrics
+ TestParams(TestDataPaths.PROJECT_WITHOUT_SAFE_ARGS) { event -> assertThat(event).isNull() }
+ )
}
private val projectRule = AndroidGradleProjectRule()
// The tests need to run on the EDT thread but we must initialize the project rule off of it
- @get:Rule
- val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
+ @get:Rule val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
- private val fixture get() = projectRule.fixture as JavaCodeInsightTestFixture
+ private val fixture
+ get() = projectRule.fixture as JavaCodeInsightTestFixture
@Test
fun verifyExpectedAnalytics() {
@@ -98,17 +98,17 @@
projectRule.load(params.project)
projectRule.requestSyncAndWait()
- val safeArgsEvent = tracker.usages
- .map { it.studioEvent }
- .filter { it.kind == AndroidStudioEvent.EventKind.NAV_SAFE_ARGS_EVENT }
- .map { it.navSafeArgsEvent }
- .lastOrNull()
+ val safeArgsEvent =
+ tracker.usages
+ .map { it.studioEvent }
+ .filter { it.kind == AndroidStudioEvent.EventKind.NAV_SAFE_ARGS_EVENT }
+ .map { it.navSafeArgsEvent }
+ .lastOrNull()
params.verify(safeArgsEvent)
- }
- finally {
+ } finally {
tracker.close()
UsageTracker.cleanAfterTesting()
}
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/ArgsClassKtDescriptorsTest.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/ArgsClassKtDescriptorsTest.kt
index a5bba3b..70bfec7 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/ArgsClassKtDescriptorsTest.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/ArgsClassKtDescriptorsTest.kt
@@ -34,14 +34,13 @@
@RunsInEdt
class ArgsClassKtDescriptorsTest {
- @get:Rule
- val safeArgsRule = SafeArgsRule(SafeArgsMode.KOTLIN)
+ @get:Rule val safeArgsRule = SafeArgsRule(SafeArgsMode.KOTLIN)
@Test
fun checkContributorsOfArgsClass() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -63,61 +62,67 @@
app:argType=".Fragment1" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
- val safeArgProviderExtension = PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
- it is SafeArgsKtPackageProviderExtension
- }
+ val safeArgProviderExtension =
+ PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
+ it is SafeArgsKtPackageProviderExtension
+ }
val traceMock: BindingTrace = MockitoKt.mock()
val moduleSourceInfo = safeArgsRule.module.productionSourceInfo
val moduleDescriptor = safeArgsRule.module.toDescriptor()
- val fragmentProvider = safeArgProviderExtension.getPackageFragmentProvider(
- project = safeArgsRule.project,
- module = moduleDescriptor!!,
- storageManager = LockBasedStorageManager.NO_LOCKS,
- trace = traceMock,
- moduleInfo = moduleSourceInfo,
- lookupTracker = LookupTracker.DO_NOTHING
- ) as SafeArgsSyntheticPackageProvider
+ val fragmentProvider =
+ safeArgProviderExtension.getPackageFragmentProvider(
+ project = safeArgsRule.project,
+ module = moduleDescriptor!!,
+ storageManager = LockBasedStorageManager.NO_LOCKS,
+ trace = traceMock,
+ moduleInfo = moduleSourceInfo,
+ lookupTracker = LookupTracker.DO_NOTHING
+ ) as SafeArgsSyntheticPackageProvider
- val argsClassMetadata = fragmentProvider
- .getPackageFragments(FqName("test.safeargs"))
- .flatMap { it.getMemberScope().classesInScope() }
- .single()
+ val argsClassMetadata =
+ fragmentProvider
+ .getPackageFragments(FqName("test.safeargs"))
+ .flatMap { it.getMemberScope().classesInScope() }
+ .single()
assertThat(argsClassMetadata.fqcn).isEqualTo("test.safeargs.Fragment1Args")
- assertThat(argsClassMetadata.constructors.map { it.toString() }).containsExactly(
- "Fragment1Args(argOne: kotlin.String, argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1)"
- )
+ assertThat(argsClassMetadata.constructors.map { it.toString() })
+ .containsExactly(
+ "Fragment1Args(argOne: kotlin.String, argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1)"
+ )
- assertThat(argsClassMetadata.classifiers.map { it.toString() }).containsExactly(
- "test.safeargs.Fragment1Args.Companion"
- )
+ assertThat(argsClassMetadata.classifiers.map { it.toString() })
+ .containsExactly("test.safeargs.Fragment1Args.Companion")
- assertThat(argsClassMetadata.companionObject!!.functions.map { it.toString() }).containsExactly(
- "fromBundle(bundle: android.os.Bundle): test.safeargs.Fragment1Args"
- )
+ assertThat(argsClassMetadata.companionObject!!.functions.map { it.toString() })
+ .containsExactly("fromBundle(bundle: android.os.Bundle): test.safeargs.Fragment1Args")
- assertThat(argsClassMetadata.properties.map { it.toString() }).containsExactly(
- "val argOne: kotlin.String",
- "val argTwo: kotlin.IntArray",
- "val argThree: test.safeargs.Fragment1"
- )
+ assertThat(argsClassMetadata.properties.map { it.toString() })
+ .containsExactly(
+ "val argOne: kotlin.String",
+ "val argTwo: kotlin.IntArray",
+ "val argThree: test.safeargs.Fragment1"
+ )
- assertThat(argsClassMetadata.functions.map { it.toString() }).containsExactly(
- // generated functions of data class
- "component1(): kotlin.String",
- "component2(): kotlin.IntArray",
- "component3(): test.safeargs.Fragment1",
- "copy(argOne: kotlin.String, argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1): test.safeargs.Fragment1Args",
- // normal functions
- "toBundle(): android.os.Bundle"
- )
+ assertThat(argsClassMetadata.functions.map { it.toString() })
+ .containsExactly(
+ // generated functions of data class
+ "component1(): kotlin.String",
+ "component2(): kotlin.IntArray",
+ "component3(): test.safeargs.Fragment1",
+ "copy(argOne: kotlin.String, argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1): test.safeargs.Fragment1Args",
+ // normal functions
+ "toBundle(): android.os.Bundle"
+ )
}
@Test
@@ -126,7 +131,7 @@
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -149,63 +154,71 @@
app:argType=".Fragment1" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
- val safeArgProviderExtension = PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
- it is SafeArgsKtPackageProviderExtension
- }
+ val safeArgProviderExtension =
+ PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
+ it is SafeArgsKtPackageProviderExtension
+ }
val traceMock: BindingTrace = MockitoKt.mock()
val moduleSourceInfo = safeArgsRule.module.productionSourceInfo
val moduleDescriptor = safeArgsRule.module.toDescriptor()
- val fragmentProvider = safeArgProviderExtension.getPackageFragmentProvider(
- project = safeArgsRule.project,
- module = moduleDescriptor!!,
- storageManager = LockBasedStorageManager.NO_LOCKS,
- trace = traceMock,
- moduleInfo = moduleSourceInfo,
- lookupTracker = LookupTracker.DO_NOTHING
- ) as SafeArgsSyntheticPackageProvider
+ val fragmentProvider =
+ safeArgProviderExtension.getPackageFragmentProvider(
+ project = safeArgsRule.project,
+ module = moduleDescriptor!!,
+ storageManager = LockBasedStorageManager.NO_LOCKS,
+ trace = traceMock,
+ moduleInfo = moduleSourceInfo,
+ lookupTracker = LookupTracker.DO_NOTHING
+ ) as SafeArgsSyntheticPackageProvider
- val argsClassMetadata = fragmentProvider
- .getPackageFragments(FqName("test.safeargs"))
- .flatMap { it.getMemberScope().classesInScope() }
- .single()
+ val argsClassMetadata =
+ fragmentProvider
+ .getPackageFragments(FqName("test.safeargs"))
+ .flatMap { it.getMemberScope().classesInScope() }
+ .single()
assertThat(argsClassMetadata.fqcn).isEqualTo("test.safeargs.Fragment1Args")
- assertThat(argsClassMetadata.constructors.map { it.toString() }).containsExactly(
- "Fragment1Args(argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1, argOne: kotlin.String)"
- )
+ assertThat(argsClassMetadata.constructors.map { it.toString() })
+ .containsExactly(
+ "Fragment1Args(argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1, argOne: kotlin.String)"
+ )
- assertThat(argsClassMetadata.classifiers.map { it.toString() }).containsExactly(
- "test.safeargs.Fragment1Args.Companion"
- )
+ assertThat(argsClassMetadata.classifiers.map { it.toString() })
+ .containsExactly("test.safeargs.Fragment1Args.Companion")
- assertThat(argsClassMetadata.companionObject!!.functions.map { it.toString() }).containsExactly(
- "fromBundle(bundle: android.os.Bundle): test.safeargs.Fragment1Args",
- "fromSavedStateHandle(savedStateHandle: androidx.lifecycle.SavedStateHandle): test.safeargs.Fragment1Args",
- )
+ assertThat(argsClassMetadata.companionObject!!.functions.map { it.toString() })
+ .containsExactly(
+ "fromBundle(bundle: android.os.Bundle): test.safeargs.Fragment1Args",
+ "fromSavedStateHandle(savedStateHandle: androidx.lifecycle.SavedStateHandle): test.safeargs.Fragment1Args",
+ )
- assertThat(argsClassMetadata.properties.map { it.toString() }).containsExactly(
- "val argOne: kotlin.String",
- "val argTwo: kotlin.IntArray",
- "val argThree: test.safeargs.Fragment1"
- )
+ assertThat(argsClassMetadata.properties.map { it.toString() })
+ .containsExactly(
+ "val argOne: kotlin.String",
+ "val argTwo: kotlin.IntArray",
+ "val argThree: test.safeargs.Fragment1"
+ )
- assertThat(argsClassMetadata.functions.map { it.toString() }).containsExactly(
- // generated functions of data class
- "component1(): kotlin.String",
- "component2(): kotlin.IntArray",
- "component3(): test.safeargs.Fragment1",
- "copy(argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1, argOne: kotlin.String): test.safeargs.Fragment1Args",
- // normal functions
- "toSavedStateHandle(): androidx.lifecycle.SavedStateHandle",
- "toBundle(): android.os.Bundle"
- )
+ assertThat(argsClassMetadata.functions.map { it.toString() })
+ .containsExactly(
+ // generated functions of data class
+ "component1(): kotlin.String",
+ "component2(): kotlin.IntArray",
+ "component3(): test.safeargs.Fragment1",
+ "copy(argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1, argOne: kotlin.String): test.safeargs.Fragment1Args",
+ // normal functions
+ "toSavedStateHandle(): androidx.lifecycle.SavedStateHandle",
+ "toBundle(): android.os.Bundle"
+ )
}
@Test
@@ -214,7 +227,7 @@
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -236,63 +249,71 @@
app:argType=".Fragment1" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
- val safeArgProviderExtension = PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
- it is SafeArgsKtPackageProviderExtension
- }
+ val safeArgProviderExtension =
+ PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
+ it is SafeArgsKtPackageProviderExtension
+ }
val traceMock: BindingTrace = MockitoKt.mock()
val moduleSourceInfo = safeArgsRule.module.productionSourceInfo
val moduleDescriptor = safeArgsRule.module.toDescriptor()
- val fragmentProvider = safeArgProviderExtension.getPackageFragmentProvider(
- project = safeArgsRule.project,
- module = moduleDescriptor!!,
- storageManager = LockBasedStorageManager.NO_LOCKS,
- trace = traceMock,
- moduleInfo = moduleSourceInfo,
- lookupTracker = LookupTracker.DO_NOTHING
- ) as SafeArgsSyntheticPackageProvider
+ val fragmentProvider =
+ safeArgProviderExtension.getPackageFragmentProvider(
+ project = safeArgsRule.project,
+ module = moduleDescriptor!!,
+ storageManager = LockBasedStorageManager.NO_LOCKS,
+ trace = traceMock,
+ moduleInfo = moduleSourceInfo,
+ lookupTracker = LookupTracker.DO_NOTHING
+ ) as SafeArgsSyntheticPackageProvider
- val argsClassMetadata = fragmentProvider
- .getPackageFragments(FqName("test.safeargs"))
- .flatMap { it.getMemberScope().classesInScope() }
- .single()
+ val argsClassMetadata =
+ fragmentProvider
+ .getPackageFragments(FqName("test.safeargs"))
+ .flatMap { it.getMemberScope().classesInScope() }
+ .single()
assertThat(argsClassMetadata.fqcn).isEqualTo("test.safeargs.Fragment1Args")
- assertThat(argsClassMetadata.constructors.map { it.toString() }).containsExactly(
- "Fragment1Args(argOne: kotlin.String, argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1)"
- )
+ assertThat(argsClassMetadata.constructors.map { it.toString() })
+ .containsExactly(
+ "Fragment1Args(argOne: kotlin.String, argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1)"
+ )
- assertThat(argsClassMetadata.classifiers.map { it.toString() }).containsExactly(
- "test.safeargs.Fragment1Args.Companion"
- )
+ assertThat(argsClassMetadata.classifiers.map { it.toString() })
+ .containsExactly("test.safeargs.Fragment1Args.Companion")
- assertThat(argsClassMetadata.companionObject!!.functions.map { it.toString() }).containsExactly(
- "fromBundle(bundle: android.os.Bundle): test.safeargs.Fragment1Args",
- "fromSavedStateHandle(savedStateHandle: androidx.lifecycle.SavedStateHandle): test.safeargs.Fragment1Args",
- )
+ assertThat(argsClassMetadata.companionObject!!.functions.map { it.toString() })
+ .containsExactly(
+ "fromBundle(bundle: android.os.Bundle): test.safeargs.Fragment1Args",
+ "fromSavedStateHandle(savedStateHandle: androidx.lifecycle.SavedStateHandle): test.safeargs.Fragment1Args",
+ )
- assertThat(argsClassMetadata.properties.map { it.toString() }).containsExactly(
- "val argOne: kotlin.String",
- "val argTwo: kotlin.IntArray",
- "val argThree: test.safeargs.Fragment1"
- )
+ assertThat(argsClassMetadata.properties.map { it.toString() })
+ .containsExactly(
+ "val argOne: kotlin.String",
+ "val argTwo: kotlin.IntArray",
+ "val argThree: test.safeargs.Fragment1"
+ )
- assertThat(argsClassMetadata.functions.map { it.toString() }).containsExactly(
- // generated functions of data class
- "component1(): kotlin.String",
- "component2(): kotlin.IntArray",
- "component3(): test.safeargs.Fragment1",
- "copy(argOne: kotlin.String, argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1): test.safeargs.Fragment1Args",
- // normal functions
- "toSavedStateHandle(): androidx.lifecycle.SavedStateHandle",
- "toBundle(): android.os.Bundle"
- )
+ assertThat(argsClassMetadata.functions.map { it.toString() })
+ .containsExactly(
+ // generated functions of data class
+ "component1(): kotlin.String",
+ "component2(): kotlin.IntArray",
+ "component3(): test.safeargs.Fragment1",
+ "copy(argOne: kotlin.String, argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1): test.safeargs.Fragment1Args",
+ // normal functions
+ "toSavedStateHandle(): androidx.lifecycle.SavedStateHandle",
+ "toBundle(): android.os.Bundle"
+ )
}
@Test
@@ -301,7 +322,7 @@
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -323,61 +344,69 @@
app:argType=".Fragment1" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
- val safeArgProviderExtension = PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
- it is SafeArgsKtPackageProviderExtension
- }
+ val safeArgProviderExtension =
+ PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
+ it is SafeArgsKtPackageProviderExtension
+ }
val traceMock: BindingTrace = MockitoKt.mock()
val moduleSourceInfo = safeArgsRule.module.productionSourceInfo
val moduleDescriptor = safeArgsRule.module.toDescriptor()
- val fragmentProvider = safeArgProviderExtension.getPackageFragmentProvider(
- project = safeArgsRule.project,
- module = moduleDescriptor!!,
- storageManager = LockBasedStorageManager.NO_LOCKS,
- trace = traceMock,
- moduleInfo = moduleSourceInfo,
- lookupTracker = LookupTracker.DO_NOTHING
- ) as SafeArgsSyntheticPackageProvider
+ val fragmentProvider =
+ safeArgProviderExtension.getPackageFragmentProvider(
+ project = safeArgsRule.project,
+ module = moduleDescriptor!!,
+ storageManager = LockBasedStorageManager.NO_LOCKS,
+ trace = traceMock,
+ moduleInfo = moduleSourceInfo,
+ lookupTracker = LookupTracker.DO_NOTHING
+ ) as SafeArgsSyntheticPackageProvider
- val argsClassMetadata = fragmentProvider
- .getPackageFragments(FqName("test.safeargs"))
- .flatMap { it.getMemberScope().classesInScope() }
- .single()
+ val argsClassMetadata =
+ fragmentProvider
+ .getPackageFragments(FqName("test.safeargs"))
+ .flatMap { it.getMemberScope().classesInScope() }
+ .single()
assertThat(argsClassMetadata.fqcn).isEqualTo("test.safeargs.Fragment1Args")
- assertThat(argsClassMetadata.constructors.map { it.toString() }).containsExactly(
- "Fragment1Args(argOne: kotlin.String, argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1)"
- )
+ assertThat(argsClassMetadata.constructors.map { it.toString() })
+ .containsExactly(
+ "Fragment1Args(argOne: kotlin.String, argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1)"
+ )
- assertThat(argsClassMetadata.classifiers.map { it.toString() }).containsExactly(
- "test.safeargs.Fragment1Args.Companion"
- )
+ assertThat(argsClassMetadata.classifiers.map { it.toString() })
+ .containsExactly("test.safeargs.Fragment1Args.Companion")
- assertThat(argsClassMetadata.companionObject!!.functions.map { it.toString() }).containsExactly(
- "fromBundle(bundle: android.os.Bundle): test.safeargs.Fragment1Args",
- "fromSavedStateHandle(savedStateHandle: androidx.lifecycle.SavedStateHandle): test.safeargs.Fragment1Args",
- )
+ assertThat(argsClassMetadata.companionObject!!.functions.map { it.toString() })
+ .containsExactly(
+ "fromBundle(bundle: android.os.Bundle): test.safeargs.Fragment1Args",
+ "fromSavedStateHandle(savedStateHandle: androidx.lifecycle.SavedStateHandle): test.safeargs.Fragment1Args",
+ )
- assertThat(argsClassMetadata.properties.map { it.toString() }).containsExactly(
- "val argOne: kotlin.String",
- "val argTwo: kotlin.IntArray",
- "val argThree: test.safeargs.Fragment1"
- )
+ assertThat(argsClassMetadata.properties.map { it.toString() })
+ .containsExactly(
+ "val argOne: kotlin.String",
+ "val argTwo: kotlin.IntArray",
+ "val argThree: test.safeargs.Fragment1"
+ )
- assertThat(argsClassMetadata.functions.map { it.toString() }).containsExactly(
- // generated functions of data class
- "component1(): kotlin.String",
- "component2(): kotlin.IntArray",
- "component3(): test.safeargs.Fragment1",
- "copy(argOne: kotlin.String, argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1): test.safeargs.Fragment1Args",
- // normal functions
- "toBundle(): android.os.Bundle"
- )
+ assertThat(argsClassMetadata.functions.map { it.toString() })
+ .containsExactly(
+ // generated functions of data class
+ "component1(): kotlin.String",
+ "component2(): kotlin.IntArray",
+ "component3(): test.safeargs.Fragment1",
+ "copy(argOne: kotlin.String, argTwo: kotlin.IntArray, argThree: test.safeargs.Fragment1): test.safeargs.Fragment1Args",
+ // normal functions
+ "toBundle(): android.os.Bundle"
+ )
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/ClassMetadata.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/ClassMetadata.kt
index a5cbcd3..279ec71 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/ClassMetadata.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/ClassMetadata.kt
@@ -55,10 +55,7 @@
}
}
-internal class ParameterMetadata(
- var name: String = "",
- var type: String = ""
-) {
+internal class ParameterMetadata(var name: String = "", var type: String = "") {
override fun toString(): String {
return "$name: $type"
}
@@ -94,8 +91,7 @@
descriptor.unsubstitutedMemberScope.getContributedDescriptors().forEach { desc ->
if (desc is ClassDescriptor) {
classifiers.add(fromDescriptor(desc))
- }
- else {
+ } else {
desc.accept(visitor, this)
}
}
@@ -111,7 +107,9 @@
}
}
-internal fun ResolutionScope.classesInScope(nameFilter: (String) -> Boolean = { true }): Collection<ClassMetadata> {
+internal fun ResolutionScope.classesInScope(
+ nameFilter: (String) -> Boolean = { true }
+): Collection<ClassMetadata> {
return this.getContributedDescriptors { nameFilter(it.asString()) }
.sortedWith(MemberComparator.INSTANCE)
.filterIsInstance<ClassDescriptor>()
@@ -126,8 +124,9 @@
}
private fun List<ParameterDescriptor>.toMetadata(): MutableList<ParameterMetadata> {
- return this
- .map { paramDesc -> ParameterMetadata(paramDesc.name.asString(), paramDesc.type.asString()) }
+ return this.map { paramDesc ->
+ ParameterMetadata(paramDesc.name.asString(), paramDesc.type.asString())
+ }
.toMutableList()
}
@@ -146,13 +145,17 @@
private class MetadataVisitor : DeclarationDescriptorVisitor<Unit, ClassMetadata> {
override fun visitClassDescriptor(descriptor: ClassDescriptor, data: ClassMetadata) {
data.fqcn = descriptor.fqNameSafe.asString()
- data.supertypes = descriptor.typeConstructor.supertypes
- .filter { type -> !type.isAnyOrNullableAny() }
- .map { type -> type.asString() }
- .toMutableList()
+ data.supertypes =
+ descriptor.typeConstructor.supertypes
+ .filter { type -> !type.isAnyOrNullableAny() }
+ .map { type -> type.asString() }
+ .toMutableList()
}
- override fun visitConstructorDescriptor(constructorDescriptor: ConstructorDescriptor, data: ClassMetadata) {
+ override fun visitConstructorDescriptor(
+ constructorDescriptor: ConstructorDescriptor,
+ data: ClassMetadata
+ ) {
data.constructors.add(constructorDescriptor.toMetadata())
}
@@ -164,16 +167,33 @@
data.properties.add(descriptor.toMetadata())
}
-
override fun visitModuleDeclaration(descriptor: ModuleDescriptor, data: ClassMetadata) {}
override fun visitPackageViewDescriptor(descriptor: PackageViewDescriptor, data: ClassMetadata) {}
- override fun visitPropertyGetterDescriptor(descriptor: PropertyGetterDescriptor, data: ClassMetadata) {}
- override fun visitPropertySetterDescriptor(descriptor: PropertySetterDescriptor, data: ClassMetadata) {}
- override fun visitReceiverParameterDescriptor(descriptor: ReceiverParameterDescriptor, data: ClassMetadata) {}
- override fun visitPackageFragmentDescriptor(descriptor: PackageFragmentDescriptor, data: ClassMetadata) {}
+ override fun visitPropertyGetterDescriptor(
+ descriptor: PropertyGetterDescriptor,
+ data: ClassMetadata
+ ) {}
+ override fun visitPropertySetterDescriptor(
+ descriptor: PropertySetterDescriptor,
+ data: ClassMetadata
+ ) {}
+ override fun visitReceiverParameterDescriptor(
+ descriptor: ReceiverParameterDescriptor,
+ data: ClassMetadata
+ ) {}
+ override fun visitPackageFragmentDescriptor(
+ descriptor: PackageFragmentDescriptor,
+ data: ClassMetadata
+ ) {}
override fun visitScriptDescriptor(scriptDescriptor: ScriptDescriptor, data: ClassMetadata) {}
override fun visitTypeAliasDescriptor(descriptor: TypeAliasDescriptor, data: ClassMetadata) {}
- override fun visitTypeParameterDescriptor(descriptor: TypeParameterDescriptor, data: ClassMetadata) {}
- override fun visitValueParameterDescriptor(descriptor: ValueParameterDescriptor, data: ClassMetadata) {}
+ override fun visitTypeParameterDescriptor(
+ descriptor: TypeParameterDescriptor,
+ data: ClassMetadata
+ ) {}
+ override fun visitValueParameterDescriptor(
+ descriptor: ValueParameterDescriptor,
+ data: ClassMetadata
+ ) {}
override fun visitVariableDescriptor(descriptor: VariableDescriptor, data: ClassMetadata) {}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/DirectionsClassKtDescriptorsTest.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/DirectionsClassKtDescriptorsTest.kt
index bc012bb..3f18006 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/DirectionsClassKtDescriptorsTest.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/DirectionsClassKtDescriptorsTest.kt
@@ -34,14 +34,13 @@
@RunsInEdt
class DirectionsClassKtDescriptorsTest {
- @get:Rule
- val safeArgsRule = SafeArgsRule(SafeArgsMode.KOTLIN)
+ @get:Rule val safeArgsRule = SafeArgsRule(SafeArgsMode.KOTLIN)
@Test
fun checkContributorsOfDirectionsClass() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -70,41 +69,47 @@
android:name="test.safeargs.Fragment2"
android:label="Fragment2" />
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
- val safeArgProviderExtension = PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
- it is SafeArgsKtPackageProviderExtension
- }
+ val safeArgProviderExtension =
+ PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
+ it is SafeArgsKtPackageProviderExtension
+ }
val traceMock: BindingTrace = MockitoKt.mock()
val moduleSourceInfo = safeArgsRule.module.productionSourceInfo
val moduleDescriptor = safeArgsRule.module.toDescriptor()
- val fragmentProvider = safeArgProviderExtension.getPackageFragmentProvider(
- project = safeArgsRule.project,
- module = moduleDescriptor!!,
- storageManager = LockBasedStorageManager.NO_LOCKS,
- trace = traceMock,
- moduleInfo = moduleSourceInfo,
- lookupTracker = LookupTracker.DO_NOTHING
- ) as SafeArgsSyntheticPackageProvider
+ val fragmentProvider =
+ safeArgProviderExtension.getPackageFragmentProvider(
+ project = safeArgsRule.project,
+ module = moduleDescriptor!!,
+ storageManager = LockBasedStorageManager.NO_LOCKS,
+ trace = traceMock,
+ moduleInfo = moduleSourceInfo,
+ lookupTracker = LookupTracker.DO_NOTHING
+ ) as SafeArgsSyntheticPackageProvider
- val directionsClassMetadata = fragmentProvider.getPackageFragments(FqName("test.safeargs"))
- .flatMap { it.getMemberScope().classesInScope { name -> name.endsWith("Directions") } }
- .first()
+ val directionsClassMetadata =
+ fragmentProvider
+ .getPackageFragments(FqName("test.safeargs"))
+ .flatMap { it.getMemberScope().classesInScope { name -> name.endsWith("Directions") } }
+ .first()
assertThat(directionsClassMetadata.constructors).isEmpty()
- assertThat(directionsClassMetadata.classifiers.map { it.toString() }).containsExactly(
- "test.safeargs.Fragment1Directions.Companion"
- )
- assertThat(directionsClassMetadata.companionObject!!.functions.map { it.toString() }).containsExactly(
- "actionFragment1ToFragment2(): androidx.navigation.NavDirections",
- "actionFragment1ToMain(): androidx.navigation.NavDirections",
- "actionWithoutDestination(): androidx.navigation.NavDirections",
- )
+ assertThat(directionsClassMetadata.classifiers.map { it.toString() })
+ .containsExactly("test.safeargs.Fragment1Directions.Companion")
+ assertThat(directionsClassMetadata.companionObject!!.functions.map { it.toString() })
+ .containsExactly(
+ "actionFragment1ToFragment2(): androidx.navigation.NavDirections",
+ "actionFragment1ToMain(): androidx.navigation.NavDirections",
+ "actionWithoutDestination(): androidx.navigation.NavDirections",
+ )
assertThat(directionsClassMetadata.functions).isEmpty()
}
@@ -113,7 +118,7 @@
safeArgsRule.addFakeNavigationDependency(SafeArgsFeatureVersions.ADJUST_PARAMS_WITH_DEFAULTS)
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -159,54 +164,60 @@
</action>
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
- val safeArgProviderExtension = PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
- it is SafeArgsKtPackageProviderExtension
- }
+ val safeArgProviderExtension =
+ PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
+ it is SafeArgsKtPackageProviderExtension
+ }
val traceMock: BindingTrace = MockitoKt.mock()
val moduleSourceInfo = safeArgsRule.module.productionSourceInfo
val moduleDescriptor = safeArgsRule.module.toDescriptor()
- val fragmentProvider = safeArgProviderExtension.getPackageFragmentProvider(
- project = safeArgsRule.project,
- module = moduleDescriptor!!,
- storageManager = LockBasedStorageManager.NO_LOCKS,
- trace = traceMock,
- moduleInfo = moduleSourceInfo,
- lookupTracker = LookupTracker.DO_NOTHING
- ) as SafeArgsSyntheticPackageProvider
+ val fragmentProvider =
+ safeArgProviderExtension.getPackageFragmentProvider(
+ project = safeArgsRule.project,
+ module = moduleDescriptor!!,
+ storageManager = LockBasedStorageManager.NO_LOCKS,
+ trace = traceMock,
+ moduleInfo = moduleSourceInfo,
+ lookupTracker = LookupTracker.DO_NOTHING
+ ) as SafeArgsSyntheticPackageProvider
- val directionsClassMetadata = fragmentProvider.getPackageFragments(FqName("test.safeargs"))
- .flatMap { it.getMemberScope().classesInScope { name -> name.endsWith("Directions") } }
- .sortedBy { it.fqcn }
+ val directionsClassMetadata =
+ fragmentProvider
+ .getPackageFragments(FqName("test.safeargs"))
+ .flatMap { it.getMemberScope().classesInScope { name -> name.endsWith("Directions") } }
+ .sortedBy { it.fqcn }
assertThat(directionsClassMetadata.size).isEqualTo(2)
directionsClassMetadata[0].let { directionsClass ->
assertThat(directionsClass.constructors).isEmpty()
- assertThat(directionsClass.classifiers.map { it.toString() }).containsExactly(
- "test.safeargs.Fragment1Directions.Companion"
- )
- assertThat(directionsClass.companionObject!!.functions.map { it.toString() }).containsExactly(
- "actionFragment1ToFragment2(overriddenArg: kotlin.String, arg: kotlin.String, overriddenArgWithDefaultValue: kotlin.Int)" +
- ": androidx.navigation.NavDirections"
- )
+ assertThat(directionsClass.classifiers.map { it.toString() })
+ .containsExactly("test.safeargs.Fragment1Directions.Companion")
+ assertThat(directionsClass.companionObject!!.functions.map { it.toString() })
+ .containsExactly(
+ "actionFragment1ToFragment2(overriddenArg: kotlin.String, arg: kotlin.String, overriddenArgWithDefaultValue: kotlin.Int)" +
+ ": androidx.navigation.NavDirections"
+ )
assertThat(directionsClass.functions).isEmpty()
}
directionsClassMetadata[1].let { directionsClass ->
assertThat(directionsClass.constructors).isEmpty()
- assertThat(directionsClass.classifiers.map { it.toString() }).containsExactly(
- "test.safeargs.Fragment2Directions.Companion"
- )
- assertThat(directionsClass.companionObject!!.functions.map { it.toString() }).containsExactly(
- "actionFragment2ToMain(overriddenArgWithDefaultValue: kotlin.Int): androidx.navigation.NavDirections"
- )
+ assertThat(directionsClass.classifiers.map { it.toString() })
+ .containsExactly("test.safeargs.Fragment2Directions.Companion")
+ assertThat(directionsClass.companionObject!!.functions.map { it.toString() })
+ .containsExactly(
+ "actionFragment2ToMain(overriddenArgWithDefaultValue: kotlin.Int): androidx.navigation.NavDirections"
+ )
assertThat(directionsClass.functions).isEmpty()
}
}
@@ -215,7 +226,7 @@
fun testIncludedNavigationCase() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -232,42 +243,46 @@
app:destination="@id/included_graph" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
- val safeArgProviderExtension = PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
- it is SafeArgsKtPackageProviderExtension
- }
+ val safeArgProviderExtension =
+ PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
+ it is SafeArgsKtPackageProviderExtension
+ }
val traceMock: BindingTrace = MockitoKt.mock()
val moduleSourceInfo = safeArgsRule.module.productionSourceInfo
val moduleDescriptor = safeArgsRule.module.toDescriptor()
- val fragmentProvider = safeArgProviderExtension.getPackageFragmentProvider(
- project = safeArgsRule.project,
- module = moduleDescriptor!!,
- storageManager = LockBasedStorageManager.NO_LOCKS,
- trace = traceMock,
- moduleInfo = moduleSourceInfo,
- lookupTracker = LookupTracker.DO_NOTHING
- ) as SafeArgsSyntheticPackageProvider
+ val fragmentProvider =
+ safeArgProviderExtension.getPackageFragmentProvider(
+ project = safeArgsRule.project,
+ module = moduleDescriptor!!,
+ storageManager = LockBasedStorageManager.NO_LOCKS,
+ trace = traceMock,
+ moduleInfo = moduleSourceInfo,
+ lookupTracker = LookupTracker.DO_NOTHING
+ ) as SafeArgsSyntheticPackageProvider
- val directionsClassMetadata = fragmentProvider.getPackageFragments(FqName("test.safeargs"))
- .flatMap { it.getMemberScope().classesInScope { name -> name.endsWith("Directions") } }
- .sortedBy { it.fqcn }
+ val directionsClassMetadata =
+ fragmentProvider
+ .getPackageFragments(FqName("test.safeargs"))
+ .flatMap { it.getMemberScope().classesInScope { name -> name.endsWith("Directions") } }
+ .sortedBy { it.fqcn }
assertThat(directionsClassMetadata.size).isEqualTo(1)
directionsClassMetadata[0].let { directionsClass ->
assertThat(directionsClass.constructors).isEmpty()
- assertThat(directionsClass.classifiers.map { it.toString() }).containsExactly(
- "test.safeargs.Fragment2Directions.Companion"
- )
- assertThat(directionsClass.companionObject!!.functions.map { it.toString() }).containsExactly(
- "actionFragment2ToIncludedGraph(): androidx.navigation.NavDirections"
- )
+ assertThat(directionsClass.classifiers.map { it.toString() })
+ .containsExactly("test.safeargs.Fragment2Directions.Companion")
+ assertThat(directionsClass.companionObject!!.functions.map { it.toString() })
+ .containsExactly("actionFragment2ToIncludedGraph(): androidx.navigation.NavDirections")
assertThat(directionsClass.functions).isEmpty()
}
}
@@ -276,7 +291,7 @@
fun testGlobalActionCase() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -312,67 +327,71 @@
</navigation>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
- val safeArgProviderExtension = PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
- it is SafeArgsKtPackageProviderExtension
- }
+ val safeArgProviderExtension =
+ PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
+ it is SafeArgsKtPackageProviderExtension
+ }
val traceMock: BindingTrace = MockitoKt.mock()
val moduleSourceInfo = safeArgsRule.module.productionSourceInfo
val moduleDescriptor = safeArgsRule.module.toDescriptor()
- val fragmentProvider = safeArgProviderExtension.getPackageFragmentProvider(
- project = safeArgsRule.project,
- module = moduleDescriptor!!,
- storageManager = LockBasedStorageManager.NO_LOCKS,
- trace = traceMock,
- moduleInfo = moduleSourceInfo,
- lookupTracker = LookupTracker.DO_NOTHING
- ) as SafeArgsSyntheticPackageProvider
+ val fragmentProvider =
+ safeArgProviderExtension.getPackageFragmentProvider(
+ project = safeArgsRule.project,
+ module = moduleDescriptor!!,
+ storageManager = LockBasedStorageManager.NO_LOCKS,
+ trace = traceMock,
+ moduleInfo = moduleSourceInfo,
+ lookupTracker = LookupTracker.DO_NOTHING
+ ) as SafeArgsSyntheticPackageProvider
- val directionsClassMetadata = fragmentProvider.getPackageFragments(FqName("test.safeargs"))
- .flatMap { it.getMemberScope().classesInScope { name -> name.endsWith("Directions") } }
- .sortedBy { it.fqcn }
+ val directionsClassMetadata =
+ fragmentProvider
+ .getPackageFragments(FqName("test.safeargs"))
+ .flatMap { it.getMemberScope().classesInScope { name -> name.endsWith("Directions") } }
+ .sortedBy { it.fqcn }
assertThat(directionsClassMetadata.size).isEqualTo(3)
directionsClassMetadata[0].let { directionsClass ->
assertThat(directionsClass.constructors).isEmpty()
- assertThat(directionsClass.classifiers.map { it.toString() }).containsExactly(
- "test.safeargs.Fragment2Directions.Companion"
- )
- assertThat(directionsClass.companionObject!!.functions.map { it.toString() }).containsExactly(
- "actionFragment2ToIncludedGraph(): androidx.navigation.NavDirections",
- "actionToIncludedGraph(): androidx.navigation.NavDirections",
- "actionInnerNavigationToIncludedGraph(): androidx.navigation.NavDirections"
- )
+ assertThat(directionsClass.classifiers.map { it.toString() })
+ .containsExactly("test.safeargs.Fragment2Directions.Companion")
+ assertThat(directionsClass.companionObject!!.functions.map { it.toString() })
+ .containsExactly(
+ "actionFragment2ToIncludedGraph(): androidx.navigation.NavDirections",
+ "actionToIncludedGraph(): androidx.navigation.NavDirections",
+ "actionInnerNavigationToIncludedGraph(): androidx.navigation.NavDirections"
+ )
assertThat(directionsClass.functions).isEmpty()
}
directionsClassMetadata[1].let { directionsClass ->
assertThat(directionsClass.constructors).isEmpty()
- assertThat(directionsClass.classifiers.map { it.toString() }).containsExactly(
- "test.safeargs.InnerNavigationDirections.Companion"
- )
- assertThat(directionsClass.companionObject!!.functions.map { it.toString() }).containsExactly(
- "actionToIncludedGraph(): androidx.navigation.NavDirections",
- "actionInnerNavigationToIncludedGraph(): androidx.navigation.NavDirections"
- )
+ assertThat(directionsClass.classifiers.map { it.toString() })
+ .containsExactly("test.safeargs.InnerNavigationDirections.Companion")
+ assertThat(directionsClass.companionObject!!.functions.map { it.toString() })
+ .containsExactly(
+ "actionToIncludedGraph(): androidx.navigation.NavDirections",
+ "actionInnerNavigationToIncludedGraph(): androidx.navigation.NavDirections"
+ )
assertThat(directionsClass.functions).isEmpty()
}
directionsClassMetadata[2].let { directionsClass ->
assertThat(directionsClass.constructors).isEmpty()
- assertThat(directionsClass.classifiers.map { it.toString() }).containsExactly(
- "test.safeargs.MainDirections.Companion"
- )
- assertThat(directionsClass.companionObject!!.functions.map { it.toString() }).containsExactly(
- "actionToIncludedGraph(): androidx.navigation.NavDirections"
- )
+ assertThat(directionsClass.classifiers.map { it.toString() })
+ .containsExactly("test.safeargs.MainDirections.Companion")
+ assertThat(directionsClass.companionObject!!.functions.map { it.toString() })
+ .containsExactly("actionToIncludedGraph(): androidx.navigation.NavDirections")
assertThat(directionsClass.functions).isEmpty()
}
}
@@ -381,7 +400,7 @@
fun checkNoDestinationDefinedCase() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -405,39 +424,43 @@
app:popUpTo="@id/fragment1" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
- val safeArgProviderExtension = PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
- it is SafeArgsKtPackageProviderExtension
- }
+ val safeArgProviderExtension =
+ PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
+ it is SafeArgsKtPackageProviderExtension
+ }
val traceMock: BindingTrace = MockitoKt.mock()
val moduleSourceInfo = safeArgsRule.module.productionSourceInfo
val moduleDescriptor = safeArgsRule.module.toDescriptor()
- val fragmentProvider = safeArgProviderExtension.getPackageFragmentProvider(
- project = safeArgsRule.project,
- module = moduleDescriptor!!,
- storageManager = LockBasedStorageManager.NO_LOCKS,
- trace = traceMock,
- moduleInfo = moduleSourceInfo,
- lookupTracker = LookupTracker.DO_NOTHING
- ) as SafeArgsSyntheticPackageProvider
+ val fragmentProvider =
+ safeArgProviderExtension.getPackageFragmentProvider(
+ project = safeArgsRule.project,
+ module = moduleDescriptor!!,
+ storageManager = LockBasedStorageManager.NO_LOCKS,
+ trace = traceMock,
+ moduleInfo = moduleSourceInfo,
+ lookupTracker = LookupTracker.DO_NOTHING
+ ) as SafeArgsSyntheticPackageProvider
- val directionsClassMetadata = fragmentProvider.getPackageFragments(FqName("test.safeargs"))
- .flatMap { it.getMemberScope().classesInScope { name -> name.endsWith("Directions") } }
- .first()
+ val directionsClassMetadata =
+ fragmentProvider
+ .getPackageFragments(FqName("test.safeargs"))
+ .flatMap { it.getMemberScope().classesInScope { name -> name.endsWith("Directions") } }
+ .first()
assertThat(directionsClassMetadata.constructors).isEmpty()
- assertThat(directionsClassMetadata.classifiers.map { it.toString() }).containsExactly(
- "test.safeargs.Fragment2Directions.Companion"
- )
- assertThat(directionsClassMetadata.companionObject!!.functions.map { it.toString() }).containsExactly(
- "actionFragment2ToFragment1(): androidx.navigation.NavDirections"
- )
+ assertThat(directionsClassMetadata.classifiers.map { it.toString() })
+ .containsExactly("test.safeargs.Fragment2Directions.Companion")
+ assertThat(directionsClassMetadata.companionObject!!.functions.map { it.toString() })
+ .containsExactly("actionFragment2ToFragment1(): androidx.navigation.NavDirections")
assertThat(directionsClassMetadata.functions).isEmpty()
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KotlinTypeUtilsKtTest.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KotlinTypeUtilsKtTest.kt
index edb70e5..a475b47 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KotlinTypeUtilsKtTest.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/KotlinTypeUtilsKtTest.kt
@@ -30,13 +30,10 @@
import org.junit.Rule
import org.junit.Test
-/**
- * Tests on types check, nullabilities check, inferred types check
- */
+/** Tests on types check, nullabilities check, inferred types check */
@RunsInEdt
class KotlinTypeUtilsKtTest {
- @get:Rule
- val safeArgsRule = SafeArgsRule(SafeArgsMode.KOTLIN)
+ @get:Rule val safeArgsRule = SafeArgsRule(SafeArgsMode.KOTLIN)
private val builtIn = DefaultBuiltIns.Instance
private lateinit var moduleDescriptor: ModuleDescriptor
@@ -46,129 +43,141 @@
moduleDescriptor = safeArgsRule.module.toDescriptor()!!
}
- /**
- * Primitive types regular check
- */
+ /** Primitive types regular check */
@Test
fun checkInteger() {
- val type = builtIn.getKotlinType(
- typeStr = "integer",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "integer",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor
+ )
assertThat(type).isEqualTo(builtIn.intType)
}
@Test
fun checkIntegerArray() {
- val type = builtIn.getKotlinType(
- typeStr = "integer[]",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor
- )
- assertThat(type).isEqualTo(builtIn.getPrimitiveArrayKotlinTypeByPrimitiveKotlinType(builtIn.intType))
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "integer[]",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor
+ )
+ assertThat(type)
+ .isEqualTo(builtIn.getPrimitiveArrayKotlinTypeByPrimitiveKotlinType(builtIn.intType))
}
@Test
fun checkFloat() {
- val type = builtIn.getKotlinType(
- typeStr = "float",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "float",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor
+ )
assertThat(type).isEqualTo(builtIn.floatType)
}
@Test
fun checkFloatArray() {
- val type = builtIn.getKotlinType(
- typeStr = "float[]",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor
- )
- assertThat(type).isEqualTo(builtIn.getPrimitiveArrayKotlinTypeByPrimitiveKotlinType(builtIn.floatType))
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "float[]",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor
+ )
+ assertThat(type)
+ .isEqualTo(builtIn.getPrimitiveArrayKotlinTypeByPrimitiveKotlinType(builtIn.floatType))
}
@Test
fun checkLong() {
- val type = builtIn.getKotlinType(
- typeStr = "long",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "long",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor
+ )
assertThat(type).isEqualTo(builtIn.longType)
}
@Test
fun checkLongArray() {
- val type = builtIn.getKotlinType(
- typeStr = "long[]",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor
- )
- assertThat(type).isEqualTo(builtIn.getPrimitiveArrayKotlinTypeByPrimitiveKotlinType(builtIn.longType))
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "long[]",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor
+ )
+ assertThat(type)
+ .isEqualTo(builtIn.getPrimitiveArrayKotlinTypeByPrimitiveKotlinType(builtIn.longType))
}
@Test
fun checkBoolean() {
- val type = builtIn.getKotlinType(
- typeStr = "boolean",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "boolean",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor
+ )
assertThat(type).isEqualTo(builtIn.booleanType)
}
@Test
fun checkBooleanArray() {
- val type = builtIn.getKotlinType(
- typeStr = "boolean[]",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor
- )
- assertThat(type).isEqualTo(builtIn.getPrimitiveArrayKotlinTypeByPrimitiveKotlinType(builtIn.booleanType))
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "boolean[]",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor
+ )
+ assertThat(type)
+ .isEqualTo(builtIn.getPrimitiveArrayKotlinTypeByPrimitiveKotlinType(builtIn.booleanType))
}
- /**
- * Not primitive types regular check
- */
+ /** Not primitive types regular check */
@Test
fun checkString() {
- val type = builtIn.getKotlinType(
- typeStr = "string",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "string",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor
+ )
assertThat(type).isEqualTo(builtIn.stringType)
}
@Test
fun checkStringArray() {
- val type = builtIn.getKotlinType(
- typeStr = "string[]",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "string[]",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor
+ )
assertThat(type).isEqualTo(builtIn.getArrayType(Variance.INVARIANT, builtIn.stringType))
}
@Test
fun checkResourceReference() {
- val type = builtIn.getKotlinType(
- typeStr = "reference",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "reference",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor
+ )
assertThat(type).isEqualTo(builtIn.intType)
}
@Test
fun checkCustomType() {
- val type = builtIn.getKotlinType(
- typeStr = "test.safeargs.MyCustomType",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor
- ) as ErrorType
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "test.safeargs.MyCustomType",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor
+ ) as ErrorType
assertThat(type.nullability()).isEqualTo(TypeNullability.NOT_NULL)
assertThat(type.debugMessage).isEqualTo("Unresolved type for test.safeargs.MyCustomType")
@@ -176,135 +185,139 @@
@Test
fun checkCustomTypeArray() {
- val type = builtIn.getKotlinType(
- typeStr = "test.safeargs.MyCustomType[]",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "test.safeargs.MyCustomType[]",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor
+ )
val elementType = builtIn.getArrayElementType(type) as ErrorType
assertThat(elementType.debugMessage).isEqualTo("Unresolved type for test.safeargs.MyCustomType")
}
- /**
- * Inferred types check
- */
+ /** Inferred types check */
@Test
fun checkInferredInteger() {
- val type = builtIn.getKotlinType(
- typeStr = null,
- defaultValue = "1",
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(typeStr = null, defaultValue = "1", moduleDescriptor = moduleDescriptor)
assertThat(type).isEqualTo(builtIn.intType)
}
@Test
fun checkInferredFloat() {
- val type = builtIn.getKotlinType(
- typeStr = null,
- defaultValue = "1f",
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = null,
+ defaultValue = "1f",
+ moduleDescriptor = moduleDescriptor
+ )
assertThat(type).isEqualTo(builtIn.floatType)
}
@Test
fun checkInferredLong() {
- val type = builtIn.getKotlinType(
- typeStr = null,
- defaultValue = "1L",
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = null,
+ defaultValue = "1L",
+ moduleDescriptor = moduleDescriptor
+ )
assertThat(type).isEqualTo(builtIn.longType)
}
@Test
fun checkInferredBoolean() {
- val type = builtIn.getKotlinType(
- typeStr = null,
- defaultValue = "true",
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = null,
+ defaultValue = "true",
+ moduleDescriptor = moduleDescriptor
+ )
assertThat(type).isEqualTo(builtIn.booleanType)
}
@Test
fun checkInferredString() {
- val type = builtIn.getKotlinType(
- typeStr = null,
- defaultValue = "someString",
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = null,
+ defaultValue = "someString",
+ moduleDescriptor = moduleDescriptor
+ )
assertThat(type).isEqualTo(builtIn.stringType)
}
@Test
fun checkInferredNullString() {
- val type = builtIn.getKotlinType(
- typeStr = null,
- defaultValue = "@null",
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = null,
+ defaultValue = "@null",
+ moduleDescriptor = moduleDescriptor
+ )
assertThat(type).isEqualTo(builtIn.stringType)
}
@Test
fun checkInferredResourceReference() {
- val type = builtIn.getKotlinType(
- typeStr = null,
- defaultValue = "@resourceType/resourceName",
- moduleDescriptor = moduleDescriptor
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = null,
+ defaultValue = "@resourceType/resourceName",
+ moduleDescriptor = moduleDescriptor
+ )
assertThat(type).isEqualTo(builtIn.intType)
}
- /**
- * Not primitive types nullability check
- */
+ /** Not primitive types nullability check */
@Test
fun checkNullableString() {
- val type = builtIn.getKotlinType(
- typeStr = "string",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor,
- isNonNull = false
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "string",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor,
+ isNonNull = false
+ )
assertThat(type.nullability()).isEqualTo(TypeNullability.NULLABLE)
}
@Test
fun checkNullableCustomType() {
- val type = builtIn.getKotlinType(
- typeStr = "test.safeargs.MyCustomType",
- defaultValue = "@null",
- moduleDescriptor = moduleDescriptor,
- isNonNull = false
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "test.safeargs.MyCustomType",
+ defaultValue = "@null",
+ moduleDescriptor = moduleDescriptor,
+ isNonNull = false
+ )
assertThat(type.nullability()).isEqualTo(TypeNullability.NULLABLE)
}
@Test
fun checkNullableStringArray() {
- val type = builtIn.getKotlinType(
- typeStr = "string[]",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor,
- isNonNull = false
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "string[]",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor,
+ isNonNull = false
+ )
assertThat(type.nullability()).isEqualTo(TypeNullability.NULLABLE)
}
@Test
fun checkNullableCustomTypeArray() {
- val type = builtIn.getKotlinType(
- typeStr = "test.safeargs.MyCustomType[]",
- defaultValue = null,
- moduleDescriptor = moduleDescriptor,
- isNonNull = false
- )
+ val type =
+ builtIn.getKotlinType(
+ typeStr = "test.safeargs.MyCustomType[]",
+ defaultValue = null,
+ moduleDescriptor = moduleDescriptor,
+ isNonNull = false
+ )
assertThat(type.nullability()).isEqualTo(TypeNullability.NULLABLE)
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgNavigationKtTest.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgNavigationKtTest.kt
index 8ece8c3..e8acbb7f 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgNavigationKtTest.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgNavigationKtTest.kt
@@ -38,14 +38,13 @@
@RunsInEdt
class SafeArgNavigationKtTest {
- @get:Rule
- val safeArgsRule = SafeArgsRule(SafeArgsMode.KOTLIN)
+ @get:Rule val safeArgsRule = SafeArgsRule(SafeArgsMode.KOTLIN)
@Test
fun canNavigateToXmlTagFromArgsKtClass() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -69,33 +68,38 @@
app:argType="string" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
- val safeArgProviderExtension = PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
- it is SafeArgsKtPackageProviderExtension
- }
+ val safeArgProviderExtension =
+ PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
+ it is SafeArgsKtPackageProviderExtension
+ }
val traceMock: BindingTrace = MockitoKt.mock()
val moduleSourceInfo = safeArgsRule.module.productionSourceInfo
val moduleDescriptor = safeArgsRule.module.toDescriptor()
- val fragmentProvider = safeArgProviderExtension.getPackageFragmentProvider(
- project = safeArgsRule.project,
- module = moduleDescriptor!!,
- storageManager = LockBasedStorageManager.NO_LOCKS,
- trace = traceMock,
- moduleInfo = moduleSourceInfo,
- lookupTracker = LookupTracker.DO_NOTHING
- ) as SafeArgsSyntheticPackageProvider
+ val fragmentProvider =
+ safeArgProviderExtension.getPackageFragmentProvider(
+ project = safeArgsRule.project,
+ module = moduleDescriptor!!,
+ storageManager = LockBasedStorageManager.NO_LOCKS,
+ trace = traceMock,
+ moduleInfo = moduleSourceInfo,
+ lookupTracker = LookupTracker.DO_NOTHING
+ ) as SafeArgsSyntheticPackageProvider
-
- val argsClassDescriptors: List<LightArgsKtClass> = fragmentProvider.getPackageFragments(FqName("test.safeargs"))
- .flatMap { it.getMemberScope().getContributedDescriptors() }
- .sortedWith(MemberComparator.INSTANCE)
- .mapNotNull { it as? LightArgsKtClass }
+ val argsClassDescriptors: List<LightArgsKtClass> =
+ fragmentProvider
+ .getPackageFragments(FqName("test.safeargs"))
+ .flatMap { it.getMemberScope().getContributedDescriptors() }
+ .sortedWith(MemberComparator.INSTANCE)
+ .mapNotNull { it as? LightArgsKtClass }
assertThat(argsClassDescriptors.size).isEqualTo(2)
// check Fragment1Args class navigation
@@ -112,27 +116,33 @@
is PropertyDescriptorImpl -> {
val resolvedNavigationElement = descriptor.source.getPsi()!!
assertThat(resolvedNavigationElement is XmlTag).isTrue()
- assertThat((resolvedNavigationElement as XmlTag).text).isEqualTo(
- """
+ assertThat((resolvedNavigationElement as XmlTag).text)
+ .isEqualTo(
+ """
<argument
android:name="arg_one"
app:argType="string" />
- """.trimIndent())
+ """
+ .trimIndent()
+ )
assertThat(descriptor.name.asString()).isEqualTo("argOne")
}
is SimpleFunctionDescriptorImpl -> {
val resolvedNavigationElement = descriptor.source.getPsi()!!
assertThat(resolvedNavigationElement is XmlTag).isTrue()
if (descriptor.name.asString() == "component1") {
- assertThat((resolvedNavigationElement as XmlTag).text).isEqualTo(
- """
+ assertThat((resolvedNavigationElement as XmlTag).text)
+ .isEqualTo(
+ """
<argument
android:name="arg_one"
app:argType="string" />
- """.trimIndent())
- }
- else {
- assertThat((resolvedNavigationElement as XmlTag).text).contains("id=\"@+id/fragment1\"")
+ """
+ .trimIndent()
+ )
+ } else {
+ assertThat((resolvedNavigationElement as XmlTag).text)
+ .contains("id=\"@+id/fragment1\"")
}
}
}
@@ -155,12 +165,15 @@
is PropertyDescriptorImpl -> {
val resolvedNavigationElement = descriptor.source.getPsi()!!
assertThat(resolvedNavigationElement is XmlTag).isTrue()
- assertThat((resolvedNavigationElement as XmlTag).text).isEqualTo(
- """
+ assertThat((resolvedNavigationElement as XmlTag).text)
+ .isEqualTo(
+ """
<argument
android:name="Arg_two"
app:argType="string" />
- """.trimIndent())
+ """
+ .trimIndent()
+ )
assertThat(descriptor.name.asString()).isEqualTo("ArgTwo")
}
is SimpleFunctionDescriptorImpl -> {
@@ -168,15 +181,18 @@
assertThat(resolvedNavigationElement is XmlTag).isTrue()
if (descriptor.name.asString() == "component1") {
- assertThat((resolvedNavigationElement as XmlTag).text).isEqualTo(
- """
+ assertThat((resolvedNavigationElement as XmlTag).text)
+ .isEqualTo(
+ """
<argument
android:name="Arg_two"
app:argType="string" />
- """.trimIndent())
- }
- else {
- assertThat((resolvedNavigationElement as XmlTag).text).contains("id=\"@+id/fragment2\"")
+ """
+ .trimIndent()
+ )
+ } else {
+ assertThat((resolvedNavigationElement as XmlTag).text)
+ .contains("id=\"@+id/fragment2\"")
}
}
}
@@ -188,7 +204,7 @@
fun canNavigateToXmlTagFromDirectionsKtClass() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -229,32 +245,38 @@
</navigation>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
- val safeArgProviderExtension = PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
- it is SafeArgsKtPackageProviderExtension
- }
+ val safeArgProviderExtension =
+ PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
+ it is SafeArgsKtPackageProviderExtension
+ }
val traceMock: BindingTrace = MockitoKt.mock()
val moduleSourceInfo = safeArgsRule.module.productionSourceInfo
val moduleDescriptor = safeArgsRule.module.toDescriptor()
- val fragmentProvider = safeArgProviderExtension.getPackageFragmentProvider(
- project = safeArgsRule.project,
- module = moduleDescriptor!!,
- storageManager = LockBasedStorageManager.NO_LOCKS,
- trace = traceMock,
- moduleInfo = moduleSourceInfo,
- lookupTracker = LookupTracker.DO_NOTHING
- ) as SafeArgsSyntheticPackageProvider
+ val fragmentProvider =
+ safeArgProviderExtension.getPackageFragmentProvider(
+ project = safeArgsRule.project,
+ module = moduleDescriptor!!,
+ storageManager = LockBasedStorageManager.NO_LOCKS,
+ trace = traceMock,
+ moduleInfo = moduleSourceInfo,
+ lookupTracker = LookupTracker.DO_NOTHING
+ ) as SafeArgsSyntheticPackageProvider
- val directionsClassDescriptors: List<LightDirectionsKtClass> = fragmentProvider.getPackageFragments(FqName("test.safeargs"))
- .flatMap { it.getMemberScope().getContributedDescriptors() }
- .sortedWith(MemberComparator.INSTANCE)
- .mapNotNull { it as? LightDirectionsKtClass }
+ val directionsClassDescriptors: List<LightDirectionsKtClass> =
+ fragmentProvider
+ .getPackageFragments(FqName("test.safeargs"))
+ .flatMap { it.getMemberScope().getContributedDescriptors() }
+ .sortedWith(MemberComparator.INSTANCE)
+ .mapNotNull { it as? LightDirectionsKtClass }
assertThat(directionsClassDescriptors.size).isEqualTo(5)
@@ -264,30 +286,36 @@
assertThat(navigationElement).isInstanceOf(XmlTag::class.java)
assertThat((navigationElement as XmlTag).text).contains("id=\"@+id/fragment1\"")
- it.companionObjectDescriptor
- .unsubstitutedMemberScope
+ it.companionObjectDescriptor.unsubstitutedMemberScope
.getContributedDescriptors()
.sortedWith(MemberComparator.INSTANCE)
.forEach { descriptor ->
- val resolvedNavigationElement = (descriptor as SimpleFunctionDescriptorImpl).source.getPsi()!!
+ val resolvedNavigationElement =
+ (descriptor as SimpleFunctionDescriptorImpl).source.getPsi()!!
assertThat(resolvedNavigationElement is XmlTag).isTrue()
when (descriptor.name.toString()) {
"actionFragment1ToFragment2" -> {
- assertThat((resolvedNavigationElement as XmlTag).text).isEqualTo(
- """
+ assertThat((resolvedNavigationElement as XmlTag).text)
+ .isEqualTo(
+ """
<action
android:id="@+id/action_fragment1_to_fragment2"
app:destination="@id/fragment2" />
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
"actionFragment1ToFragment3" -> {
- assertThat((resolvedNavigationElement as XmlTag).text).isEqualTo(
- """
+ assertThat((resolvedNavigationElement as XmlTag).text)
+ .isEqualTo(
+ """
<action
android:id="@+id/action_fragment1_to_fragment3"
app:destination="@id/fragment3" />
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
}
@@ -299,19 +327,22 @@
assertThat(navigationElement).isInstanceOf(XmlTag::class.java)
assertThat((navigationElement as XmlTag).text).contains("id=\"@+id/fragment2\"")
- it.companionObjectDescriptor
- .unsubstitutedMemberScope
+ it.companionObjectDescriptor.unsubstitutedMemberScope
.getContributedDescriptors()
.sortedWith(MemberComparator.INSTANCE)
.forEach { descriptor ->
- val resolvedNavigationElement = (descriptor as SimpleFunctionDescriptorImpl).source.getPsi()!!
+ val resolvedNavigationElement =
+ (descriptor as SimpleFunctionDescriptorImpl).source.getPsi()!!
assertThat(resolvedNavigationElement is XmlTag).isTrue()
- assertThat((resolvedNavigationElement as XmlTag).text).isEqualTo(
- """
+ assertThat((resolvedNavigationElement as XmlTag).text)
+ .isEqualTo(
+ """
<action
android:id="@+id/action_to_nested"
app:destination="@id/nested" />
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
@@ -321,19 +352,22 @@
assertThat(navigationElement).isInstanceOf(XmlTag::class.java)
assertThat((navigationElement as XmlTag).text).contains("id=\"@+id/fragment3\"")
- it.companionObjectDescriptor
- .unsubstitutedMemberScope
+ it.companionObjectDescriptor.unsubstitutedMemberScope
.getContributedDescriptors()
.sortedWith(MemberComparator.INSTANCE)
.forEach { descriptor ->
- val resolvedNavigationElement = (descriptor as SimpleFunctionDescriptorImpl).source.getPsi()!!
+ val resolvedNavigationElement =
+ (descriptor as SimpleFunctionDescriptorImpl).source.getPsi()!!
assertThat(resolvedNavigationElement is XmlTag).isTrue()
- assertThat((resolvedNavigationElement as XmlTag).text).isEqualTo(
- """
+ assertThat((resolvedNavigationElement as XmlTag).text)
+ .isEqualTo(
+ """
<action
android:id="@+id/action_to_nested"
app:destination="@id/nested" />
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
@@ -343,19 +377,22 @@
assertThat(navigationElement).isInstanceOf(XmlTag::class.java)
assertThat((navigationElement as XmlTag).text).contains("id=\"@+id/main\"")
- it.companionObjectDescriptor
- .unsubstitutedMemberScope
+ it.companionObjectDescriptor.unsubstitutedMemberScope
.getContributedDescriptors()
.sortedWith(MemberComparator.INSTANCE)
.forEach { descriptor ->
- val resolvedNavigationElement = (descriptor as SimpleFunctionDescriptorImpl).source.getPsi()!!
+ val resolvedNavigationElement =
+ (descriptor as SimpleFunctionDescriptorImpl).source.getPsi()!!
assertThat(resolvedNavigationElement is XmlTag).isTrue()
- assertThat((resolvedNavigationElement as XmlTag).text).isEqualTo(
- """
+ assertThat((resolvedNavigationElement as XmlTag).text)
+ .isEqualTo(
+ """
<action
android:id="@+id/action_to_nested"
app:destination="@id/nested" />
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
@@ -365,20 +402,23 @@
assertThat(navigationElement).isInstanceOf(XmlTag::class.java)
assertThat((navigationElement as XmlTag).text).contains("id=\"@+id/nested\"")
- it.companionObjectDescriptor
- .unsubstitutedMemberScope
+ it.companionObjectDescriptor.unsubstitutedMemberScope
.getContributedDescriptors()
.sortedWith(MemberComparator.INSTANCE)
.forEach { descriptor ->
- val resolvedNavigationElement = (descriptor as SimpleFunctionDescriptorImpl).source.getPsi()!!
+ val resolvedNavigationElement =
+ (descriptor as SimpleFunctionDescriptorImpl).source.getPsi()!!
assertThat(resolvedNavigationElement is XmlTag).isTrue()
- assertThat((resolvedNavigationElement as XmlTag).text).isEqualTo(
- """
+ assertThat((resolvedNavigationElement as XmlTag).text)
+ .isEqualTo(
+ """
<action
android:id="@+id/action_to_nested"
app:destination="@id/nested" />
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsCacheTest.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsCacheTest.kt
index 4ab3092..a0e8e16 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsCacheTest.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsCacheTest.kt
@@ -27,14 +27,13 @@
@RunsInEdt
class SafeArgsCacheTest {
- @get:Rule
- val safeArgsRule = SafeArgsRule(SafeArgsMode.NONE)
+ @get:Rule val safeArgsRule = SafeArgsRule(SafeArgsMode.NONE)
@Test
fun cachesAreClearedWhenPluginModeChanges() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/nav_main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -51,7 +50,9 @@
app:destination="@id/main" />
</fragment>
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
val javaCache = SafeArgsCacheModuleService.getInstance(safeArgsRule.androidFacet)
val ktCache = KtDescriptorCacheModuleService.getInstance(safeArgsRule.module)
@@ -73,4 +74,4 @@
assertThat(javaCache.directions).isEmpty()
assertThat(ktCache.getDescriptors(moduleDescriptor)).isEmpty()
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKotlinPackageDescriptorTest.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKotlinPackageDescriptorTest.kt
index 2ed5f63..82fbb1a 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKotlinPackageDescriptorTest.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/SafeArgsKotlinPackageDescriptorTest.kt
@@ -33,17 +33,14 @@
@RunsInEdt
class SafeArgsKotlinPackageDescriptorTest {
- @get:Rule
- val safeArgsRule = SafeArgsRule(SafeArgsMode.KOTLIN)
+ @get:Rule val safeArgsRule = SafeArgsRule(SafeArgsMode.KOTLIN)
- /**
- * Check contributed args and directions class descriptors for single module case
- */
+ /** Check contributed args and directions class descriptors for single module case */
@Test
fun checkContributorsOfPackage() {
safeArgsRule.fixture.addFileToProject(
"res/navigation/main.xml",
- //language=XML
+ // language=XML
"""
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
@@ -76,49 +73,57 @@
android:id="@+id/action_main_to_fragment1"
app:destination="@id/fragment1" />
</navigation>
- """.trimIndent())
+ """
+ .trimIndent()
+ )
// Initialize repository after creating resources, needed for codegen to work
StudioResourceRepositoryManager.getInstance(safeArgsRule.androidFacet).moduleResources
- val safeArgProviderExtension = PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
- it is SafeArgsKtPackageProviderExtension
- }
+ val safeArgProviderExtension =
+ PackageFragmentProviderExtension.getInstances(safeArgsRule.project).first {
+ it is SafeArgsKtPackageProviderExtension
+ }
val traceMock: BindingTrace = mock()
val moduleSourceInfo = safeArgsRule.module.productionSourceInfo
val moduleDescriptor = safeArgsRule.module.toDescriptor()
- val fragmentProvider = safeArgProviderExtension.getPackageFragmentProvider(
- project = safeArgsRule.project,
- module = moduleDescriptor!!,
- storageManager = LockBasedStorageManager.NO_LOCKS,
- trace = traceMock,
- moduleInfo = moduleSourceInfo,
- lookupTracker = LookupTracker.DO_NOTHING
- ) as SafeArgsSyntheticPackageProvider
+ val fragmentProvider =
+ safeArgProviderExtension.getPackageFragmentProvider(
+ project = safeArgsRule.project,
+ module = moduleDescriptor!!,
+ storageManager = LockBasedStorageManager.NO_LOCKS,
+ trace = traceMock,
+ moduleInfo = moduleSourceInfo,
+ lookupTracker = LookupTracker.DO_NOTHING
+ ) as SafeArgsSyntheticPackageProvider
// Check contents for Fragment1
- val classesMetadata1 = fragmentProvider
- .getPackageFragments(FqName("test.safeargs"))
- .flatMap { it.getMemberScope().classesInScope() }
- .sortedBy { it.fqcn }
+ val classesMetadata1 =
+ fragmentProvider
+ .getPackageFragments(FqName("test.safeargs"))
+ .flatMap { it.getMemberScope().classesInScope() }
+ .sortedBy { it.fqcn }
- assertThat(classesMetadata1.map { it.toString() }).containsExactly(
- "test.safeargs.Fragment1Args: androidx.navigation.NavArgs",
- "test.safeargs.Fragment1Directions",
- "test.safeargs.MainDirections"
- )
+ assertThat(classesMetadata1.map { it.toString() })
+ .containsExactly(
+ "test.safeargs.Fragment1Args: androidx.navigation.NavArgs",
+ "test.safeargs.Fragment1Directions",
+ "test.safeargs.MainDirections"
+ )
// Check contents for Fragment2
- val classesMetadata2 = fragmentProvider
- .getPackageFragments(FqName("test.safeargs.sub1"))
- .flatMap { it.getMemberScope().classesInScope() }
- .sortedBy { it.fqcn }
+ val classesMetadata2 =
+ fragmentProvider
+ .getPackageFragments(FqName("test.safeargs.sub1"))
+ .flatMap { it.getMemberScope().classesInScope() }
+ .sortedBy { it.fqcn }
- assertThat(classesMetadata2.map { it.toString() }).containsExactly(
- "test.safeargs.sub1.Fragment2Args: androidx.navigation.NavArgs",
- "test.safeargs.sub1.Fragment2Directions"
- )
+ assertThat(classesMetadata2.map { it.toString() })
+ .containsExactly(
+ "test.safeargs.sub1.Fragment2Args: androidx.navigation.NavArgs",
+ "test.safeargs.sub1.Fragment2Directions"
+ )
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/HighlightingTest.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/HighlightingTest.kt
index 591412b..4f19028 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/HighlightingTest.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/HighlightingTest.kt
@@ -27,21 +27,21 @@
import com.intellij.testFramework.EdtRule
import com.intellij.testFramework.RunsInEdt
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
+import java.io.File
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
-import java.io.File
@RunsInEdt
class HighlightingTest {
private val projectRule = AndroidGradleProjectRule()
// The tests need to run on the EDT thread but we must initialize the project rule off of it
- @get:Rule
- val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
+ @get:Rule val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
- private val fixture get() = projectRule.fixture as JavaCodeInsightTestFixture
+ private val fixture
+ get() = projectRule.fixture as JavaCodeInsightTestFixture
@Before
fun setUp() {
@@ -56,7 +56,9 @@
// language=kotlin
"""
class FooClass
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
NavigationResourcesModificationListener.ensureSubscribed(fixture.project)
@@ -66,7 +68,10 @@
fun testDestructuringDeclaration() {
projectRule.requestSyncAndWait()
- val file = fixture.project.findAppModule().fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
+ val file =
+ fixture.project
+ .findAppModule()
+ .fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
WriteCommandAction.runWriteCommandAction(fixture.project) {
file!!.setText(
// language=kotlin
@@ -77,11 +82,13 @@
val (arg1, arg2) = FirstFragmentArgs(1, 2)
}
}
- """.trimIndent(),
- fixture.project)
+ """
+ .trimIndent(),
+ fixture.project
+ )
}
fixture.configureFromExistingVirtualFile(file!!)
val highlightInfos = fixture.doHighlighting(HighlightSeverity.ERROR)
assertThat(highlightInfos).isEmpty()
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsGeneratedKotlinCodeMatchTest.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsGeneratedKotlinCodeMatchTest.kt
index 27fcf29..0ab29ad 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsGeneratedKotlinCodeMatchTest.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsGeneratedKotlinCodeMatchTest.kt
@@ -28,6 +28,7 @@
import com.intellij.testFramework.EdtRule
import com.intellij.testFramework.RunsInEdt
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
+import java.io.File
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
@@ -54,40 +55,42 @@
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TemporaryFolder
-import java.io.File
@RunsInEdt
class SafeArgsGeneratedKotlinCodeMatchTest {
private val moduleName = "kotlinapp"
private val projectRule = AndroidGradleProjectRule()
- private val fixture get() = projectRule.fixture as JavaCodeInsightTestFixture
- //TODO (b/162520387): Do not ignore these methods when testing.
- private val IGNORED_METHODS = setOf("equals", "hashCode", "toString", "getActionId", "getArguments")
+ private val fixture
+ get() = projectRule.fixture as JavaCodeInsightTestFixture
+ // TODO (b/162520387): Do not ignore these methods when testing.
+ private val IGNORED_METHODS =
+ setOf("equals", "hashCode", "toString", "getActionId", "getArguments")
- @get:Rule
- val expect: Expect = Expect.create()
+ @get:Rule val expect: Expect = Expect.create()
- @get:Rule
- val temporaryFolder = TemporaryFolder()
+ @get:Rule val temporaryFolder = TemporaryFolder()
-
- @get:Rule
- val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
+ @get:Rule val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
@Before
fun initProject() {
// to be able to change the project before import, we copy it into a temp folder
- val testSrc = resolveWorkspacePath("tools/adt/idea/nav/safeargs/testData/projects/SafeArgsTestApp")
+ val testSrc =
+ resolveWorkspacePath("tools/adt/idea/nav/safeargs/testData/projects/SafeArgsTestApp")
val container = temporaryFolder.newFile("TestApp")
testSrc.toFile().copyRecursively(container, overwrite = true)
- val settingsFile = container.resolve("settings.gradle").also {
- assertWithMessage("settings file should exist").that(it.exists()).isTrue()
- }
+ val settingsFile =
+ container.resolve("settings.gradle").also {
+ assertWithMessage("settings file should exist").that(it.exists()).isTrue()
+ }
// update settings to only include the desired module
- settingsFile.writeText("""
+ settingsFile.writeText(
+ """
include ':$moduleName'
- """.trimIndent())
+ """
+ .trimIndent()
+ )
projectRule.fixture.testDataPath = temporaryFolder.root.absolutePath
projectRule.load("TestApp") { projectRoot ->
@@ -99,7 +102,9 @@
// language=kotlin
"""
class FooClass
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
@@ -114,9 +119,12 @@
assertThat(assembleDebug.isBuildSuccessful).isTrue()
LocalFileSystem.getInstance().refresh(false)
- val codeOutDir = File(projectRule.project.basePath, "$moduleName/$PLUGIN_OUT_DIR").also {
- assertWithMessage("should be able to find generated navigation code").that(it.exists()).isTrue()
- }
+ val codeOutDir =
+ File(projectRule.project.basePath, "$moduleName/$PLUGIN_OUT_DIR").also {
+ assertWithMessage("should be able to find generated navigation code")
+ .that(it.exists())
+ .isTrue()
+ }
// parse generated code
val allGeneratedCode = listOf(codeOutDir).flatMap(::loadClasses).toSet()
// delete generated code
@@ -127,103 +135,146 @@
// now find all that code via other means (in memory codegen) and assert it is the same.
val moduleDescriptor = projectRule.project.findAppModule().getMainModule().toDescriptor()!!
- moduleDescriptor.resolveClassByFqName(FqName("com.example.safeargtest.Foo"), NoLookupLocation.WHEN_FIND_BY_FQNAME)
+ moduleDescriptor.resolveClassByFqName(
+ FqName("com.example.safeargtest.Foo"),
+ NoLookupLocation.WHEN_FIND_BY_FQNAME
+ )
allGeneratedCode.forEach { generated ->
-
- val classDescriptor = if (generated.isCompanionObject) {
- moduleDescriptor.resolveClassByFqName(FqName(generated.qualifiedName).parent(), NoLookupLocation.WHEN_FIND_BY_FQNAME)
- ?.companionObjectDescriptor?.toDescription()
- }
- else {
- moduleDescriptor.resolveClassByFqName(FqName(generated.qualifiedName), NoLookupLocation.WHEN_FIND_BY_FQNAME)
- ?.toDescription()
- }
+ val classDescriptor =
+ if (generated.isCompanionObject) {
+ moduleDescriptor
+ .resolveClassByFqName(
+ FqName(generated.qualifiedName).parent(),
+ NoLookupLocation.WHEN_FIND_BY_FQNAME
+ )
+ ?.companionObjectDescriptor
+ ?.toDescription()
+ } else {
+ moduleDescriptor
+ .resolveClassByFqName(
+ FqName(generated.qualifiedName),
+ NoLookupLocation.WHEN_FIND_BY_FQNAME
+ )
+ ?.toDescription()
+ }
expect.withMessage(generated.qualifiedName).that(classDescriptor).isNotNull()
classDescriptor!!.let {
- expect.withMessage(generated.qualifiedName).that(classDescriptor.qualifiedName).isEqualTo(generated.qualifiedName)
- expect.withMessage(generated.qualifiedName).that(classDescriptor.constructor).isEqualTo(generated.constructor)
- expect.withMessage(generated.qualifiedName).that(classDescriptor.methods).containsExactlyElementsIn(generated.methods)
- expect.withMessage(generated.qualifiedName).that(classDescriptor.fields).containsExactlyElementsIn(generated.fields)
+ expect
+ .withMessage(generated.qualifiedName)
+ .that(classDescriptor.qualifiedName)
+ .isEqualTo(generated.qualifiedName)
+ expect
+ .withMessage(generated.qualifiedName)
+ .that(classDescriptor.constructor)
+ .isEqualTo(generated.constructor)
+ expect
+ .withMessage(generated.qualifiedName)
+ .that(classDescriptor.methods)
+ .containsExactlyElementsIn(generated.methods)
+ expect
+ .withMessage(generated.qualifiedName)
+ .that(classDescriptor.fields)
+ .containsExactlyElementsIn(generated.fields)
}
}
}
private fun loadClasses(classesOut: File): List<ClassDescription> {
- return classesOut.walkTopDown().filter {
- it.name.endsWith("kt")
- }.toList().flatMap { generatedSourceFile ->
- generatedSourceFile.loadClassesDescriptions()
- }
+ return classesOut
+ .walkTopDown()
+ .filter { it.name.endsWith("kt") }
+ .toList()
+ .flatMap { generatedSourceFile -> generatedSourceFile.loadClassesDescriptions() }
}
private fun File.loadClassesDescriptions(): List<ClassDescription> {
val descriptions = mutableListOf<ClassDescription>()
- val virtual = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(this) ?: throw IllegalArgumentException("cannot find $this")
+ val virtual =
+ LocalFileSystem.getInstance().refreshAndFindFileByIoFile(this)
+ ?: throw IllegalArgumentException("cannot find $this")
val psi = PsiManager.getInstance(projectRule.project).findFile(virtual)
val uast = psi.toUElement()!!
- uast.accept(object : AbstractUastVisitor() {
- override fun visitClass(node: UClass): Boolean {
- val descriptor = (node as KotlinUClass).sourcePsi?.descriptor as? ClassDescriptor
- descriptor?.takeIf { it.visibility == DescriptorVisibilities.PUBLIC }?.toDescription()?.let {
- descriptions.add(it)
+ uast.accept(
+ object : AbstractUastVisitor() {
+ override fun visitClass(node: UClass): Boolean {
+ val descriptor = (node as KotlinUClass).sourcePsi?.descriptor as? ClassDescriptor
+ descriptor
+ ?.takeIf { it.visibility == DescriptorVisibilities.PUBLIC }
+ ?.toDescription()
+ ?.let { descriptions.add(it) }
+ return super.visitClass(node)
}
- return super.visitClass(node)
}
- })
+ )
return descriptions
}
- private fun ClassDescriptor.toDescription() = ClassDescription(
- isCompanionObject = this.isCompanionObject,
- qualifiedName = this.fqNameSafe.asString(),
- constructor = (this.unsubstitutedPrimaryConstructor as? FunctionDescriptor)
- ?.takeIf { it.visibility == DescriptorVisibilities.PUBLIC }
- ?.toDescription(),
- methods = this.unsubstitutedMemberScope.getContributedDescriptors(DescriptorKindFilter.FUNCTIONS)
- .asSequence()
- .filterIsInstance<FunctionDescriptor>()
- .filter { it.visibility == DescriptorVisibilities.PUBLIC }
- .map { it.toDescription() }
- .filter { !IGNORED_METHODS.contains(it.name) }
- .sortedBy { it.name }
- .toSet(),
- fields = this.unsubstitutedMemberScope.getContributedDescriptors(DescriptorKindFilter.VARIABLES)
- .asSequence()
- .filterIsInstance<PropertyDescriptor>()
- .map { it.toDescription() }
- .sortedBy { it.name }
- .toSet()
- )
+ private fun ClassDescriptor.toDescription() =
+ ClassDescription(
+ isCompanionObject = this.isCompanionObject,
+ qualifiedName = this.fqNameSafe.asString(),
+ constructor =
+ (this.unsubstitutedPrimaryConstructor as? FunctionDescriptor)
+ ?.takeIf { it.visibility == DescriptorVisibilities.PUBLIC }
+ ?.toDescription(),
+ methods =
+ this.unsubstitutedMemberScope
+ .getContributedDescriptors(DescriptorKindFilter.FUNCTIONS)
+ .asSequence()
+ .filterIsInstance<FunctionDescriptor>()
+ .filter { it.visibility == DescriptorVisibilities.PUBLIC }
+ .map { it.toDescription() }
+ .filter { !IGNORED_METHODS.contains(it.name) }
+ .sortedBy { it.name }
+ .toSet(),
+ fields =
+ this.unsubstitutedMemberScope
+ .getContributedDescriptors(DescriptorKindFilter.VARIABLES)
+ .asSequence()
+ .filterIsInstance<PropertyDescriptor>()
+ .map { it.toDescription() }
+ .sortedBy { it.name }
+ .toSet()
+ )
- private fun FunctionDescriptor.toDescription() = MethodDescription(
- name = this.name.asString(),
- type = this.returnType?.toDescription(),
- params = this.valueParameters.map { it.toDescription() }.toSet(),
- modifiers = setOf(this.visibility.toString(), this.modality.toString())
- )
+ private fun FunctionDescriptor.toDescription() =
+ MethodDescription(
+ name = this.name.asString(),
+ type = this.returnType?.toDescription(),
+ params = this.valueParameters.map { it.toDescription() }.toSet(),
+ modifiers = setOf(this.visibility.toString(), this.modality.toString())
+ )
- private fun PropertyDescriptor.toDescription() = FieldDescription(
- name = this.name.asString(),
- type = this.type.toDescription(),
- modifiers = setOf(this.visibility.toString(), this.modality.toString())
- )
+ private fun PropertyDescriptor.toDescription() =
+ FieldDescription(
+ name = this.name.asString(),
+ type = this.type.toDescription(),
+ modifiers = setOf(this.visibility.toString(), this.modality.toString())
+ )
- private fun ValueParameterDescriptor.toDescription() = ParamDescription(
- name = this.name.asString(),
- type = this.type.toDescription(),
- modifiers = setOf(this.visibility.toString())
- )
+ private fun ValueParameterDescriptor.toDescription() =
+ ParamDescription(
+ name = this.name.asString(),
+ type = this.type.toDescription(),
+ modifiers = setOf(this.visibility.toString())
+ )
private fun KotlinType.toDescription(): String {
val type = if (this.isMarkedNullable) this.makeNullable() else this
return when (type) {
- // Note: References to dependencies are not working when generating sources, e.g. NavDirections, but they are
- // not critical to verifying safe args behavior, so we're OK simply peeling the class name out of the error type
+ // Note: References to dependencies are not working when generating sources, e.g.
+ // NavDirections, but they are
+ // not critical to verifying safe args behavior, so we're OK simply peeling the class name out
+ // of the error type
// for now.
- is ErrorType -> type.debugMessage.removePrefix("Unresolved type for ").substringAfterLast('.').substringAfterLast('$')
+ is ErrorType ->
+ type.debugMessage
+ .removePrefix("Unresolved type for ")
+ .substringAfterLast('.')
+ .substringAfterLast('$')
else -> type.fqName!!.shortName().asString().substringAfterLast('$')
}
}
@@ -240,20 +291,23 @@
val name: String,
val type: String?,
val modifiers: Set<String>,
- val params: Set<ParamDescription>)
+ val params: Set<ParamDescription>
+ )
private data class FieldDescription(
val name: String,
val type: String,
- val modifiers: Set<String>)
+ val modifiers: Set<String>
+ )
private data class ParamDescription(
val name: String,
val type: String,
- val modifiers: Set<String>)
+ val modifiers: Set<String>
+ )
companion object {
const val PLUGIN_OUT_DIR = "build/generated/source/navigation-args/debug"
const val GENERATE_TASK = "generateSafeArgsDebug"
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsIconsRenderingTest.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsIconsRenderingTest.kt
index 053a281..fc29ac85 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsIconsRenderingTest.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsIconsRenderingTest.kt
@@ -30,22 +30,22 @@
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import com.intellij.ui.IconManager
import com.intellij.ui.PlatformIcons
+import java.io.File
import org.jetbrains.kotlin.idea.KotlinIcons
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
-import java.io.File
@RunsInEdt
class SafeArgsIconsRenderingTest {
private val projectRule = AndroidGradleProjectRule()
// The tests need to run on the EDT thread but we must initialize the project rule off of it
- @get:Rule
- val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
+ @get:Rule val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
- private val fixture get() = projectRule.fixture as JavaCodeInsightTestFixture
+ private val fixture
+ get() = projectRule.fixture as JavaCodeInsightTestFixture
@Before
fun setUp() {
@@ -61,7 +61,9 @@
"""
package com.example.myapplication
class FooClass
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
NavigationResourcesModificationListener.ensureSubscribed(fixture.project)
@@ -71,11 +73,14 @@
fun testClassIcons() {
projectRule.requestSyncAndWait()
- val file = fixture.project.findAppModule().fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
+ val file =
+ fixture.project
+ .findAppModule()
+ .fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
WriteCommandAction.runWriteCommandAction(fixture.project) {
file!!.replaceWithSaving(
"class FooClass",
- //language=kotlin
+ // language=kotlin
"""
class FooClass {
fun myTest() {
@@ -83,8 +88,10 @@
val directionsClass =
}
}
- """.trimIndent(),
- fixture.project)
+ """
+ .trimIndent(),
+ fixture.project
+ )
}
fixture.openFileInEditor(file!!)
@@ -93,32 +100,39 @@
fixture.moveCaret("val argsClass = |")
fixture.type("Args")
fixture.completeBasic()
- var icons = fixture.lookupElements!!
- .filter { it.lookupString.endsWith("FragmentArgs") }
- .map { DefaultLookupItemRenderer.getRawIcon(it) }
- .toSet()
- assertThat(icons).containsExactly(IconManager.getInstance().getPlatformIcon(PlatformIcons.Class))
+ var icons =
+ fixture.lookupElements!!
+ .filter { it.lookupString.endsWith("FragmentArgs") }
+ .map { DefaultLookupItemRenderer.getRawIcon(it) }
+ .toSet()
+ assertThat(icons)
+ .containsExactly(IconManager.getInstance().getPlatformIcon(PlatformIcons.Class))
// check directions classes
fixture.moveCaret("val directionsClass = |")
fixture.type("Directions")
fixture.completeBasic()
- icons = fixture.lookupElements!!
- .filter { it.lookupString.endsWith("FragmentDirections") }
- .mapNotNull { DefaultLookupItemRenderer.getRawIcon(it) }
- .toSet()
- assertThat(icons).containsExactly(IconManager.getInstance().getPlatformIcon(PlatformIcons.Class))
+ icons =
+ fixture.lookupElements!!
+ .filter { it.lookupString.endsWith("FragmentDirections") }
+ .mapNotNull { DefaultLookupItemRenderer.getRawIcon(it) }
+ .toSet()
+ assertThat(icons)
+ .containsExactly(IconManager.getInstance().getPlatformIcon(PlatformIcons.Class))
}
@Test
fun testMethodAndPropertyIcons() {
projectRule.requestSyncAndWait()
- val file = fixture.project.findAppModule().fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
+ val file =
+ fixture.project
+ .findAppModule()
+ .fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
WriteCommandAction.runWriteCommandAction(fixture.project) {
file!!.replaceWithSaving(
"class FooClass",
- //language=kotlin
+ // language=kotlin
"""
class FooClass {
fun myTest() {
@@ -129,8 +143,10 @@
val directionsClass2 = SecondFragmentDirections().
}
}
- """.trimIndent(),
- fixture.project)
+ """
+ .trimIndent(),
+ fixture.project
+ )
}
fixture.openFileInEditor(file!!)
@@ -138,33 +154,43 @@
// check static method from args class
fixture.moveCaret("val argsClass1 = SecondFragmentArgs.|")
fixture.completeBasic()
- var icons = fixture.lookupElements!!
- .map { it.lookupString to DefaultLookupItemRenderer.getRawIcon(it) }
- .toSet()
- assertThat(icons).contains("fromBundle" to IconManager.getInstance().getPlatformIcon(PlatformIcons.Function))
+ var icons =
+ fixture.lookupElements!!
+ .map { it.lookupString to DefaultLookupItemRenderer.getRawIcon(it) }
+ .toSet()
+ assertThat(icons)
+ .contains("fromBundle" to IconManager.getInstance().getPlatformIcon(PlatformIcons.Function))
// check static method from directions class
fixture.moveCaret("val directionsClass1 = SecondFragmentDirections.|")
fixture.completeBasic()
- icons = fixture.lookupElements!!
- .mapNotNull { it.lookupString to DefaultLookupItemRenderer.getRawIcon(it) }
- .toSet()
- assertThat(icons).contains("actionSecondFragmentToFirstFragment" to IconManager.getInstance().getPlatformIcon(PlatformIcons.Function))
+ icons =
+ fixture.lookupElements!!
+ .mapNotNull { it.lookupString to DefaultLookupItemRenderer.getRawIcon(it) }
+ .toSet()
+ assertThat(icons)
+ .contains(
+ "actionSecondFragmentToFirstFragment" to
+ IconManager.getInstance().getPlatformIcon(PlatformIcons.Function)
+ )
// check methods from args class
fixture.moveCaret("val argsClass2 = SecondFragmentArgs().|")
fixture.completeBasic()
- icons = fixture.lookupElements!!
- .map { it.lookupString to DefaultLookupItemRenderer.getRawIcon(it) }
- .toSet()
- assertThat(icons).containsAllOf(
- // componentN() functions of data class are filtered out when collecting variants during completions.
- "arg1" to KotlinIcons.FIELD_VAL,
- "copy" to IconManager.getInstance().getPlatformIcon(PlatformIcons.Function),
- "toBundle" to IconManager.getInstance().getPlatformIcon(PlatformIcons.Function)
- )
+ icons =
+ fixture.lookupElements!!
+ .map { it.lookupString to DefaultLookupItemRenderer.getRawIcon(it) }
+ .toSet()
+ assertThat(icons)
+ .containsAllOf(
+ // componentN() functions of data class are filtered out when collecting variants during
+ // completions.
+ "arg1" to KotlinIcons.FIELD_VAL,
+ "copy" to IconManager.getInstance().getPlatformIcon(PlatformIcons.Function),
+ "toBundle" to IconManager.getInstance().getPlatformIcon(PlatformIcons.Function)
+ )
// directions class only has companion object
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsImportKtResolverTest.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsImportKtResolverTest.kt
index 3a3f67e..888ed69 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsImportKtResolverTest.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsImportKtResolverTest.kt
@@ -27,21 +27,21 @@
import com.intellij.testFramework.EdtRule
import com.intellij.testFramework.RunsInEdt
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
+import java.io.File
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
-import java.io.File
@RunsInEdt
class SafeArgsImportKtResolverTest {
private val projectRule = AndroidGradleProjectRule()
// The tests need to run on the EDT thread but we must initialize the project rule off of it
- @get:Rule
- val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
+ @get:Rule val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
- private val fixture get() = projectRule.fixture as JavaCodeInsightTestFixture
+ private val fixture
+ get() = projectRule.fixture as JavaCodeInsightTestFixture
@Before
fun setUp() {
@@ -61,7 +61,9 @@
val argsClass2 = FirstFragmentArgs().
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
NavigationResourcesModificationListener.ensureSubscribed(fixture.project)
@@ -71,12 +73,15 @@
fun testImportFixWithSingleSuggestion() {
projectRule.requestSyncAndWait()
- val file = fixture.project.findAppModule().fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
+ val file =
+ fixture.project
+ .findAppModule()
+ .fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
fixture.configureFromExistingVirtualFile(file!!)
// Before auto import fix
- val unresolvedReferences = fixture.doHighlighting()
- .filter { it.description?.contains("[UNRESOLVED_REFERENCE]") == true }
+ val unresolvedReferences =
+ fixture.doHighlighting().filter { it.description?.contains("[UNRESOLVED_REFERENCE]") == true }
assertThat(unresolvedReferences).hasSize(2)
@@ -97,17 +102,22 @@
val argsClass2 = FirstFragmentArgs().
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
fun testImportFixWithAmbiguities() {
projectRule.requestSyncAndWait()
- val file = fixture.project.findAppModule().fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
+ val file =
+ fixture.project
+ .findAppModule()
+ .fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
WriteCommandAction.runWriteCommandAction(fixture.project) {
file!!.setText(
- //language=kotlin
+ // language=kotlin
"""
class FooClass {
fun myTest() {
@@ -115,14 +125,16 @@
val argsClass2 = SecondFragmentArgs().
}
}
- """.trimIndent(),
- fixture.project)
+ """
+ .trimIndent(),
+ fixture.project
+ )
}
fixture.configureFromExistingVirtualFile(file!!)
// Before auto import fix
- val unresolvedReferences = fixture.doHighlighting()
- .filter { it.description?.contains("[UNRESOLVED_REFERENCE]") == true }
+ val unresolvedReferences =
+ fixture.doHighlighting().filter { it.description?.contains("[UNRESOLVED_REFERENCE]") == true }
assertThat(unresolvedReferences).hasSize(2)
@@ -144,17 +156,22 @@
val argsClass2 = SecondFragmentArgs().
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
@Test
fun testImportFixForCompanionFunctions() {
projectRule.requestSyncAndWait()
- val file = fixture.project.findAppModule().fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
+ val file =
+ fixture.project
+ .findAppModule()
+ .fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
WriteCommandAction.runWriteCommandAction(fixture.project) {
file!!.setText(
- //language=kotlin
+ // language=kotlin
"""
package com.example.myapplication
@@ -163,14 +180,16 @@
val argsClass1 = from${caret}Bundle()
}
}
- """.trimIndent(),
- fixture.project)
+ """
+ .trimIndent(),
+ fixture.project
+ )
}
fixture.configureFromExistingVirtualFile(file!!)
// Before auto import fix
- val unresolvedReferences = fixture.doHighlighting()
- .filter { it.description?.contains("[UNRESOLVED_REFERENCE]") == true }
+ val unresolvedReferences =
+ fixture.doHighlighting().filter { it.description?.contains("[UNRESOLVED_REFERENCE]") == true }
assertThat(unresolvedReferences).hasSize(1)
@@ -189,6 +208,8 @@
val argsClass1 = from${caret}Bundle()
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsKtCompletionContributorTest.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsKtCompletionContributorTest.kt
index 2d394c1..632ad8f 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsKtCompletionContributorTest.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsKtCompletionContributorTest.kt
@@ -28,21 +28,21 @@
import com.intellij.testFramework.EdtRule
import com.intellij.testFramework.RunsInEdt
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
+import java.io.File
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
-import java.io.File
@RunsInEdt
class SafeArgsKtCompletionContributorTest {
private val projectRule = AndroidGradleProjectRule()
// The tests need to run on the EDT thread but we must initialize the project rule off of it
- @get:Rule
- val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
+ @get:Rule val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
- private val fixture get() = projectRule.fixture as JavaCodeInsightTestFixture
+ private val fixture
+ get() = projectRule.fixture as JavaCodeInsightTestFixture
@Before
fun setUp() {
@@ -58,7 +58,9 @@
"""
package com.example.myapplication
class FooClass
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
@@ -68,18 +70,21 @@
/**
* Check args and directions classes shown up in the completions
*
- * Test Project structure:
- * base app module(safe arg mode is on) --> lib dep module(safe arg mode is on)
+ * Test Project structure: base app module(safe arg mode is on) --> lib dep module(safe arg mode
+ * is on)
*/
@Test
fun testBasicCompletion() {
projectRule.requestSyncAndWait()
- val file = fixture.project.findAppModule().fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
+ val file =
+ fixture.project
+ .findAppModule()
+ .fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
WriteCommandAction.runWriteCommandAction(fixture.project) {
file!!.replaceWithSaving(
"class FooClass",
- //language=kotlin
+ // language=kotlin
"""
class FooClass {
fun myTest() {
@@ -88,8 +93,10 @@
val generatedClass =
}
}
- """.trimIndent(),
- fixture.project)
+ """
+ .trimIndent(),
+ fixture.project
+ )
}
fixture.openFileInEditor(file!!)
@@ -98,73 +105,86 @@
fixture.moveCaret("val argsClass = |")
fixture.type("Args")
fixture.completeBasic()
- val argsElements = fixture.lookupElements!!
- .map {
- val presentation = LookupElementPresentation()
- it.renderElement(presentation)
- presentation
- }
- .map { it.itemText + " " + it.tailText }
+ val argsElements =
+ fixture.lookupElements!!
+ .map {
+ val presentation = LookupElementPresentation()
+ it.renderElement(presentation)
+ presentation
+ }
+ .map { it.itemText + " " + it.tailText }
- assertThat(argsElements).containsAllOf(
- "FirstFragmentArgs (com.example.mylibrary)",
- "SecondFragmentArgs (com.example.myapplication)",
- "SecondFragmentArgs (com.example.mylibrary)")
+ assertThat(argsElements)
+ .containsAllOf(
+ "FirstFragmentArgs (com.example.mylibrary)",
+ "SecondFragmentArgs (com.example.myapplication)",
+ "SecondFragmentArgs (com.example.mylibrary)"
+ )
// check directions classes
fixture.moveCaret("val directionsClass = |")
fixture.type("Directions")
fixture.completeBasic()
- val directionsElements = fixture.lookupElements!!
- .map {
- val presentation = LookupElementPresentation()
- it.renderElement(presentation)
- presentation
- }
- .map { it.itemText + " " + it.tailText }
+ val directionsElements =
+ fixture.lookupElements!!
+ .map {
+ val presentation = LookupElementPresentation()
+ it.renderElement(presentation)
+ presentation
+ }
+ .map { it.itemText + " " + it.tailText }
- assertThat(directionsElements).containsAllOf(
- "SecondFragmentDirections (com.example.myapplication)",
- "FirstFragmentDirections (com.example.mylibrary)",
- "SecondFragmentDirections (com.example.mylibrary)")
-
+ assertThat(directionsElements)
+ .containsAllOf(
+ "SecondFragmentDirections (com.example.myapplication)",
+ "FirstFragmentDirections (com.example.mylibrary)",
+ "SecondFragmentDirections (com.example.mylibrary)"
+ )
// check all safe args classes
fixture.moveCaret("val generatedClass = |")
fixture.completeBasic()
- val allElements = fixture.lookupElements!!
- .map {
- val presentation = LookupElementPresentation()
- it.renderElement(presentation)
- presentation
- }
- .filter { it.itemText!!.endsWith("Args") || it.itemText!!.endsWith("Directions") }
- .map { it.itemText + " " + it.tailText }
+ val allElements =
+ fixture.lookupElements!!
+ .map {
+ val presentation = LookupElementPresentation()
+ it.renderElement(presentation)
+ presentation
+ }
+ .filter { it.itemText!!.endsWith("Args") || it.itemText!!.endsWith("Directions") }
+ .map { it.itemText + " " + it.tailText }
- assertThat(allElements).containsAllOf(
- "FirstFragmentArgs (com.example.mylibrary)",
- "SecondFragmentArgs (com.example.myapplication)",
- "SecondFragmentArgs (com.example.mylibrary)",
- "SecondFragmentDirections (com.example.myapplication)",
- "FirstFragmentDirections (com.example.mylibrary)",
- "SecondFragmentDirections (com.example.mylibrary)")
+ assertThat(allElements)
+ .containsAllOf(
+ "FirstFragmentArgs (com.example.mylibrary)",
+ "SecondFragmentArgs (com.example.myapplication)",
+ "SecondFragmentArgs (com.example.mylibrary)",
+ "SecondFragmentDirections (com.example.myapplication)",
+ "FirstFragmentDirections (com.example.mylibrary)",
+ "SecondFragmentDirections (com.example.mylibrary)"
+ )
}
@Test
fun testCompletionInImports() {
projectRule.requestSyncAndWait()
- val file = fixture.project.findAppModule().fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
+ val file =
+ fixture.project
+ .findAppModule()
+ .fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
WriteCommandAction.runWriteCommandAction(fixture.project) {
file!!.replaceWithSaving(
"class FooClass",
- //language=kotlin
+ // language=kotlin
"""
import com.
import com.example.mylibrary.
class FooClass
- """.trimIndent(),
- fixture.project)
+ """
+ .trimIndent(),
+ fixture.project
+ )
}
fixture.openFileInEditor(file!!)
@@ -185,38 +205,46 @@
// (Just for sanity check, completions are not provided by [SafeArgsKtCompletionContributor])
fixture.moveCaret("import com.example.mylibrary.|")
fixture.completeBasic()
- val allElements = fixture.lookupElements!!
- .map {
- val presentation = LookupElementPresentation()
- it.renderElement(presentation)
- presentation
- }
- .filter { it.itemText!!.endsWith("Args") || it.itemText!!.endsWith("Directions") }
- .map { it.itemText + " " + it.tailText }
+ val allElements =
+ fixture.lookupElements!!
+ .map {
+ val presentation = LookupElementPresentation()
+ it.renderElement(presentation)
+ presentation
+ }
+ .filter { it.itemText!!.endsWith("Args") || it.itemText!!.endsWith("Directions") }
+ .map { it.itemText + " " + it.tailText }
- assertThat(allElements).containsAllOf(
- "FirstFragmentArgs (com.example.mylibrary)",
- "FirstFragmentDirections (com.example.mylibrary)",
- "SecondFragmentArgs (com.example.mylibrary)",
- "SecondFragmentDirections (com.example.mylibrary)")
+ assertThat(allElements)
+ .containsAllOf(
+ "FirstFragmentArgs (com.example.mylibrary)",
+ "FirstFragmentDirections (com.example.mylibrary)",
+ "SecondFragmentArgs (com.example.mylibrary)",
+ "SecondFragmentDirections (com.example.mylibrary)"
+ )
}
@Test
fun testCompletionWithReceiver() {
projectRule.requestSyncAndWait()
- val file = fixture.project.findAppModule().fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
+ val file =
+ fixture.project
+ .findAppModule()
+ .fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
WriteCommandAction.runWriteCommandAction(fixture.project) {
file!!.replaceWithSaving(
"class FooClass",
- //language=kotlin
+ // language=kotlin
"""
class FooClass {
val a = com.
val b = com.example.mylibrary.
}
- """.trimIndent(),
- fixture.project)
+ """
+ .trimIndent(),
+ fixture.project
+ )
}
fixture.openFileInEditor(file!!)
@@ -237,19 +265,22 @@
// (Just for sanity check, completions are not provided by [SafeArgsKtCompletionContributor])
fixture.moveCaret("com.example.mylibrary.|")
fixture.completeBasic()
- val allElements = fixture.lookupElements!!
- .map {
- val presentation = LookupElementPresentation()
- it.renderElement(presentation)
- presentation
- }
- .filter { it.itemText!!.endsWith("Args") || it.itemText!!.endsWith("Directions") }
- .map { it.itemText + " " + it.tailText }
+ val allElements =
+ fixture.lookupElements!!
+ .map {
+ val presentation = LookupElementPresentation()
+ it.renderElement(presentation)
+ presentation
+ }
+ .filter { it.itemText!!.endsWith("Args") || it.itemText!!.endsWith("Directions") }
+ .map { it.itemText + " " + it.tailText }
- assertThat(allElements).containsAllOf(
- "FirstFragmentArgs (com.example.mylibrary)",
- "FirstFragmentDirections (com.example.mylibrary)",
- "SecondFragmentArgs (com.example.mylibrary)",
- "SecondFragmentDirections (com.example.mylibrary)")
+ assertThat(allElements)
+ .containsAllOf(
+ "FirstFragmentArgs (com.example.mylibrary)",
+ "FirstFragmentDirections (com.example.mylibrary)",
+ "SecondFragmentArgs (com.example.mylibrary)",
+ "SecondFragmentDirections (com.example.mylibrary)"
+ )
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsKtFindingUsageTest.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsKtFindingUsageTest.kt
index a9c6a46..3267f87 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsKtFindingUsageTest.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsKtFindingUsageTest.kt
@@ -29,21 +29,21 @@
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import com.intellij.usages.PsiElementUsageTarget
import com.intellij.usages.UsageTargetUtil
+import java.io.File
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
-import java.io.File
@RunsInEdt
class SafeArgsKtFindingUsageTest {
private val projectRule = AndroidGradleProjectRule()
// The tests need to run on the EDT thread but we must initialize the project rule off of it
- @get:Rule
- val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
+ @get:Rule val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
- private val fixture get() = projectRule.fixture as JavaCodeInsightTestFixture
+ private val fixture
+ get() = projectRule.fixture as JavaCodeInsightTestFixture
@Before
fun setUp() {
@@ -67,7 +67,9 @@
val argsClass2 = FirstFragmentArgs().
}
}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
NavigationResourcesModificationListener.ensureSubscribed(fixture.project)
@@ -78,11 +80,16 @@
projectRule.requestSyncAndWait()
val appModuleMain = fixture.project.findAppModule().getMainModule()
- val file = appModuleMain.fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
+ val file =
+ appModuleMain.fileUnderGradleRoot("src/main/java/com/example/myapplication/FooClass.kt")
fixture.configureFromExistingVirtualFile(file!!)
- val targets = UsageTargetUtil.findUsageTargets { (fixture.editor as EditorEx).dataContext.getData(it) }
- val presentation = fixture.getUsageViewTreeTextRepresentation((targets.first() as PsiElementUsageTarget).element)
- assertThat(presentation).isEqualTo("""
+ val targets =
+ UsageTargetUtil.findUsageTargets { (fixture.editor as EditorEx).dataContext.getData(it) }
+ val presentation =
+ fixture.getUsageViewTreeTextRepresentation((targets.first() as PsiElementUsageTarget).element)
+ assertThat(presentation)
+ .isEqualTo(
+ """
<root> (3)
XML tag
<FirstFragmentArgs> of file nav_graph.xml
@@ -107,6 +114,8 @@
FooClass.kt (1)
3import com.example.mylibrary.FirstFragmentArgs
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
-}
\ No newline at end of file
+}
diff --git a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsKtPackageDescriptorTestMultiKtModules.kt b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsKtPackageDescriptorTestMultiKtModules.kt
index 6bc3f79..dd63861 100644
--- a/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsKtPackageDescriptorTestMultiKtModules.kt
+++ b/nav/safeargs/tests/k1/src/com/android/tools/idea/nav/safeargs/kotlin/k1/gradle/SafeArgsKtPackageDescriptorTestMultiKtModules.kt
@@ -15,11 +15,9 @@
*/
package com.android.tools.idea.nav.safeargs.kotlin.gradle
-import com.android.flags.junit.FlagRule
-import com.android.tools.idea.flags.StudioFlags
import com.android.tools.idea.nav.safeargs.TestDataPaths
-import com.android.tools.idea.nav.safeargs.project.NavigationResourcesModificationListener
import com.android.tools.idea.nav.safeargs.kotlin.k1.classesInScope
+import com.android.tools.idea.nav.safeargs.project.NavigationResourcesModificationListener
import com.android.tools.idea.projectsystem.getMainModule
import com.android.tools.idea.testing.AndroidGradleProjectRule
import com.android.tools.idea.testing.findAppModule
@@ -40,10 +38,10 @@
private val projectRule = AndroidGradleProjectRule()
// The tests need to run on the EDT thread but we must initialize the project rule off of it
- @get:Rule
- val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
+ @get:Rule val ruleChain = RuleChain.outerRule(projectRule).around(EdtRule())!!
- private val fixture get() = projectRule.fixture as JavaCodeInsightTestFixture
+ private val fixture
+ get() = projectRule.fixture as JavaCodeInsightTestFixture
@Before
fun setUp() {
@@ -53,11 +51,11 @@
}
/**
- * Check contributed descriptors for base app and library module by providing fully qualified package names
+ * Check contributed descriptors for base app and library module by providing fully qualified
+ * package names
*
- * Test Project structure:
- * -base app module(safe arg mode is on) --> lib dep module(safe arg mode is on)
- * -excluded lib module(safe arg mode is on)
+ * Test Project structure: -base app module(safe arg mode is on) --> lib dep module(safe arg mode
+ * is on) -excluded lib module(safe arg mode is on)
*/
@Test
fun multiModuleTest() {
@@ -67,56 +65,64 @@
val appModule = fixture.project.findAppModule().getMainModule()
val appModuleDescriptor = appModule.toDescriptor()
- val classesMetadataAppPackageInApp = appModuleDescriptor!!
- .getPackage(FqName("com.example.myapplication"))
- .memberScope
- .classesInScope { name -> name.endsWith("Args") || name.endsWith("Directions") }
+ val classesMetadataAppPackageInApp =
+ appModuleDescriptor!!
+ .getPackage(FqName("com.example.myapplication"))
+ .memberScope
+ .classesInScope { name -> name.endsWith("Args") || name.endsWith("Directions") }
- assertThat(classesMetadataAppPackageInApp.map { it.fqcn to it.file }).containsExactly(
- "com.example.myapplication.SecondFragmentArgs" to "nav_graph.xml",
- "com.example.myapplication.SecondFragmentDirections" to "nav_graph.xml"
- )
+ assertThat(classesMetadataAppPackageInApp.map { it.fqcn to it.file })
+ .containsExactly(
+ "com.example.myapplication.SecondFragmentArgs" to "nav_graph.xml",
+ "com.example.myapplication.SecondFragmentDirections" to "nav_graph.xml"
+ )
// check contents when providing lib package name in app module.
- val classesMetadataLibPackageInApp = appModuleDescriptor
- .getPackage(FqName("com.example.mylibrary"))
- .memberScope
- .classesInScope { name -> name.endsWith("Args") || name.endsWith("Directions") }
+ val classesMetadataLibPackageInApp =
+ appModuleDescriptor.getPackage(FqName("com.example.mylibrary")).memberScope.classesInScope {
+ name ->
+ name.endsWith("Args") || name.endsWith("Directions")
+ }
// It collects 'packageFragmentProviderForWholeModuleWithDependencies'
- assertThat(classesMetadataLibPackageInApp.map { it.fqcn to it.file }).containsExactly(
- "com.example.mylibrary.FirstFragmentArgs" to "nav_graph.xml",
- "com.example.mylibrary.FirstFragmentDirections" to "nav_graph.xml",
- "com.example.mylibrary.SecondFragmentArgs" to "libnav_graph.xml",
- "com.example.mylibrary.SecondFragmentDirections" to "libnav_graph.xml"
- )
+ assertThat(classesMetadataLibPackageInApp.map { it.fqcn to it.file })
+ .containsExactly(
+ "com.example.mylibrary.FirstFragmentArgs" to "nav_graph.xml",
+ "com.example.mylibrary.FirstFragmentDirections" to "nav_graph.xml",
+ "com.example.mylibrary.SecondFragmentArgs" to "libnav_graph.xml",
+ "com.example.mylibrary.SecondFragmentDirections" to "libnav_graph.xml"
+ )
// check contents for library module
val libModule = fixture.project.findModule("mylibrary").getMainModule()
val libModuleDescriptor = libModule.toDescriptor()
- val classesMetadataInLib = libModuleDescriptor!!
- .getPackage(FqName("com.example.mylibrary"))
- .memberScope
- .classesInScope { name -> name.endsWith("Args") || name.endsWith("Directions") }
+ val classesMetadataInLib =
+ libModuleDescriptor!!
+ .getPackage(FqName("com.example.mylibrary"))
+ .memberScope
+ .classesInScope { name -> name.endsWith("Args") || name.endsWith("Directions") }
- assertThat(classesMetadataInLib.map { it.fqcn to it.file }).containsExactly(
- "com.example.mylibrary.SecondFragmentArgs" to "libnav_graph.xml",
- "com.example.mylibrary.SecondFragmentDirections" to "libnav_graph.xml"
- )
+ assertThat(classesMetadataInLib.map { it.fqcn to it.file })
+ .containsExactly(
+ "com.example.mylibrary.SecondFragmentArgs" to "libnav_graph.xml",
+ "com.example.mylibrary.SecondFragmentDirections" to "libnav_graph.xml"
+ )
// check contents for excluded library module
val libModuleExcluded = fixture.project.findModule("mylibraryexcluded").getMainModule()
val libModuleExcludedDescriptor = libModuleExcluded.toDescriptor()
- val classesMetadataInLibExcluded = libModuleExcludedDescriptor!!
- .getPackage(FqName("com.example.mylibrary"))
- .memberScope
- .classesInScope { name -> name.endsWith("Args") || name.endsWith("Directions") }
+ val classesMetadataInLibExcluded =
+ libModuleExcludedDescriptor!!
+ .getPackage(FqName("com.example.mylibrary"))
+ .memberScope
+ .classesInScope { name -> name.endsWith("Args") || name.endsWith("Directions") }
- assertThat(classesMetadataInLibExcluded.map { it.fqcn to it.file }).containsExactly(
- "com.example.mylibrary.SecondFragmentArgs" to "excludedlibnav_graph.xml",
- "com.example.mylibrary.SecondFragmentDirections" to "excludedlibnav_graph.xml"
- )
+ assertThat(classesMetadataInLibExcluded.map { it.fqcn to it.file })
+ .containsExactly(
+ "com.example.mylibrary.SecondFragmentArgs" to "excludedlibnav_graph.xml",
+ "com.example.mylibrary.SecondFragmentDirections" to "excludedlibnav_graph.xml"
+ )
}
-}
\ No newline at end of file
+}
diff --git a/old-agp-tests/BUILD b/old-agp-tests/BUILD
index 9ee6ee8..9270df4 100644
--- a/old-agp-tests/BUILD
+++ b/old-agp-tests/BUILD
@@ -1,6 +1,6 @@
load("//tools/base/bazel:bazel.bzl", "iml_module")
load("//tools/base/bazel:maven.bzl", "maven_repository")
-load(":agp_versions.bzl", "AGP_3_1_4", "AGP_3_3_2", "AGP_3_5", "AGP_4_0", "AGP_4_1", "AGP_4_2", "AGP_7_0", "AGP_7_1", "AGP_7_2", "AGP_7_3", "AGP_7_4", "AGP_8_0", "AGP_8_1", "AGP_8_2", "GRADLE_5_3_1", "GRADLE_5_5", "GRADLE_6_7_1", "GRADLE_7_0_2", "GRADLE_7_2", "GRADLE_7_3_3", "GRADLE_7_4", "GRADLE_7_5", "GRADLE_8_0", "GRADLE_8_2", "GRADLE_LATEST", "local_old_agp_test")
+load(":agp_versions.bzl", "AGP_3_1_4", "AGP_3_3_2", "AGP_3_5", "AGP_4_0", "AGP_4_1", "AGP_4_2", "AGP_7_0", "AGP_7_1", "AGP_7_2", "AGP_7_3", "AGP_7_4", "AGP_8_0", "AGP_8_1", "AGP_8_2", "GRADLE_5_3_1", "GRADLE_5_5", "GRADLE_6_7_1", "GRADLE_7_0_2", "GRADLE_7_2", "GRADLE_7_3_3", "GRADLE_7_4", "GRADLE_7_5", "GRADLE_8_0", "GRADLE_8_2", "GRADLE_LATEST", "generate_old_agp_tests_from_list")
# managed by go/iml_to_build
iml_module(
@@ -174,177 +174,113 @@
],
)
-local_old_agp_test(
- additional_jdks = ["11"],
- agp_version = AGP_3_1_4,
- gradle_version = GRADLE_5_3_1,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
+COMMON_OLD_AGP_TEST_TARGETS_TAGS = [
+ "block-network",
+ "no_test_mac",
+ "no_test_windows",
+]
-local_old_agp_test(
- additional_jdks = ["11"],
- agp_version = AGP_3_3_2,
- gradle_version = GRADLE_5_3_1,
- shard_count = 1,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
-
-local_old_agp_test(
- additional_jdks = ["11"],
- agp_version = AGP_3_3_2,
- gradle_version = GRADLE_5_5,
- shard_count = 1,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
-
-local_old_agp_test(
- additional_jdks = [
- "1.8",
- "11",
- ],
- agp_version = AGP_3_5,
- gradle_version = GRADLE_5_5,
- shard_count = 4,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
-
-local_old_agp_test(
- additional_jdks = ["11"],
- agp_version = AGP_4_0,
- gradle_version = GRADLE_6_7_1,
- shard_count = 3,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
-
-local_old_agp_test(
- additional_jdks = ["11"],
- agp_version = AGP_4_1,
- gradle_version = GRADLE_6_7_1,
- shard_count = 3,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
-
-local_old_agp_test(
- additional_jdks = ["11"],
- agp_version = AGP_4_2,
- gradle_version = GRADLE_6_7_1,
- shard_count = 4,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
-
-local_old_agp_test(
- additional_jdks = ["11"],
- agp_version = AGP_7_0,
- gradle_version = GRADLE_7_0_2,
- shard_count = 3,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
-
-local_old_agp_test(
- agp_version = AGP_7_1,
- gradle_version = GRADLE_7_2,
- shard_count = 3,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
-
-local_old_agp_test(
- agp_version = AGP_7_2,
- gradle_version = GRADLE_7_3_3,
- shard_count = 5,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
-
-local_old_agp_test(
- agp_version = AGP_7_3,
- gradle_version = GRADLE_7_4,
- shard_count = 5,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
-
-local_old_agp_test(
- agp_version = AGP_7_4,
- gradle_version = GRADLE_7_5,
- shard_count = 5,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
-
-local_old_agp_test(
- agp_version = AGP_8_0,
- gradle_version = GRADLE_8_0,
- shard_count = 5,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
-
-local_old_agp_test(
- agp_version = AGP_8_1,
- gradle_version = GRADLE_8_0,
- shard_count = 5,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
- ],
-)
-
-local_old_agp_test(
- agp_version = AGP_8_2,
- gradle_version = GRADLE_8_2,
- shard_count = 5,
- tags = [
- "block-network",
- "no_test_mac",
- "no_test_windows",
+generate_old_agp_tests_from_list(
+ tests_list = [
+ {
+ "additional_jdks": ["11"],
+ "agp_version": AGP_3_1_4,
+ "gradle_version": GRADLE_5_3_1,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "additional_jdks": ["11"],
+ "agp_version": AGP_3_3_2,
+ "gradle_version": GRADLE_5_3_1,
+ "shard_count": 1,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "additional_jdks": ["11"],
+ "agp_version": AGP_3_3_2,
+ "gradle_version": GRADLE_5_5,
+ "shard_count": 1,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "additional_jdks": [
+ "1.8",
+ "11",
+ ],
+ "agp_version": AGP_3_5,
+ "gradle_version": GRADLE_5_5,
+ "shard_count": 4,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "additional_jdks": ["11"],
+ "agp_version": AGP_4_0,
+ "gradle_version": GRADLE_6_7_1,
+ "shard_count": 3,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "additional_jdks": ["11"],
+ "agp_version": AGP_4_1,
+ "gradle_version": GRADLE_6_7_1,
+ "shard_count": 3,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "additional_jdks": ["11"],
+ "agp_version": AGP_4_2,
+ "gradle_version": GRADLE_6_7_1,
+ "shard_count": 4,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "additional_jdks": ["11"],
+ "agp_version": AGP_7_0,
+ "gradle_version": GRADLE_7_0_2,
+ "shard_count": 3,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "agp_version": AGP_7_1,
+ "gradle_version": GRADLE_7_2,
+ "shard_count": 3,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "agp_version": AGP_7_2,
+ "gradle_version": GRADLE_7_3_3,
+ "shard_count": 5,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "agp_version": AGP_7_3,
+ "gradle_version": GRADLE_7_4,
+ "shard_count": 5,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "agp_version": AGP_7_4,
+ "gradle_version": GRADLE_7_5,
+ "shard_count": 5,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "agp_version": AGP_8_0,
+ "gradle_version": GRADLE_8_0,
+ "shard_count": 5,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "agp_version": AGP_8_1,
+ "gradle_version": GRADLE_8_0,
+ "shard_count": 5,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
+ {
+ "agp_version": AGP_8_2,
+ "gradle_version": GRADLE_8_2,
+ "shard_count": 5,
+ "tags": COMMON_OLD_AGP_TEST_TARGETS_TAGS,
+ },
],
)
diff --git a/old-agp-tests/agp_versions.bzl b/old-agp-tests/agp_versions.bzl
index dd731fd..509831f 100644
--- a/old-agp-tests/agp_versions.bzl
+++ b/old-agp-tests/agp_versions.bzl
@@ -146,7 +146,7 @@
GRADLE_5_3_1: ["//tools/base/build-system:gradle-distrib-5.3.1"],
}
-def local_old_agp_test(
+def _local_old_agp_test(
gradle_version,
agp_version,
additional_jdks = [],
@@ -169,3 +169,16 @@
ignore_other_tests = False,
**kwargs
)
+
+def generate_old_agp_tests_from_list(tests_list):
+ """Creates tests running with OldAgpSuite from a list of test descriptions.
+
+ Having all test definitions as a list in one macro allows us to implement a check to ensure all
+ OldAgpTest tests from the module are covered with a test target and thus will actually run.
+
+ Args:
+ tests_list: list of kwargs objects, one per required test, containing arguments for that test.
+ See _local_old_agp_test and old_agp_test for test arguments description.
+ """
+ for test_kwargs in tests_list:
+ _local_old_agp_test(**test_kwargs)
diff --git a/preview-designer/src/com/android/tools/idea/preview/NavigatingInteractionHandler.kt b/preview-designer/src/com/android/tools/idea/preview/NavigatingInteractionHandler.kt
index 940b17b..cbaebae 100644
--- a/preview-designer/src/com/android/tools/idea/preview/NavigatingInteractionHandler.kt
+++ b/preview-designer/src/com/android/tools/idea/preview/NavigatingInteractionHandler.kt
@@ -28,7 +28,7 @@
import com.android.tools.idea.common.surface.selectComponent
import com.android.tools.idea.concurrency.AndroidCoroutineScope
import com.android.tools.idea.concurrency.AndroidDispatchers
-import com.android.tools.idea.uibuilder.surface.NlDesignSurface
+import com.android.tools.idea.uibuilder.surface.NavigationHandler
import com.android.tools.idea.uibuilder.surface.NlInteractionHandler
import java.awt.MouseInfo
import java.awt.event.InputEvent
@@ -44,6 +44,7 @@
*/
class NavigatingInteractionHandler(
private val surface: DesignSurface<*>,
+ private val navigationHandler: NavigationHandler,
private val isSelectionEnabled: () -> Boolean = { false }
) : NlInteractionHandler(surface) {
@@ -172,10 +173,9 @@
val sceneView = surface.getSceneViewAt(x, y) ?: return
val androidX = Coordinates.getAndroidXDip(sceneView, x)
val androidY = Coordinates.getAndroidYDip(sceneView, y)
- val navHandler = (surface as NlDesignSurface).navigationHandler ?: return
val scene = sceneView.scene
scope.launch(AndroidDispatchers.workerThread) {
- if (!navHandler.handleNavigateWithCoordinates(sceneView, x, y, needsFocusEditor)) {
+ if (!navigationHandler.handleNavigateWithCoordinates(sceneView, x, y, needsFocusEditor)) {
val sceneComponent =
scene.findComponent(sceneView.context, androidX, androidY) ?: return@launch
navigateToComponent(sceneComponent.nlComponent, needsFocusEditor)
diff --git a/preview-designer/src/com/android/tools/idea/preview/SurfacePreviewsUpdate.kt b/preview-designer/src/com/android/tools/idea/preview/SurfacePreviewsUpdate.kt
index 438315f..f4f78c6 100644
--- a/preview-designer/src/com/android/tools/idea/preview/SurfacePreviewsUpdate.kt
+++ b/preview-designer/src/com/android/tools/idea/preview/SurfacePreviewsUpdate.kt
@@ -172,6 +172,7 @@
onRenderCompleted: () -> Unit,
previewElementModelAdapter: PreviewElementModelAdapter<T, NlModel>,
modelUpdater: NlModel.NlModelUpdaterInterface,
+ navigationHandler: PreviewNavigationHandler,
configureLayoutlibSceneManager:
(PreviewDisplaySettings, LayoutlibSceneManager) -> LayoutlibSceneManager
): List<T> {
@@ -281,11 +282,7 @@
getPsiFileSafely(project, it)
}
?: psiFile
- (navigationHandler as? PreviewNavigationHandler)?.setDefaultLocation(
- newModel,
- defaultFile,
- offset
- )
+ navigationHandler.setDefaultLocation(newModel, defaultFile, offset)
withContext(AndroidDispatchers.workerThread) {
previewElementModelAdapter.applyToConfiguration(previewElement, newModel.configuration)
diff --git a/preview-designer/src/com/android/tools/idea/preview/representation/CommonPreviewRepresentation.kt b/preview-designer/src/com/android/tools/idea/preview/representation/CommonPreviewRepresentation.kt
index 995332e..e8b012e 100644
--- a/preview-designer/src/com/android/tools/idea/preview/representation/CommonPreviewRepresentation.kt
+++ b/preview-designer/src/com/android/tools/idea/preview/representation/CommonPreviewRepresentation.kt
@@ -152,6 +152,10 @@
private val delegateInteractionHandler = DelegateInteractionHandler()
+ private val navigationHandler =
+ DefaultNavigationHandler { _, _, _, _, _ -> null }
+ .apply { Disposer.register(this@CommonPreviewRepresentation, this) }
+
private val previewView = invokeAndWaitIfNeeded {
viewConstructor(
project,
@@ -162,9 +166,10 @@
}
}
.setInteractionHandlerProvider {
- delegateInteractionHandler.apply { delegate = NavigatingInteractionHandler(it) }
+ delegateInteractionHandler.apply {
+ delegate = NavigatingInteractionHandler(it, navigationHandler)
+ }
}
- .setNavigationHandler(DefaultNavigationHandler { _, _, _, _, _ -> null })
.setDelegateDataProvider {
when (it) {
PREVIEW_VIEW_MODEL_STATUS.name -> previewViewModel
@@ -274,6 +279,7 @@
this::onAfterRender,
previewElementModelAdapter,
modelUpdater,
+ navigationHandler,
this::configureLayoutlibSceneManager
)
diff --git a/profilers-android/src/com/android/tools/idea/profilers/AndroidProfilerToolWindow.kt b/profilers-android/src/com/android/tools/idea/profilers/AndroidProfilerToolWindow.kt
index 5a2e7d5..1499b22 100644
--- a/profilers-android/src/com/android/tools/idea/profilers/AndroidProfilerToolWindow.kt
+++ b/profilers-android/src/com/android/tools/idea/profilers/AndroidProfilerToolWindow.kt
@@ -32,6 +32,14 @@
import com.android.tools.profilers.StudioProfilers
import com.android.tools.profilers.tasks.ProfilerTaskType
import com.android.tools.profilers.tasks.args.TaskArgs
+import com.android.tools.profilers.tasks.taskhandlers.ProfilerTaskHandler
+import com.android.tools.profilers.tasks.taskhandlers.singleartifact.cpu.CallstackSampleTaskHandler
+import com.android.tools.profilers.tasks.taskhandlers.singleartifact.cpu.JavaKotlinMethodSampleTaskHandler
+import com.android.tools.profilers.tasks.taskhandlers.singleartifact.cpu.JavaKotlinMethodTraceTaskHandler
+import com.android.tools.profilers.tasks.taskhandlers.singleartifact.cpu.SystemTraceTaskHandler
+import com.android.tools.profilers.tasks.taskhandlers.singleartifact.memory.HeapDumpTaskHandler
+import com.android.tools.profilers.tasks.taskhandlers.singleartifact.memory.JavaKotlinAllocationsTaskHandler
+import com.android.tools.profilers.tasks.taskhandlers.singleartifact.memory.NativeAllocationsTaskHandler
import com.intellij.openapi.Disposable
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
@@ -53,6 +61,9 @@
private val ideProfilerServices: IntellijProfilerServices
private val ideProfilerComponents: IdeProfilerComponents
val profilers: StudioProfilers
+ private var currentTaskHandler: ProfilerTaskHandler? = null
+ private val taskHandlers = HashMap<ProfilerTaskType, ProfilerTaskHandler>()
+
private lateinit var homeTab: StudioProfilersHomeTab
private lateinit var homePanel: JPanel
private lateinit var profilersTab: StudioProfilersTab
@@ -69,7 +80,7 @@
TransportService.getInstance()
val client = ProfilerClient(TransportService.channelName)
- profilers = StudioProfilers(client, ideProfilerServices)
+ profilers = StudioProfilers(client, ideProfilerServices, project, taskHandlers)
val navigator = ideProfilerServices.codeNavigator
// CPU ABI architecture, when needed by the code navigator, should be retrieved from StudioProfiler selected session.
navigator.cpuArchSource = Supplier { profilers.sessionsManager.selectedSessionMetaData.processAbi }
@@ -90,6 +101,9 @@
ideProfilerComponents = IntellijProfilerComponents(project, ideProfilerServices.featureTracker)
+ // Create and store the task handlers in a map.
+ initializeTaskHandlers()
+
if (ideProfilerServices.featureConfig.isTaskBasedUxEnabled) {
homeTab = StudioProfilersHomeTab(project, profilers, ideProfilerComponents)
homePanel = JPanel(BorderLayout())
@@ -105,8 +119,15 @@
initializeProfilerTab()
}
- private fun createTaskTab() {
- createNewTab(profilersPanel, "<Task Name>", true)
+ private fun initializeTaskHandlers() {
+ val sessionsManager = profilers.sessionsManager
+ taskHandlers[ProfilerTaskType.SYSTEM_TRACE] = SystemTraceTaskHandler(sessionsManager)
+ taskHandlers[ProfilerTaskType.CALLSTACK_SAMPLE] = CallstackSampleTaskHandler(sessionsManager)
+ taskHandlers[ProfilerTaskType.JAVA_KOTLIN_METHOD_TRACE] = JavaKotlinMethodTraceTaskHandler(sessionsManager)
+ taskHandlers[ProfilerTaskType.JAVA_KOTLIN_METHOD_SAMPLE] = JavaKotlinMethodSampleTaskHandler(sessionsManager)
+ taskHandlers[ProfilerTaskType.HEAP_DUMP] = HeapDumpTaskHandler(sessionsManager)
+ taskHandlers[ProfilerTaskType.NATIVE_ALLOCATIONS] = NativeAllocationsTaskHandler(sessionsManager)
+ taskHandlers[ProfilerTaskType.JAVA_KOTLIN_ALLOCATIONS] = JavaKotlinAllocationsTaskHandler(sessionsManager)
}
@VisibleForTesting
@@ -144,11 +165,6 @@
profilersPanel.add(profilersTab.view.component)
profilersPanel.revalidate()
profilersPanel.repaint()
-
- // TODO(b/277797528) Remove once the task tab supports an L2 or L3 stage.
- if (ideProfilerServices.featureConfig.isTaskBasedUxEnabled) {
- profilers.setDefaultStage()
- }
}
fun openHomeTab() {
@@ -162,13 +178,19 @@
}
fun openTaskTab(taskType: ProfilerTaskType, taskArgs: TaskArgs?) {
- // The taskType and taskArgs will be utilized in the (to-be-added) task handlers.
- val content = findTaskTab()
- if (content != null) {
- window.getContentManager().setSelectedContent(content)
+ val taskTab = findTaskTab()
+ val taskName = taskHandlers[taskType]?.getTaskName() ?: "Task Not Supported Yet"
+ if (taskTab != null) {
+ taskTab.displayName = taskName
+ window.getContentManager().setSelectedContent(taskTab)
}
else {
- createTaskTab()
+ createNewTab(profilersPanel, taskName, true)
+ }
+ currentTaskHandler?.exit()
+ currentTaskHandler = taskHandlers[taskType]
+ currentTaskHandler?.let { taskHandler ->
+ val enterSuccessful = taskHandler.enter(taskArgs)
}
}
diff --git a/profilers-android/src/com/android/tools/idea/profilers/StartSystemTraceAction.kt b/profilers-android/src/com/android/tools/idea/profilers/StartSystemTraceAction.kt
index f222117..01dad92 100644
--- a/profilers-android/src/com/android/tools/idea/profilers/StartSystemTraceAction.kt
+++ b/profilers-android/src/com/android/tools/idea/profilers/StartSystemTraceAction.kt
@@ -20,6 +20,7 @@
import com.android.tools.idea.run.profiler.CpuProfilerConfig
import com.android.tools.profilers.cpu.CpuProfilerStage
import com.android.tools.profilers.cpu.config.PerfettoConfiguration
+import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAwareAction
@@ -28,6 +29,9 @@
"Start a system trace in the current profiling session",
null
) {
+
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+
override fun update(e: AnActionEvent) {
val project = e.project
e.presentation.isEnabled =
diff --git a/profilers-android/src/com/android/tools/idea/profilers/StopCpuCaptureAction.kt b/profilers-android/src/com/android/tools/idea/profilers/StopCpuCaptureAction.kt
index 774fd14..24e3d46 100644
--- a/profilers-android/src/com/android/tools/idea/profilers/StopCpuCaptureAction.kt
+++ b/profilers-android/src/com/android/tools/idea/profilers/StopCpuCaptureAction.kt
@@ -18,6 +18,7 @@
import android.annotation.SuppressLint
import com.android.tools.idea.flags.StudioFlags
import com.android.tools.profilers.cpu.CpuProfilerStage
+import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAwareAction
@@ -26,6 +27,9 @@
"Stop a CPU capture in the current profiling session",
null
) {
+
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+
override fun update(e: AnActionEvent) {
val project = e.project
val profilers = project?.let { AndroidProfilerToolWindowFactory.getProfilerToolWindow(it)?.profilers }
diff --git a/profilers-android/src/com/android/tools/idea/profilers/StopProfilingSessionAction.kt b/profilers-android/src/com/android/tools/idea/profilers/StopProfilingSessionAction.kt
index 523dcd4..81b23af 100644
--- a/profilers-android/src/com/android/tools/idea/profilers/StopProfilingSessionAction.kt
+++ b/profilers-android/src/com/android/tools/idea/profilers/StopProfilingSessionAction.kt
@@ -16,6 +16,7 @@
package com.android.tools.idea.profilers
import com.android.tools.idea.flags.StudioFlags
+import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAwareAction
@@ -24,6 +25,9 @@
"Stop the current profiling session",
null
) {
+
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+
override fun update(e: AnActionEvent) {
val project = e.project
e.presentation.isEnabled =
diff --git a/profilers-android/src/com/android/tools/idea/profilers/profilingconfig/CpuProfilingConfigurationsDialog.java b/profilers-android/src/com/android/tools/idea/profilers/profilingconfig/CpuProfilingConfigurationsDialog.java
index 7c58d60..daa67fd 100644
--- a/profilers-android/src/com/android/tools/idea/profilers/profilingconfig/CpuProfilingConfigurationsDialog.java
+++ b/profilers-android/src/com/android/tools/idea/profilers/profilingconfig/CpuProfilingConfigurationsDialog.java
@@ -74,6 +74,7 @@
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.VisibleForTesting;
public class CpuProfilingConfigurationsDialog extends SingleConfigurableEditor {
@@ -113,7 +114,8 @@
myOnCloseCallback.accept(selectedConfigSupported ? selectedConfig : null);
}
- private static class ProfilingConfigurable implements Configurable {
+ @VisibleForTesting
+ static class ProfilingConfigurable implements Configurable {
private static final String ADD = "Add";
@@ -226,14 +228,29 @@
ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(myConfigurations)
.setToolbarPosition(ActionToolbarPosition.TOP)
.setPanelBorder(JBUI.Borders.empty())
- .setMoveUpAction(moveUpAction).setMoveUpActionUpdater(moveUpAction).setMoveUpActionName(MOVE_UP)
- .setMoveDownAction(moveDownAction).setMoveDownActionUpdater(moveDownAction).setMoveDownActionName(MOVE_DOWN)
- .setRemoveAction(removeAction).setRemoveActionUpdater(removeAction).setRemoveActionName(REMOVE)
- .setAddAction(addAction).setAddActionUpdater(addAction).setAddActionName(ADD)
.setMinimumSize(new JBDimension(200, 200))
.setForcedDnD();
+
+ if (!StudioFlags.PROFILER_TASK_BASED_UX.get()) {
+ toolbarDecorator
+ .setMoveUpAction(moveUpAction).setMoveUpActionUpdater(moveUpAction).setMoveUpActionName(MOVE_UP)
+ .setMoveDownAction(moveDownAction).setMoveDownActionUpdater(moveDownAction).setMoveDownActionName(MOVE_DOWN)
+ .setRemoveAction(removeAction).setRemoveActionUpdater(removeAction).setRemoveActionName(REMOVE)
+ .setAddAction(addAction).setAddActionUpdater(addAction).setAddActionName(ADD);
+ }
+ else {
+ // Make the icons invisible
+ toolbarDecorator
+ .disableRemoveAction()
+ .disableDownAction()
+ .disableUpAction()
+ .disableAddAction();
+ }
+
JComponent panel = toolbarDecorator.createPanel();
- addAction.setPopupParent(panel);
+ if (!StudioFlags.PROFILER_TASK_BASED_UX.get()) {
+ addAction.setPopupParent(panel);
+ }
return panel;
}
diff --git a/profilers-android/testSrc/com/android/tools/idea/profilers/AndroidProfilerToolWindowFactoryTest.kt b/profilers-android/testSrc/com/android/tools/idea/profilers/AndroidProfilerToolWindowFactoryTest.kt
index 35a7a7e..eecf406 100644
--- a/profilers-android/testSrc/com/android/tools/idea/profilers/AndroidProfilerToolWindowFactoryTest.kt
+++ b/profilers-android/testSrc/com/android/tools/idea/profilers/AndroidProfilerToolWindowFactoryTest.kt
@@ -3,11 +3,23 @@
import com.android.testutils.waitForCondition
import com.android.tools.idea.flags.StudioFlags
import com.android.tools.idea.sdk.AndroidOrApkFacetChecker
+import com.android.tools.profiler.proto.Common
+import com.android.tools.profiler.proto.Trace
+import com.android.tools.profilers.cpu.CpuCaptureSessionArtifact
+import com.android.tools.profilers.cpu.CpuProfilerStage
+import com.android.tools.profilers.memory.HeapProfdSessionArtifact
+import com.android.tools.profilers.memory.MainMemoryProfilerStage
import com.android.tools.profilers.taskbased.home.OpenHomeTabListener
+import com.android.tools.profilers.tasks.ProfilerTaskType
+import com.android.tools.profilers.tasks.args.singleartifact.cpu.CpuTaskArgs
+import com.android.tools.profilers.tasks.args.singleartifact.memory.NativeAllocationsTaskArgs
import com.google.common.truth.Truth.assertThat
import com.intellij.openapi.wm.ext.LibraryDependentToolWindow
import com.intellij.testFramework.ProjectRule
import com.intellij.toolWindow.ToolWindowHeadlessManagerImpl
+import org.jetbrains.uast.util.isInstanceOf
+import org.junit.After
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.concurrent.TimeUnit
@@ -23,6 +35,17 @@
private val project get() = projectRule.project
+ @Before
+ fun setup() {
+ // This test suite assumes the Task-Based UX is enabled unless otherwise specified.
+ StudioFlags.PROFILER_TASK_BASED_UX.override(true)
+ }
+
+ @After
+ fun cleanup() {
+ StudioFlags.PROFILER_TASK_BASED_UX.clearOverride()
+ }
+
@Test
fun isLibraryToolWindow() {
val toolWindow =
@@ -35,8 +58,6 @@
@Test
fun `createToolWindowContent implicitly opens the home tab`() {
- StudioFlags.PROFILER_TASK_BASED_UX.override(true);
-
val toolWindow = ToolWindowHeadlessManagerImpl.MockToolWindow(project)
val toolWindowFactory = AndroidProfilerToolWindowFactory()
toolWindowFactory.init(toolWindow)
@@ -52,14 +73,10 @@
assertThat(homeTabContent.tabName).isEqualTo("Home")
assertThat(homeTabContent.displayName).isEqualTo("Home")
assertThat(homeTabContent.toolwindowTitle).isEqualTo("Home")
-
- StudioFlags.PROFILER_TASK_BASED_UX.clearOverride()
}
@Test
fun `explicitly opening the home tab reselects existing home tab`() {
- StudioFlags.PROFILER_TASK_BASED_UX.override(true);
-
val toolWindow = ToolWindowHeadlessManagerImpl.MockToolWindow(project)
val toolWindowFactory = AndroidProfilerToolWindowFactory()
toolWindowFactory.init(toolWindow)
@@ -91,7 +108,71 @@
waitForCondition(5L, TimeUnit.SECONDS) {
toolWindow.contentManager.contentCount == 2 && toolWindow.contentManager.selectedContent == homeTabContent
}
+ }
- StudioFlags.PROFILER_TASK_BASED_UX.clearOverride()
+ @Test
+ fun `opening the task tab with CPU task creates second tab with CPU stage`() {
+ val toolWindow = ToolWindowHeadlessManagerImpl.MockToolWindow(project)
+ val toolWindowFactory = AndroidProfilerToolWindowFactory()
+ toolWindowFactory.init(toolWindow)
+ toolWindowFactory.createToolWindowContent(project, toolWindow)
+
+ // Wait for the home tab to be auto-selected via the call to openHomeTab() in AndroidProfilerToolWindow.createToolWindowContent.
+ waitForCondition(5L, TimeUnit.SECONDS) {
+ toolWindow.contentManager.selectedContent != null &&
+ toolWindow.contentManager.selectedContent!!.displayName == "Home"
+ }
+
+ // At this point, there is a home tab implicitly opened by the call to `createToolWindowContent`.
+
+ val profilerToolWindow = AndroidProfilerToolWindowFactory.PROJECT_PROFILER_MAP[project]
+ assertThat(profilerToolWindow).isNotNull()
+
+ profilerToolWindow!!.openTaskTab(ProfilerTaskType.SYSTEM_TRACE,
+ CpuTaskArgs(CpuCaptureSessionArtifact
+ (profilerToolWindow.profilers, Common.Session.getDefaultInstance(),
+ Common.SessionMetaData.getDefaultInstance(), Trace.TraceInfo.getDefaultInstance())))
+
+ // Opening the task tab with a SYSTEM_TRACE task (a CPU task) should open up a second tab with non-null content, a tab name
+ // of "System Trace" and the current stage should be set to the CpuProfilerStage.
+ waitForCondition(5L, TimeUnit.SECONDS) {
+ toolWindow.contentManager.contentCount == 2 &&
+ toolWindow.contentManager.selectedContent != null &&
+ toolWindow.contentManager.selectedContent!!.displayName == "System Trace"
+ profilerToolWindow.profilers.stage is CpuProfilerStage
+ }
+ }
+
+ @Test
+ fun `opening the task tab with memory task creates second tab with memory stage`() {
+ val toolWindow = ToolWindowHeadlessManagerImpl.MockToolWindow(project)
+ val toolWindowFactory = AndroidProfilerToolWindowFactory()
+ toolWindowFactory.init(toolWindow)
+ toolWindowFactory.createToolWindowContent(project, toolWindow)
+
+ // Wait for the home tab to be auto-selected via the call to openHomeTab() in AndroidProfilerToolWindow.createToolWindowContent.
+ waitForCondition(5L, TimeUnit.SECONDS) {
+ toolWindow.contentManager.selectedContent != null &&
+ toolWindow.contentManager.selectedContent!!.displayName == "Home"
+ }
+
+ // At this point, there is a home tab implicitly opened by the call to `createToolWindowContent`.
+
+ val profilerToolWindow = AndroidProfilerToolWindowFactory.PROJECT_PROFILER_MAP[project]
+ assertThat(profilerToolWindow).isNotNull()
+
+ profilerToolWindow!!.openTaskTab(ProfilerTaskType.NATIVE_ALLOCATIONS,
+ NativeAllocationsTaskArgs(HeapProfdSessionArtifact(
+ profilerToolWindow.profilers, Common.Session.getDefaultInstance(),
+ Common.SessionMetaData.getDefaultInstance(), Trace.TraceInfo.getDefaultInstance())))
+
+ // Opening the task tab with a NATIVE_ALLOCATIONS task (a memory task) should open up a second tab with non-null content, a tab name
+ // of "Native Allocations" and the current stage should be set to the MainMemoryProfilerStage.
+ waitForCondition(5L, TimeUnit.SECONDS) {
+ toolWindow.contentManager.contentCount == 2 &&
+ toolWindow.contentManager.selectedContent != null &&
+ toolWindow.contentManager.selectedContent!!.displayName == "Native Allocations"
+ profilerToolWindow.profilers.stage is MainMemoryProfilerStage
+ }
}
}
\ No newline at end of file
diff --git a/profilers-android/testSrc/com/android/tools/idea/profilers/profilingconfig/CpuProfilingConfigurationsDialogTest.kt b/profilers-android/testSrc/com/android/tools/idea/profilers/profilingconfig/CpuProfilingConfigurationsDialogTest.kt
new file mode 100644
index 0000000..c336e31
--- /dev/null
+++ b/profilers-android/testSrc/com/android/tools/idea/profilers/profilingconfig/CpuProfilingConfigurationsDialogTest.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.profilers.profilingconfig
+
+import com.android.tools.adtui.TreeWalker
+import com.android.tools.adtui.model.FakeTimer
+import com.android.tools.idea.flags.StudioFlags
+import com.android.tools.idea.transport.faketransport.FakeGrpcChannel
+import com.android.tools.idea.transport.faketransport.FakeTransportService
+import com.android.tools.profilers.FakeFeatureTracker
+import com.android.tools.profilers.FakeIdeProfilerServices
+import com.android.tools.profilers.ProfilerClient
+import com.android.tools.profilers.StudioProfilers
+import com.android.tools.profilers.analytics.FeatureTracker
+import com.android.tools.profilers.cpu.CpuProfilerStage
+import com.android.tools.profilers.cpu.config.CpuProfilerConfigModel
+import com.android.tools.profilers.event.FakeEventService
+import com.google.common.truth.Truth.assertThat
+import com.intellij.mock.MockProjectEx
+import com.intellij.openapi.project.Project
+import com.intellij.testFramework.ApplicationRule
+import com.intellij.testFramework.DisposableRule
+import com.intellij.testFramework.EdtRule
+import com.intellij.ui.CommonActionsPanel
+import com.intellij.ui.JBSplitter
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import javax.swing.JComponent
+
+class CpuProfilingConfigurationsDialogTest {
+
+ private lateinit var configurations: CpuProfilingConfigurationsDialog.ProfilingConfigurable
+ private lateinit var project: Project
+ private lateinit var model: CpuProfilerConfigModel
+ private lateinit var featureTracker: FeatureTracker
+ private lateinit var myStage: CpuProfilerStage
+ private var deviceLevel = 0
+
+ private val myTimer = FakeTimer()
+ private val myIdeServices = FakeIdeProfilerServices()
+ private val myTransportService = FakeTransportService(myTimer)
+
+ @get:Rule
+ val myGrpcChannel = FakeGrpcChannel("CpuProfilingConfigDialogTestChannel", myTransportService, FakeEventService())
+
+ @get:Rule
+ val myEdtRule = EdtRule()
+
+ @get:Rule
+ val applicationRule = ApplicationRule()
+
+ @get:Rule
+ val disposableRule = DisposableRule()
+
+ @Before
+ fun setUp() {
+ val profilers = StudioProfilers(ProfilerClient(myGrpcChannel.channel), myIdeServices, myTimer)
+ profilers.setPreferredProcess(FakeTransportService.FAKE_DEVICE_NAME, FakeTransportService.FAKE_PROCESS_NAME, null)
+ project = MockProjectEx(disposableRule.disposable)
+ myStage = CpuProfilerStage(profilers)
+ model = CpuProfilerConfigModel(profilers, myStage)
+ model.profilingConfiguration = FakeIdeProfilerServices.ATRACE_CONFIG
+ featureTracker = FakeFeatureTracker()
+ configurations = CpuProfilingConfigurationsDialog.ProfilingConfigurable(project, model, deviceLevel, featureTracker)
+ }
+
+ @After
+ fun tearDown() {
+ // Need to clear any override we use inside tests here.
+ StudioFlags.PROFILER_TASK_BASED_UX.clearOverride()
+ }
+
+ @Test
+ fun configDialogIconWhenTaskBasedUxEnabled() {
+ StudioFlags.PROFILER_TASK_BASED_UX.override(true)
+ var profilingComponent : JComponent = configurations.createComponent()!!
+ assertThat(profilingComponent).isNotNull()
+ val splitter: JBSplitter = TreeWalker(profilingComponent).descendants().filterIsInstance<JBSplitter>().first()
+ val firstComponent = splitter.firstComponent
+ val actionPanel: CommonActionsPanel = TreeWalker(firstComponent).descendants().filterIsInstance<CommonActionsPanel>().first()
+ // Add, remove, up and down buttons should be null
+ val addButtonAction = actionPanel.getAnAction(CommonActionsPanel.Buttons.ADD)
+ assertThat(addButtonAction).isNull()
+ val downButtonAction = actionPanel.getAnAction(CommonActionsPanel.Buttons.DOWN)
+ assertThat(downButtonAction).isNull()
+ val upButtonAction = actionPanel.getAnAction(CommonActionsPanel.Buttons.UP)
+ assertThat(upButtonAction).isNull()
+ val removeButtonAction = actionPanel.getAnAction(CommonActionsPanel.Buttons.REMOVE)
+ assertThat(removeButtonAction).isNull()
+ }
+
+ @Test
+ fun configDialogIconWhenTaskBasedUxDisabled() {
+ StudioFlags.PROFILER_TASK_BASED_UX.override(false)
+ var profilingComponent : JComponent = configurations.createComponent()!!
+ assertThat(profilingComponent).isNotNull()
+ val splitter: JBSplitter = TreeWalker(profilingComponent).descendants().filterIsInstance<JBSplitter>().first()
+ val firstComponent = splitter.firstComponent
+ val actionPanel: CommonActionsPanel = TreeWalker(firstComponent).descendants().filterIsInstance<CommonActionsPanel>().first()
+ // Add, remove, up and down buttons should not be null
+ val addButtonAction = actionPanel.getAnAction(CommonActionsPanel.Buttons.ADD)
+ assertThat(addButtonAction).isNotNull()
+ val downButtonAction = actionPanel.getAnAction(CommonActionsPanel.Buttons.DOWN)
+ assertThat(downButtonAction).isNotNull()
+ val upButtonAction = actionPanel.getAnAction(CommonActionsPanel.Buttons.UP)
+ assertThat(upButtonAction).isNotNull()
+ val removeButtonAction = actionPanel.getAnAction(CommonActionsPanel.Buttons.REMOVE)
+ assertThat(removeButtonAction).isNotNull()
+ }
+}
\ No newline at end of file
diff --git a/profilers-ui/src/com/android/tools/profilers/ExportArtifactUtils.kt b/profilers-ui/src/com/android/tools/profilers/ExportArtifactUtils.kt
new file mode 100644
index 0000000..1d79b17
--- /dev/null
+++ b/profilers-ui/src/com/android/tools/profilers/ExportArtifactUtils.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.profilers
+
+import java.io.File
+import java.io.FileOutputStream
+import java.io.OutputStream
+import java.util.function.Consumer
+
+/**
+ * This class contains utility methods to aid in exporting a {@link SessionArtifact} to a file.
+ */
+object ExportArtifactUtils {
+
+ /**
+ * Opens an export file dialog and prompts user to export/save artifact as a file to disk.
+ */
+ @JvmStatic
+ fun exportArtifact(artifact: ExportableArtifact,
+ exportAction: Consumer<OutputStream>,
+ ideProfilerComponents: IdeProfilerComponents,
+ ideServices: IdeProfilerServices) {
+ ideProfilerComponents.createExportDialog().open(
+ { "Export As" },
+ { artifact.exportableName },
+ { artifact.exportExtension },
+ { file: File -> ideServices.saveFile(file, { outputStream: FileOutputStream -> exportAction.accept(outputStream) }, null) }
+ )
+ }
+}
\ No newline at end of file
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/CpuCaptureArtifactView.java b/profilers-ui/src/com/android/tools/profilers/cpu/CpuCaptureArtifactView.java
index 0336c05..786bf0a 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/CpuCaptureArtifactView.java
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/CpuCaptureArtifactView.java
@@ -15,7 +15,6 @@
*/
package com.android.tools.profilers.cpu;
-import com.android.tools.profilers.cpu.config.ProfilingConfiguration.TraceType;
import com.android.tools.profilers.sessions.SessionArtifactView;
import icons.StudioIcons;
import org.jetbrains.annotations.NotNull;
@@ -37,14 +36,4 @@
return buildCaptureArtifactView(getArtifact().getName(), getArtifact().getSubtitle(), StudioIcons.Profiler.Sessions.CPU,
getArtifact().isOngoing());
}
-
- @Override
- protected void exportArtifact() {
- assert !getArtifact().isOngoing();
- getSessionsView().getIdeProfilerComponents().createExportDialog().open(
- () -> "Export As",
- () -> CpuProfiler.generateCaptureFileName(TraceType.from(getArtifact().getArtifactProto().getConfiguration())),
- () -> "trace",
- file -> getArtifact().getProfilers().getIdeServices().saveFile(file, outputStream -> getArtifact().export(outputStream), null));
- }
}
\ No newline at end of file
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/CpuProfilerContextMenuInstaller.java b/profilers-ui/src/com/android/tools/profilers/cpu/CpuProfilerContextMenuInstaller.java
index a7feb11..d252344 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/CpuProfilerContextMenuInstaller.java
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/CpuProfilerContextMenuInstaller.java
@@ -78,7 +78,8 @@
() -> "trace",
file -> myStage.getStudioProfilers().getIdeServices().saveFile(
file,
- (output) -> CpuProfiler.saveCaptureToFile(myStage.getStudioProfilers(), getTraceIntersectingWithMouseX(x).getTraceInfo(), output),
+ (output) -> CpuProfiler.saveCaptureToFile(myStage.getStudioProfilers(), myStage.getStudioProfilers().getSession(),
+ getTraceIntersectingWithMouseX(x).getTraceInfo(), output),
null)));
myInstaller.installGenericContextMenu(myComponent, ContextMenuItem.SEPARATOR);
}
diff --git a/profilers-ui/src/com/android/tools/profilers/memory/HeapProfdArtifactView.java b/profilers-ui/src/com/android/tools/profilers/memory/HeapProfdArtifactView.java
index 7eca4d0..3dc9d3e 100644
--- a/profilers-ui/src/com/android/tools/profilers/memory/HeapProfdArtifactView.java
+++ b/profilers-ui/src/com/android/tools/profilers/memory/HeapProfdArtifactView.java
@@ -35,14 +35,4 @@
return buildCaptureArtifactView(getArtifact().getName(), getArtifact().getSubtitle(), StudioIcons.Profiler.Sessions.HEAP,
getArtifact().isOngoing());
}
-
- @Override
- protected void exportArtifact() {
- assert !getArtifact().isOngoing();
- getSessionsView().getIdeProfilerComponents().createExportDialog().open(
- () -> "Export As",
- () -> MemoryProfiler.generateCaptureFileName(),
- () -> "heapprofd",
- file -> getSessionsView().getProfilers().getIdeServices().saveFile(file, outputStream -> getArtifact().export(outputStream), null));
- }
}
diff --git a/profilers-ui/src/com/android/tools/profilers/memory/HprofArtifactView.java b/profilers-ui/src/com/android/tools/profilers/memory/HprofArtifactView.java
index a35ba62..ab4684d 100644
--- a/profilers-ui/src/com/android/tools/profilers/memory/HprofArtifactView.java
+++ b/profilers-ui/src/com/android/tools/profilers/memory/HprofArtifactView.java
@@ -36,14 +36,4 @@
return buildCaptureArtifactView(getArtifact().getName(), getArtifact().getSubtitle(), StudioIcons.Profiler.Sessions.HEAP,
getArtifact().isOngoing());
}
-
- @Override
- protected void exportArtifact() {
- assert !getArtifact().isOngoing();
- getSessionsView().getIdeProfilerComponents().createExportDialog().open(
- () -> "Export As",
- () -> MemoryProfiler.generateCaptureFileName(),
- () -> "hprof",
- file -> getSessionsView().getProfilers().getIdeServices().saveFile(file, outputStream -> getArtifact().export(outputStream), null));
- }
}
diff --git a/profilers-ui/src/com/android/tools/profilers/memory/LegacyAllocationsArtifactView.java b/profilers-ui/src/com/android/tools/profilers/memory/LegacyAllocationsArtifactView.java
index 21124e7..0910e2a 100644
--- a/profilers-ui/src/com/android/tools/profilers/memory/LegacyAllocationsArtifactView.java
+++ b/profilers-ui/src/com/android/tools/profilers/memory/LegacyAllocationsArtifactView.java
@@ -36,14 +36,4 @@
return buildCaptureArtifactView(getArtifact().getName(), getArtifact().getSubtitle(), StudioIcons.Profiler.Sessions.ALLOCATIONS,
getArtifact().isOngoing());
}
-
- @Override
- protected void exportArtifact() {
- assert !getArtifact().isOngoing();
- getSessionsView().getIdeProfilerComponents().createExportDialog().open(
- () -> "Export As",
- () -> MemoryProfiler.generateCaptureFileName(),
- () -> "alloc",
- file -> getSessionsView().getProfilers().getIdeServices().saveFile(file, outputStream -> getArtifact().export(outputStream), null));
- }
}
diff --git a/profilers-ui/src/com/android/tools/profilers/sessions/SessionArtifactView.java b/profilers-ui/src/com/android/tools/profilers/sessions/SessionArtifactView.java
index 5cc9d59..be967c5 100644
--- a/profilers-ui/src/com/android/tools/profilers/sessions/SessionArtifactView.java
+++ b/profilers-ui/src/com/android/tools/profilers/sessions/SessionArtifactView.java
@@ -25,6 +25,8 @@
import com.android.tools.adtui.stdui.DefaultContextMenuItem;
import com.android.tools.adtui.stdui.StandardColors;
import com.android.tools.inspectors.common.ui.ContextMenuInstaller;
+import com.android.tools.profilers.ExportArtifactUtils;
+import com.android.tools.profilers.ExportableArtifact;
import com.android.tools.profilers.StudioProfilers;
import com.google.common.annotations.VisibleForTesting;
import com.intellij.util.IconUtil;
@@ -271,6 +273,11 @@
protected abstract JComponent buildComponent();
protected void exportArtifact() {
+ if (getArtifact() instanceof ExportableArtifact) {
+ assert !getArtifact().isOngoing();
+ ExportArtifactUtils.exportArtifact((ExportableArtifact)getArtifact(), getArtifact()::export,
+ getSessionsView().getIdeProfilerComponents(), getProfilers().getIdeServices());
+ }
}
private void showHoverState(boolean hover) {
diff --git a/profilers-ui/src/com/android/tools/profilers/sessions/SessionItemView.java b/profilers-ui/src/com/android/tools/profilers/sessions/SessionItemView.java
index ca46247..e313f98 100644
--- a/profilers-ui/src/com/android/tools/profilers/sessions/SessionItemView.java
+++ b/profilers-ui/src/com/android/tools/profilers/sessions/SessionItemView.java
@@ -25,6 +25,8 @@
import com.android.tools.adtui.stdui.ContextMenuItem;
import com.android.tools.adtui.stdui.DefaultContextMenuItem;
import com.android.tools.adtui.stdui.StandardColors;
+import com.android.tools.profilers.ExportArtifactUtils;
+import com.android.tools.profilers.ExportableArtifact;
import com.android.tools.profilers.SupportLevel;
import com.google.common.collect.ImmutableList;
import com.intellij.ide.BrowserUtil;
@@ -184,6 +186,23 @@
return ImmutableList.of(endAction, ContextMenuItem.SEPARATOR, deleteAction);
}
+ @Override
+ protected void exportArtifact() {
+ assert getArtifact().getCanExport();
+
+ List<SessionArtifact<?>> childArtifacts = getArtifact().getChildArtifacts();
+ assert childArtifacts.size() == 1;
+ SessionArtifact<?> artifact = childArtifacts.get(0);
+
+ assert !artifact.isOngoing();
+
+ assert artifact instanceof ExportableArtifact;
+ ExportableArtifact exportableArtifact = (ExportableArtifact) artifact;
+
+ ExportArtifactUtils.exportArtifact(exportableArtifact, artifact::export, getSessionsView().getIdeProfilerComponents(),
+ getProfilers().getIdeServices());
+ }
+
/**
* A component for rendering a green dot in {@link SessionItemView} to indicate that the session is ongoing.
*/
diff --git a/app-quality-insights/ide/testSrc/com/android/tools/idea/insights/vcs/TestUtils.kt b/profilers/src/com/android/tools/profilers/ExportableArtifact.kt
similarity index 64%
rename from app-quality-insights/ide/testSrc/com/android/tools/idea/insights/vcs/TestUtils.kt
rename to profilers/src/com/android/tools/profilers/ExportableArtifact.kt
index e890913..6c15ccb 100644
--- a/app-quality-insights/ide/testSrc/com/android/tools/idea/insights/vcs/TestUtils.kt
+++ b/profilers/src/com/android/tools/profilers/ExportableArtifact.kt
@@ -13,12 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tools.idea.insights.vcs
+package com.android.tools.profilers
-import com.android.tools.idea.project.DefaultModuleSystem
-import com.android.tools.idea.projectsystem.getModuleSystem
-import com.intellij.openapi.module.Module
-
-fun Module.updateVcsInfoFlagInModel(enable: Boolean) {
- (getModuleSystem() as DefaultModuleSystem).enableVcsInfo = enable
-}
+interface ExportableArtifact {
+ val exportableName: String
+ val exportExtension: String
+}
\ No newline at end of file
diff --git a/profilers/src/com/android/tools/profilers/StudioProfilers.java b/profilers/src/com/android/tools/profilers/StudioProfilers.java
index 6b2e094..6e5be0f 100644
--- a/profilers/src/com/android/tools/profilers/StudioProfilers.java
+++ b/profilers/src/com/android/tools/profilers/StudioProfilers.java
@@ -53,6 +53,8 @@
import com.android.tools.profilers.memory.MemoryProfiler;
import com.android.tools.profilers.sessions.SessionAspect;
import com.android.tools.profilers.sessions.SessionsManager;
+import com.android.tools.profilers.tasks.ProfilerTaskType;
+import com.android.tools.profilers.tasks.taskhandlers.ProfilerTaskHandler;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
@@ -60,6 +62,7 @@
import com.google.common.collect.Sets;
import com.google.common.hash.Hashing;
import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import java.io.File;
@@ -133,6 +136,9 @@
@NotNull
private final IdeProfilerServices myIdeServices;
+ @Nullable
+ private Project myProject;
+
/**
* Processes from devices come from the latest update, and are filtered to include only ALIVE ones and {@code myProcess}.
*/
@@ -145,6 +151,8 @@
private Map<Long, Common.Stream> myStreamIdToStreams;
+ private final Map<ProfilerTaskType, ProfilerTaskHandler> myTaskHandlers;
+
@NotNull private final SessionsManager mySessionsManager;
@Nullable
@@ -195,19 +203,44 @@
private TransportEventPoller myTransportPoller;
+ @VisibleForTesting
public StudioProfilers(@NotNull ProfilerClient client, @NotNull IdeProfilerServices ideServices) {
this(client, ideServices, new FpsTimer(PROFILERS_UPDATE_RATE));
}
+ /**
+ * Under the Task-Based UX, this constructor serves as the primary constructor to create the StudioProfilers instance for the Profiler
+ * tool window. What differentiates it from other StudioProfilers constructors is the addition of the project and taskHandlers parameters.
+ * The project is utilized to interface to the tool window code, allowing us to create and open tabs from StudioProfilers. The
+ * taskHandlers is just a map of task types to their respective handlers. These task handlers and their functionality can now be utilized
+ * in profiler-level code, not just toolwindow code where they are created.
+ */
+ public StudioProfilers(@NotNull ProfilerClient client,
+ @NotNull IdeProfilerServices ideServices,
+ @Nullable Project project,
+ @NotNull HashMap<ProfilerTaskType, ProfilerTaskHandler> taskHandlers) {
+ this(client, ideServices, new FpsTimer(PROFILERS_UPDATE_RATE), project, taskHandlers);
+ }
+
@VisibleForTesting
public StudioProfilers(@NotNull ProfilerClient client, @NotNull IdeProfilerServices ideServices, @NotNull StopwatchTimer timer) {
+ this(client, ideServices, timer, null, new HashMap<>());
+ }
+
+ private StudioProfilers(@NotNull ProfilerClient client,
+ @NotNull IdeProfilerServices ideServices,
+ @NotNull StopwatchTimer timer,
+ @Nullable Project project,
+ @NotNull HashMap<ProfilerTaskType, ProfilerTaskHandler> taskHandlers) {
myClient = client;
myIdeServices = ideServices;
+ myProject = project;
myStage = createDefaultStage();
mySessionsManager = new SessionsManager(this);
mySessionChangeListener = new HashMap<>();
myDeviceToStreamIds = new HashMap<>();
myStreamIdToStreams = new HashMap<>();
+ myTaskHandlers = taskHandlers;
myStage.enter();
myUpdater = new Updater(timer);
@@ -821,6 +854,13 @@
}
/**
+ * @return map of task types to their respective task handlers.
+ */
+ public Map<ProfilerTaskType, ProfilerTaskHandler> getTaskHandlers() {
+ return myTaskHandlers;
+ }
+
+ /**
* Return the selected app's package name if present, otherwise returns empty string.
* <p>
* <p>TODO (78597376): Clean up the method to make it reusable.</p>
diff --git a/profilers/src/com/android/tools/profilers/cpu/CpuCaptureSessionArtifact.java b/profilers/src/com/android/tools/profilers/cpu/CpuCaptureSessionArtifact.java
index 73fbe27..39ec41b 100644
--- a/profilers/src/com/android/tools/profilers/cpu/CpuCaptureSessionArtifact.java
+++ b/profilers/src/com/android/tools/profilers/cpu/CpuCaptureSessionArtifact.java
@@ -19,7 +19,9 @@
import com.android.tools.adtui.model.formatter.TimeFormatter;
import com.android.tools.profiler.proto.Common;
import com.android.tools.profiler.proto.Trace;
+import com.android.tools.profilers.ExportableArtifact;
import com.android.tools.profilers.StudioProfilers;
+import com.android.tools.profilers.cpu.config.ProfilingConfiguration;
import com.android.tools.profilers.sessions.SessionArtifact;
import java.io.OutputStream;
import java.util.ArrayList;
@@ -30,7 +32,7 @@
/**
* An artifact representation of a CPU capture.
*/
-public class CpuCaptureSessionArtifact implements SessionArtifact<Trace.TraceInfo> {
+public class CpuCaptureSessionArtifact implements SessionArtifact<Trace.TraceInfo>, ExportableArtifact {
@NotNull private final StudioProfilers myProfilers;
@NotNull private final Common.Session mySession;
@@ -165,7 +167,19 @@
@Override
public void export(@NotNull OutputStream outputStream) {
assert getCanExport();
- CpuProfiler.saveCaptureToFile(myProfilers, getArtifactProto(), outputStream);
+ CpuProfiler.saveCaptureToFile(myProfilers, getSession(), getArtifactProto(), outputStream);
+ }
+
+ @NotNull
+ @Override
+ public String getExportableName() {
+ return CpuProfiler.generateCaptureFileName(ProfilingConfiguration.TraceType.from(getArtifactProto().getConfiguration()));
+ }
+
+ @NotNull
+ @Override
+ public String getExportExtension() {
+ return "trace";
}
private boolean isImportedSession() {
diff --git a/profilers/src/com/android/tools/profilers/cpu/CpuProfiler.java b/profilers/src/com/android/tools/profilers/cpu/CpuProfiler.java
index c0c84b3..475a9c1 100644
--- a/profilers/src/com/android/tools/profilers/cpu/CpuProfiler.java
+++ b/profilers/src/com/android/tools/profilers/cpu/CpuProfiler.java
@@ -172,11 +172,14 @@
/**
* Copies the content of the trace file corresponding to a {@link CpuTraceInfo} to a given {@link FileOutputStream}.
*/
- static void saveCaptureToFile(@NotNull StudioProfilers profilers, @NotNull TraceInfo info, @NotNull OutputStream outputStream) {
+ static void saveCaptureToFile(@NotNull StudioProfilers profilers,
+ @NotNull Common.Session session,
+ @NotNull TraceInfo info,
+ @NotNull OutputStream outputStream) {
try {
Transport.BytesRequest traceRequest = Transport.BytesRequest.newBuilder()
- .setStreamId(profilers.getSession().getStreamId())
+ .setStreamId(session.getStreamId())
.setId(String.valueOf(info.getTraceId()))
.build();
Transport.BytesResponse traceResponse = profilers.getClient().getTransportClient().getBytes(traceRequest);
diff --git a/profilers/src/com/android/tools/profilers/memory/HeapProfdSessionArtifact.java b/profilers/src/com/android/tools/profilers/memory/HeapProfdSessionArtifact.java
index 012ff5f..a309fcd 100644
--- a/profilers/src/com/android/tools/profilers/memory/HeapProfdSessionArtifact.java
+++ b/profilers/src/com/android/tools/profilers/memory/HeapProfdSessionArtifact.java
@@ -29,6 +29,7 @@
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import org.jetbrains.annotations.NotNull;
public class HeapProfdSessionArtifact extends MemorySessionArtifact<Trace.TraceInfo> {
@@ -63,6 +64,18 @@
}
}
+ @NotNull
+ @Override
+ public String getExportableName() {
+ return MemoryProfiler.generateCaptureFileName();
+ }
+
+ @NotNull
+ @Override
+ public String getExportExtension() {
+ return "heapprofd";
+ }
+
public static List<SessionArtifact<?>> getSessionArtifacts(@NotNull StudioProfilers profilers,
@NotNull Common.Session session,
@NotNull Common.SessionMetaData sessionMetaData) {
diff --git a/profilers/src/com/android/tools/profilers/memory/HprofSessionArtifact.java b/profilers/src/com/android/tools/profilers/memory/HprofSessionArtifact.java
index 8dc6c24..759d4a9 100644
--- a/profilers/src/com/android/tools/profilers/memory/HprofSessionArtifact.java
+++ b/profilers/src/com/android/tools/profilers/memory/HprofSessionArtifact.java
@@ -24,9 +24,11 @@
import com.android.tools.profilers.memory.adapters.CaptureObject;
import com.android.tools.profilers.sessions.SessionArtifact;
import com.intellij.util.containers.ContainerUtil;
+import java.io.File;
import java.io.OutputStream;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import org.jetbrains.annotations.NotNull;
/**
@@ -59,6 +61,18 @@
getProfilers().getIdeServices().getFeatureTracker());
}
+ @NotNull
+ @Override
+ public String getExportableName() {
+ return MemoryProfiler.generateCaptureFileName();
+ }
+
+ @NotNull
+ @Override
+ public String getExportExtension() {
+ return "hprof";
+ }
+
public static List<SessionArtifact<?>> getSessionArtifacts(@NotNull StudioProfilers profilers,
@NotNull Common.Session session,
@NotNull Common.SessionMetaData sessionMetaData) {
diff --git a/profilers/src/com/android/tools/profilers/memory/LegacyAllocationsSessionArtifact.java b/profilers/src/com/android/tools/profilers/memory/LegacyAllocationsSessionArtifact.java
index 22358ec..f840fa6 100644
--- a/profilers/src/com/android/tools/profilers/memory/LegacyAllocationsSessionArtifact.java
+++ b/profilers/src/com/android/tools/profilers/memory/LegacyAllocationsSessionArtifact.java
@@ -21,6 +21,7 @@
import com.android.tools.adtui.model.formatter.TimeFormatter;
import com.android.tools.profiler.proto.Common;
import com.android.tools.profiler.proto.Memory;
+import com.android.tools.profilers.ExportableArtifact;
import com.android.tools.profilers.StudioProfilers;
import com.android.tools.profilers.sessions.SessionArtifact;
import java.io.OutputStream;
@@ -30,7 +31,7 @@
/**
* A session artifact representation of a memory allocation recording (legacy).
*/
-public class LegacyAllocationsSessionArtifact implements SessionArtifact<Memory.AllocationsInfo> {
+public class LegacyAllocationsSessionArtifact implements SessionArtifact<Memory.AllocationsInfo>, ExportableArtifact {
@NotNull private final StudioProfilers myProfilers;
@NotNull private final Common.Session mySession;
@@ -137,4 +138,16 @@
assert getCanExport();
saveLegacyAllocationToFile(myProfilers.getClient(), mySession, myInfo, outputStream, myProfilers.getIdeServices().getFeatureTracker());
}
+
+ @NotNull
+ @Override
+ public String getExportableName() {
+ return MemoryProfiler.generateCaptureFileName();
+ }
+
+ @NotNull
+ @Override
+ public String getExportExtension() {
+ return "alloc";
+ }
}
diff --git a/profilers/src/com/android/tools/profilers/memory/MemorySessionArtifact.java b/profilers/src/com/android/tools/profilers/memory/MemorySessionArtifact.java
index 150f1bf..e53a79d 100644
--- a/profilers/src/com/android/tools/profilers/memory/MemorySessionArtifact.java
+++ b/profilers/src/com/android/tools/profilers/memory/MemorySessionArtifact.java
@@ -19,6 +19,7 @@
import com.android.tools.adtui.model.formatter.TimeFormatter;
import com.android.tools.idea.protobuf.GeneratedMessageV3;
import com.android.tools.profiler.proto.Common;
+import com.android.tools.profilers.ExportableArtifact;
import com.android.tools.profilers.StudioProfilers;
import com.android.tools.profilers.sessions.SessionArtifact;
import java.util.concurrent.TimeUnit;
@@ -27,7 +28,7 @@
/**
* An artifact representation of a memory capture.
*/
-public abstract class MemorySessionArtifact<T extends GeneratedMessageV3> implements SessionArtifact<T> {
+public abstract class MemorySessionArtifact<T extends GeneratedMessageV3> implements SessionArtifact<T>, ExportableArtifact {
@NotNull private final StudioProfilers myProfilers;
@NotNull private final Common.Session mySession;
diff --git a/profilers/src/com/android/tools/profilers/sessions/SessionItem.kt b/profilers/src/com/android/tools/profilers/sessions/SessionItem.kt
index 40da324..7c02df5 100644
--- a/profilers/src/com/android/tools/profilers/sessions/SessionItem.kt
+++ b/profilers/src/com/android/tools/profilers/sessions/SessionItem.kt
@@ -24,6 +24,7 @@
import com.android.tools.profilers.StudioMonitorStage
import com.android.tools.profilers.StudioProfilers
import com.google.common.annotations.VisibleForTesting
+import java.io.OutputStream
import java.util.concurrent.TimeUnit
/**
@@ -69,7 +70,18 @@
override val isOngoing
get() = SessionsManager.isSessionAlive(activeSession)
- override val canExport = false
+ /**
+ * The MEMORY_CAPTURE and CPU_CAPTURE session types are indicative of imported memory and CPU sessions respectively.
+ */
+ override val canExport = sessionMetaData.type == SessionMetaData.SessionType.MEMORY_CAPTURE
+ || sessionMetaData.type == SessionMetaData.SessionType.CPU_CAPTURE
+
+ override fun export(outputStream: OutputStream) {
+ assert(canExport)
+ assert(childArtifacts.size == 1)
+ val artifact = childArtifacts.first()
+ artifact.export(outputStream)
+ }
/**
* Update the [Common.Session] object. Note that while the content within the session can change, the new session instance should
diff --git a/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/CallstackSampleTaskHandler.kt b/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/CallstackSampleTaskHandler.kt
new file mode 100644
index 0000000..254564d
--- /dev/null
+++ b/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/CallstackSampleTaskHandler.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.profilers.tasks.taskhandlers.singleartifact.cpu
+
+import com.android.tools.profilers.cpu.CpuCaptureSessionArtifact
+import com.android.tools.profilers.cpu.config.SimpleperfConfiguration
+import com.android.tools.profilers.sessions.SessionArtifact
+import com.android.tools.profilers.sessions.SessionsManager
+
+class CallstackSampleTaskHandler(sessionsManager: SessionsManager) : CpuTaskHandler(sessionsManager) {
+ override fun getCpuRecordingConfig() = SimpleperfConfiguration(getTaskName())
+
+ override fun supportsArtifact(artifact: SessionArtifact<*>?) =
+ artifact is CpuCaptureSessionArtifact
+ && artifact.artifactProto.hasConfiguration()
+ && artifact.artifactProto.configuration.hasSimpleperfOptions()
+
+ override fun getTaskName() = "Callstack Sample"
+}
\ No newline at end of file
diff --git a/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/CpuTaskHandler.kt b/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/CpuTaskHandler.kt
new file mode 100644
index 0000000..d8d0a36
--- /dev/null
+++ b/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/CpuTaskHandler.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.profilers.tasks.taskhandlers.singleartifact.cpu
+
+import com.android.tools.profiler.proto.Common
+import com.android.tools.profilers.cpu.CpuCaptureSessionArtifact
+import com.android.tools.profilers.cpu.CpuProfilerStage
+import com.android.tools.profilers.cpu.config.ProfilingConfiguration
+import com.android.tools.profilers.sessions.SessionItem
+import com.android.tools.profilers.sessions.SessionsManager
+import com.android.tools.profilers.tasks.args.TaskArgs
+import com.android.tools.profilers.tasks.args.singleartifact.cpu.CpuTaskArgs
+import com.android.tools.profilers.tasks.taskhandlers.TaskHandlerUtils.findTaskArtifact
+import com.android.tools.profilers.tasks.taskhandlers.singleartifact.SingleArtifactTaskHandler
+import com.intellij.util.asSafely
+
+abstract class CpuTaskHandler(private val sessionsManager: SessionsManager) : SingleArtifactTaskHandler<CpuProfilerStage>(sessionsManager) {
+ override fun setupStage() {
+ val studioProfilers = sessionsManager.studioProfilers
+ val stage = CpuProfilerStage(studioProfilers, this::stopTask)
+ stage.profilerConfigModel.profilingConfiguration = getCpuRecordingConfig()
+ studioProfilers.stage = stage
+ super.stage = stage
+ }
+
+ override fun startCapture(stage: CpuProfilerStage) {
+ stage.startCpuRecording()
+ }
+
+ override fun stopCapture(stage: CpuProfilerStage) {
+ stage.stopCpuRecording()
+ }
+
+ override fun loadTask(args: TaskArgs?): Boolean {
+ if (args !is CpuTaskArgs) {
+ handleError("The task arguments (TaskArgs) supplied are not of the expected type (CpuTaskArgs)")
+ return false
+ }
+ loadCapture(args.getCpuCaptureArtifact())
+ return true
+ }
+
+ override fun createArgs(
+ sessionItems: Map<Long, SessionItem>,
+ selectedSession: Common.Session
+ ): CpuTaskArgs? {
+ val artifact = findTaskArtifact(selectedSession, sessionItems, ::supportsArtifact)
+
+ // Only if the underlying artifact is non-null should the TaskArgs be non-null
+ return if (supportsArtifact(artifact)) {
+ artifact.asSafely<CpuCaptureSessionArtifact>()?.let { CpuTaskArgs(it) }
+ }
+ else {
+ null
+ }
+ }
+
+ protected abstract fun getCpuRecordingConfig(): ProfilingConfiguration
+}
\ No newline at end of file
diff --git a/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/JavaKotlinMethodSampleTaskHandler.kt b/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/JavaKotlinMethodSampleTaskHandler.kt
new file mode 100644
index 0000000..3973d0d
--- /dev/null
+++ b/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/JavaKotlinMethodSampleTaskHandler.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.profilers.tasks.taskhandlers.singleartifact.cpu
+
+import com.android.tools.profilers.cpu.CpuCaptureSessionArtifact
+import com.android.tools.profilers.cpu.config.ArtSampledConfiguration
+import com.android.tools.profilers.sessions.SessionArtifact
+import com.android.tools.profilers.sessions.SessionsManager
+
+class JavaKotlinMethodSampleTaskHandler(sessionsManager: SessionsManager) : CpuTaskHandler(sessionsManager) {
+ override fun getCpuRecordingConfig() = ArtSampledConfiguration(getTaskName())
+
+ override fun supportsArtifact(artifact: SessionArtifact<*>?) =
+ artifact is CpuCaptureSessionArtifact
+ && artifact.artifactProto.hasConfiguration()
+ && artifact.artifactProto.configuration.hasArtOptions()
+
+ override fun getTaskName() = "Java/Kotlin Method Sample (legacy)"
+}
\ No newline at end of file
diff --git a/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/JavaKotlinMethodTraceTaskHandler.kt b/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/JavaKotlinMethodTraceTaskHandler.kt
new file mode 100644
index 0000000..d932078
--- /dev/null
+++ b/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/JavaKotlinMethodTraceTaskHandler.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.profilers.tasks.taskhandlers.singleartifact.cpu
+
+import com.android.tools.profilers.cpu.CpuCaptureSessionArtifact
+import com.android.tools.profilers.cpu.config.ArtInstrumentedConfiguration
+import com.android.tools.profilers.sessions.SessionArtifact
+import com.android.tools.profilers.sessions.SessionsManager
+
+class JavaKotlinMethodTraceTaskHandler(sessionsManager: SessionsManager) : CpuTaskHandler(sessionsManager) {
+ override fun getCpuRecordingConfig() = ArtInstrumentedConfiguration(getTaskName())
+
+ override fun supportsArtifact(artifact: SessionArtifact<*>?) =
+ artifact is CpuCaptureSessionArtifact
+ && artifact.artifactProto.hasConfiguration()
+ && artifact.artifactProto.configuration.hasArtOptions()
+
+ override fun getTaskName() = "Java/Kotlin Method Trace"
+}
\ No newline at end of file
diff --git a/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/SystemTraceTaskHandler.kt b/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/SystemTraceTaskHandler.kt
new file mode 100644
index 0000000..fed0f97
--- /dev/null
+++ b/profilers/src/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/SystemTraceTaskHandler.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.profilers.tasks.taskhandlers.singleartifact.cpu
+
+import com.android.tools.profilers.cpu.CpuCaptureSessionArtifact
+import com.android.tools.profilers.cpu.config.PerfettoConfiguration
+import com.android.tools.profilers.sessions.SessionArtifact
+import com.android.tools.profilers.sessions.SessionsManager
+
+class SystemTraceTaskHandler(sessionsManager: SessionsManager) : CpuTaskHandler(sessionsManager) {
+ override fun getCpuRecordingConfig() = PerfettoConfiguration(getTaskName(), false)
+
+ override fun supportsArtifact(artifact: SessionArtifact<*>?) =
+ artifact is CpuCaptureSessionArtifact
+ && artifact.artifactProto.hasConfiguration()
+ && (artifact.artifactProto.configuration.hasPerfettoOptions() || artifact.artifactProto.configuration.hasAtraceOptions())
+
+ override fun getTaskName() = "System Trace"
+}
\ No newline at end of file
diff --git a/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/TaskHandlerTestUtils.kt b/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/TaskHandlerTestUtils.kt
index 5f501e5..07ee822 100644
--- a/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/TaskHandlerTestUtils.kt
+++ b/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/TaskHandlerTestUtils.kt
@@ -35,14 +35,37 @@
object TaskHandlerTestUtils {
+ fun createCpuCaptureSessionArtifactWithConfig(profilers: StudioProfilers,
+ session: Common.Session,
+ sessionId: Long,
+ traceId: Long,
+ config: Trace.TraceConfiguration) = createCpuCaptureSessionArtifactWithConfig(profilers,
+ session,
+ sessionId,
+ traceId, 0, 0,
+ config)
+
+ /**
+ * Overload of createCpuCaptureSessionArtifactWithConfig that takes in from and end timestamps.
+ */
+ fun createCpuCaptureSessionArtifactWithConfig(profilers: StudioProfilers,
+ session: Common.Session,
+ sessionId: Long,
+ traceId: Long,
+ fromTimestamp: Long,
+ toTimestamp: Long,
+ config: Trace.TraceConfiguration): CpuCaptureSessionArtifact {
+ val sessionMetadata = Common.SessionMetaData.newBuilder().setSessionId(sessionId).build()
+ val info = Trace.TraceInfo.newBuilder().setFromTimestamp(fromTimestamp).setToTimestamp(toTimestamp).setTraceId(
+ traceId).setConfiguration(config.toBuilder()).build()
+ return CpuCaptureSessionArtifact(profilers, session, sessionMetadata, info)
+ }
+
fun createCpuCaptureSessionArtifact(profilers: StudioProfilers,
session: Common.Session,
sessionId: Long,
- traceId: Long): CpuCaptureSessionArtifact {
- val sessionMetadata = Common.SessionMetaData.newBuilder().setSessionId(sessionId).build()
- val info = Trace.TraceInfo.newBuilder().setTraceId(traceId).build()
- return CpuCaptureSessionArtifact(profilers, session, sessionMetadata, info)
- }
+ traceId: Long) = createCpuCaptureSessionArtifactWithConfig(profilers, session, sessionId, traceId,
+ Trace.TraceConfiguration.getDefaultInstance())
fun createHprofSessionArtifact(profilers: StudioProfilers, session: Common.Session,
startTimestamp: Long,
diff --git a/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/CallstackSampleTaskHandlerTest.kt b/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/CallstackSampleTaskHandlerTest.kt
new file mode 100644
index 0000000..b358f39
--- /dev/null
+++ b/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/CallstackSampleTaskHandlerTest.kt
@@ -0,0 +1,264 @@
+/*
+ * 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.profilers.tasks.taskhandlers.singleartifact.cpu
+
+import com.android.tools.adtui.model.FakeTimer
+import com.android.tools.idea.transport.faketransport.FakeGrpcChannel
+import com.android.tools.idea.transport.faketransport.FakeTransportService
+import com.android.tools.idea.transport.faketransport.commands.StartTrace
+import com.android.tools.idea.transport.faketransport.commands.StopTrace
+import com.android.tools.profiler.proto.Commands
+import com.android.tools.profiler.proto.Common
+import com.android.tools.profiler.proto.Common.Process.ExposureLevel
+import com.android.tools.profiler.proto.Trace
+import com.android.tools.profilers.FakeIdeProfilerServices
+import com.android.tools.profilers.ProfilerClient
+import com.android.tools.profilers.StudioProfilers
+import com.android.tools.profilers.cpu.CpuProfilerStage
+import com.android.tools.profilers.event.FakeEventService
+import com.android.tools.profilers.memory.HeapProfdSessionArtifact
+import com.android.tools.profilers.sessions.SessionsManager
+import com.android.tools.profilers.tasks.args.singleartifact.cpu.CpuTaskArgs
+import com.android.tools.profilers.tasks.taskhandlers.TaskHandlerTestUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import kotlin.test.assertFailsWith
+
+@RunWith(Parameterized::class)
+class CallstackSampleTaskHandlerTest(private val myExposureLevel: ExposureLevel) {
+ private val myTimer = FakeTimer()
+ private val myTransportService = FakeTransportService(myTimer, false)
+
+ @get:Rule
+ var myGrpcChannel = FakeGrpcChannel("CallstackSampleTaskHandlerTestChannel", myTransportService, FakeEventService())
+
+ private lateinit var myProfilers: StudioProfilers
+ private lateinit var ideProfilerServices: FakeIdeProfilerServices
+ private lateinit var myManager: SessionsManager
+ private lateinit var myCallstackSampleTaskHandler: CallstackSampleTaskHandler
+
+ @Before
+ fun setup() {
+ ideProfilerServices = FakeIdeProfilerServices()
+ myProfilers = StudioProfilers(
+ ProfilerClient(myGrpcChannel.channel),
+ ideProfilerServices,
+ myTimer
+ )
+ myManager = myProfilers.sessionsManager
+ myCallstackSampleTaskHandler = CallstackSampleTaskHandler(myManager)
+ assertThat(myManager.sessionArtifacts).isEmpty()
+ assertThat(myManager.selectedSession).isEqualTo(Common.Session.getDefaultInstance())
+ assertThat(myManager.profilingSession).isEqualTo(Common.Session.getDefaultInstance())
+ ideProfilerServices.enableTaskBasedUx(true)
+ }
+
+ @Test
+ fun testSupportsArtifactWithCallstackSampleSessionArtifact() {
+ val callstackSampleSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(),
+ 1L, 100L,
+ createDefaultSimpleperfTraceConfiguration())
+ assertThat(myCallstackSampleTaskHandler.supportsArtifact(callstackSampleSessionArtifact)).isTrue()
+ }
+
+ @Test
+ fun testSupportsArtifactWithNonCallstackSampleSessionArtifact() {
+ val heapProfdSessionArtifact = HeapProfdSessionArtifact(myProfilers, Common.Session.getDefaultInstance(),
+ Common.SessionMetaData.getDefaultInstance(),
+ Trace.TraceInfo.getDefaultInstance())
+ assertThat(myCallstackSampleTaskHandler.supportsArtifact(heapProfdSessionArtifact)).isFalse()
+ }
+
+ @Test
+ fun testStartTaskInvokedOnEnterWithAliveSession() {
+ TaskHandlerTestUtils.startSession(myExposureLevel, myProfilers, myTransportService, myTimer)
+ val callstackSampleSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(),
+ 1L,
+ 100L,
+ createDefaultSimpleperfTraceConfiguration())
+ val cpuTaskArgs = CpuTaskArgs(callstackSampleSessionArtifact)
+ myCallstackSampleTaskHandler.enter(cpuTaskArgs)
+ // The session is alive, so startTask and thus startCapture should be called.
+ assertThat(myCallstackSampleTaskHandler.stage!!.recordingModel.isRecording)
+ }
+
+ @Test
+ fun testStartTaskWithSetStage() {
+ TaskHandlerTestUtils.startSession(myExposureLevel, myProfilers, myTransportService, myTimer)
+ // To start the task and thus the capture, the stage must be set up before. This will be taken care of via the setupStage() method call,
+ // on enter of the task handler, but this test is testing the explicit invocation of startTask.
+ myCallstackSampleTaskHandler.setupStage()
+ myCallstackSampleTaskHandler.startTask()
+ assertThat(myCallstackSampleTaskHandler.stage!!.recordingModel.isRecording).isTrue()
+ }
+
+ @Test
+ fun testStartTaskWithUnsetStage() {
+ // To start the task and thus the capture, the stage must be set up before. Here we will test the case where startTask is invoked
+ // without the stage being set precondition being met.
+ val exception = assertFailsWith<Throwable> {
+ myCallstackSampleTaskHandler.startTask()
+ }
+ assertThat(myCallstackSampleTaskHandler.stage).isNull()
+ assertThat(exception.message).isEqualTo(
+ "There was an error with the Callstack Sample task. Error message: Cannot start the task as the InterimStage was null.")
+ }
+
+ @Test
+ fun testStopTaskSuccessfullyTerminatesRecording() {
+ TaskHandlerTestUtils.startSession(myExposureLevel, myProfilers, myTransportService, myTimer)
+ // First start the task successfully.
+ (myTransportService.getRegisteredCommand(Commands.Command.CommandType.START_TRACE) as StartTrace)
+ .startStatus = Trace.TraceStartStatus.newBuilder()
+ .setStatus(Trace.TraceStartStatus.Status.SUCCESS)
+ .build()
+ myCallstackSampleTaskHandler.setupStage()
+ myCallstackSampleTaskHandler.startTask()
+ assertThat(myCallstackSampleTaskHandler.stage!!.recordingModel.isRecording).isTrue()
+
+ // Wait for successful start event to be consumed.
+ myTimer.tick(FakeTimer.ONE_SECOND_IN_NS)
+ // Stop the task successfully.
+ (myTransportService.getRegisteredCommand(Commands.Command.CommandType.STOP_TRACE) as StopTrace)
+ .stopStatus = Trace.TraceStopStatus.newBuilder()
+ .setStatus(Trace.TraceStopStatus.Status.SUCCESS)
+ .build()
+ myCallstackSampleTaskHandler.stopTask()
+ // Wait for successful end event to be consumed.
+ myTimer.tick(FakeTimer.ONE_SECOND_IN_NS)
+ assertThat(myCallstackSampleTaskHandler.stage!!.recordingModel.isRecording).isFalse()
+ }
+
+ @Test
+ fun testLoadTaskInvokedOnEnterWithDeadSession() {
+ TaskHandlerTestUtils.startAndStopSession(myExposureLevel, myProfilers, myManager, myTransportService, myTimer)
+
+ // Before enter + loadTask, the stage should not be set yet.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+
+ // Create a fake CpuCaptureSessionArtifact that uses a Simplperf (Callstack Sample) configuration.
+ val callstackSampleSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(),
+ 1L,
+ 100L,
+ createDefaultSimpleperfTraceConfiguration())
+ val cpuTaskArgs = CpuTaskArgs(callstackSampleSessionArtifact)
+ // The session is not alive (dead) so loadTask and thus loadCapture should be called.
+ val argsSuccessfullyUsed = myCallstackSampleTaskHandler.enter(cpuTaskArgs)
+ assertThat(argsSuccessfullyUsed).isTrue()
+
+ // Verify that the artifact doSelect behavior is called by checking if the stage was set to CpuProfilerStage.
+ assertThat(myProfilers.stage).isInstanceOf(CpuProfilerStage::class.java)
+ }
+
+ @Test
+ fun testLoadTaskWithNonNullTaskArgs() {
+ TaskHandlerTestUtils.startAndStopSession(myExposureLevel, myProfilers, myManager, myTransportService, myTimer)
+
+ // Before enter + loadTask, the stage should not be set yet.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+
+ val callstackSampleSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(),
+ 1L,
+ 100L,
+ createDefaultSimpleperfTraceConfiguration())
+ val cpuTaskArgs = CpuTaskArgs(callstackSampleSessionArtifact)
+ val argsSuccessfullyUsed = myCallstackSampleTaskHandler.loadTask(cpuTaskArgs)
+ assertThat(argsSuccessfullyUsed).isTrue()
+
+ // Verify that the artifact doSelect behavior was called by checking if the stage was set to CpuProfilerStage.
+ assertThat(myProfilers.stage).isInstanceOf(CpuProfilerStage::class.java)
+ }
+
+ @Test
+ fun testLoadTaskWithNullTaskArgs() {
+ TaskHandlerTestUtils.startAndStopSession(myExposureLevel, myProfilers, myManager, myTransportService, myTimer)
+
+ // Before enter + loadTask, the stage should not be set yet.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+
+ val exception = assertFailsWith<Throwable> {
+ myCallstackSampleTaskHandler.loadTask(null)
+ }
+
+ assertThat(exception.message).isEqualTo(
+ "There was an error with the Callstack Sample task. Error message: The task arguments (TaskArgs) supplied are not of the expected " +
+ "type (CpuTaskArgs).")
+
+ // Verify that the artifact doSelect behavior was not called by checking if the stage was not set to CpuProfilerStage.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+ }
+
+ @Test
+ fun testCreateArgsSuccessfully() {
+ val selectedSession = Common.Session.newBuilder().setSessionId(1).setEndTimestamp(100).build()
+ val sessionIdToSessionItems = mapOf(
+ 1L to TaskHandlerTestUtils.createSessionItem(myProfilers, selectedSession, 1, listOf(
+ TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers, selectedSession, 1, 100L,
+ 5L, 500L,
+ createDefaultSimpleperfTraceConfiguration()))),
+ )
+
+ val cpuTaskArgs = myCallstackSampleTaskHandler.createArgs(sessionIdToSessionItems, selectedSession)
+ assertThat(cpuTaskArgs).isNotNull()
+ assertThat(cpuTaskArgs).isInstanceOf(CpuTaskArgs::class.java)
+ assertThat(cpuTaskArgs!!.getCpuCaptureArtifact()).isNotNull()
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.configuration.hasSimpleperfOptions()).isTrue()
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.fromTimestamp).isEqualTo(5L)
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.toTimestamp).isEqualTo(500L)
+ }
+
+ @Test
+ fun testCreateArgsFails() {
+ // By setting a session id that does not match any of the session items, the task artifact will not be found in the call to createArgs
+ // will fail to be constructed.
+ val selectedSession = Common.Session.newBuilder().setSessionId(0).setEndTimestamp(100).build()
+ val sessionIdToSessionItems = mapOf(
+ 1L to TaskHandlerTestUtils.createSessionItem(myProfilers, selectedSession, 1, listOf(
+ TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers, selectedSession, 1, 100L,
+ 5L, 500L,
+ createDefaultSimpleperfTraceConfiguration()))),
+ )
+
+ val cpuTaskArgs = myCallstackSampleTaskHandler.createArgs(sessionIdToSessionItems, selectedSession)
+ // A return value of null indicates the task args were not constructed correctly (the underlying artifact was not found or supported by
+ // the task).
+ assertThat(cpuTaskArgs).isNull()
+ }
+
+ @Test
+ fun testGetTaskName() {
+ assertThat(myCallstackSampleTaskHandler.getTaskName()).isEqualTo("Callstack Sample")
+ }
+
+ private fun createDefaultSimpleperfTraceConfiguration() = Trace.TraceConfiguration.newBuilder().setSimpleperfOptions(
+ Trace.SimpleperfOptions.getDefaultInstance()).build()
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data(): Collection<ExposureLevel> {
+ return listOf(ExposureLevel.DEBUGGABLE, ExposureLevel.PROFILEABLE)
+ }
+ }
+}
\ No newline at end of file
diff --git a/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/JavaKotlinMethodSampleTaskHandlerTest.kt b/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/JavaKotlinMethodSampleTaskHandlerTest.kt
new file mode 100644
index 0000000..2b3fa6f
--- /dev/null
+++ b/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/JavaKotlinMethodSampleTaskHandlerTest.kt
@@ -0,0 +1,267 @@
+/*
+ * 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.profilers.tasks.taskhandlers.singleartifact.cpu
+
+import com.android.tools.adtui.model.FakeTimer
+import com.android.tools.idea.transport.faketransport.FakeGrpcChannel
+import com.android.tools.idea.transport.faketransport.FakeTransportService
+import com.android.tools.idea.transport.faketransport.commands.StartTrace
+import com.android.tools.idea.transport.faketransport.commands.StopTrace
+import com.android.tools.profiler.proto.Commands
+import com.android.tools.profiler.proto.Common
+import com.android.tools.profiler.proto.Common.Process.ExposureLevel
+import com.android.tools.profiler.proto.Trace
+import com.android.tools.profiler.proto.Trace.TraceMode
+import com.android.tools.profilers.FakeIdeProfilerServices
+import com.android.tools.profilers.ProfilerClient
+import com.android.tools.profilers.StudioProfilers
+import com.android.tools.profilers.cpu.CpuProfilerStage
+import com.android.tools.profilers.event.FakeEventService
+import com.android.tools.profilers.memory.HeapProfdSessionArtifact
+import com.android.tools.profilers.sessions.SessionsManager
+import com.android.tools.profilers.tasks.args.singleartifact.cpu.CpuTaskArgs
+import com.android.tools.profilers.tasks.taskhandlers.TaskHandlerTestUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import kotlin.test.assertFailsWith
+
+@RunWith(Parameterized::class)
+class JavaKotlinMethodSampleTaskHandlerTest(private val myExposureLevel: ExposureLevel) {
+ private val myTimer = FakeTimer()
+ private val myTransportService = FakeTransportService(myTimer, false)
+
+ @get:Rule
+ var myGrpcChannel = FakeGrpcChannel("JavaKotlinMethodSampleTaskHandlerTestChannel", myTransportService, FakeEventService())
+
+ private lateinit var myProfilers: StudioProfilers
+ private lateinit var ideProfilerServices: FakeIdeProfilerServices
+ private lateinit var myManager: SessionsManager
+ private lateinit var myJavaKotlinMethodSampleTaskHandler: JavaKotlinMethodSampleTaskHandler
+
+ @Before
+ fun setup() {
+ ideProfilerServices = FakeIdeProfilerServices()
+ myProfilers = StudioProfilers(
+ ProfilerClient(myGrpcChannel.channel),
+ ideProfilerServices,
+ myTimer
+ )
+ myManager = myProfilers.sessionsManager
+ myJavaKotlinMethodSampleTaskHandler = JavaKotlinMethodSampleTaskHandler(myManager)
+ assertThat(myManager.sessionArtifacts).isEmpty()
+ assertThat(myManager.selectedSession).isEqualTo(Common.Session.getDefaultInstance())
+ assertThat(myManager.profilingSession).isEqualTo(Common.Session.getDefaultInstance())
+ ideProfilerServices.enableTaskBasedUx(true)
+ }
+
+ @Test
+ fun testSupportsArtifactWithJavaKotlinMethodSampleSessionArtifact() {
+ val javaKotlinMethodSampleSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(),
+ 1L, 100L,
+ createDefaultArtSampleTraceConfiguration())
+ assertThat(myJavaKotlinMethodSampleTaskHandler.supportsArtifact(javaKotlinMethodSampleSessionArtifact)).isTrue()
+ }
+
+ @Test
+ fun testSupportsArtifactWithNonJavaKotlinMethodSampleSessionArtifact() {
+ val heapProfdSessionArtifact = HeapProfdSessionArtifact(myProfilers, Common.Session.getDefaultInstance(),
+ Common.SessionMetaData.getDefaultInstance(),
+ Trace.TraceInfo.getDefaultInstance())
+ assertThat(myJavaKotlinMethodSampleTaskHandler.supportsArtifact(heapProfdSessionArtifact)).isFalse()
+ }
+
+ @Test
+ fun testStartTaskInvokedOnEnterWithAliveSession() {
+ TaskHandlerTestUtils.startSession(myExposureLevel, myProfilers, myTransportService, myTimer)
+ val javaKotlinMethodSampleSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(),
+ 1L,
+ 100L,
+ createDefaultArtSampleTraceConfiguration())
+ val cpuTaskArgs = CpuTaskArgs(javaKotlinMethodSampleSessionArtifact)
+ myJavaKotlinMethodSampleTaskHandler.enter(cpuTaskArgs)
+ // The session is alive, so startTask and thus startCapture should be called.
+ assertThat(myJavaKotlinMethodSampleTaskHandler.stage!!.recordingModel.isRecording)
+ }
+
+ @Test
+ fun testStartTaskWithSetStage() {
+ TaskHandlerTestUtils.startSession(myExposureLevel, myProfilers, myTransportService, myTimer)
+ // To start the task and thus the capture, the stage must be set up before. This will be taken care of via the setupStage() method call,
+ // on enter of the task handler, but this test is testing the explicit invocation of startTask.
+ myJavaKotlinMethodSampleTaskHandler.setupStage()
+ myJavaKotlinMethodSampleTaskHandler.startTask()
+ assertThat(myJavaKotlinMethodSampleTaskHandler.stage!!.recordingModel.isRecording).isTrue()
+ }
+
+ @Test
+ fun testStartTaskWithUnsetStage() {
+ // To start the task and thus the capture, the stage must be set up before. Here we will test the case where startTask is invoked
+ // without the stage being set precondition being met.
+ val exception = assertFailsWith<Throwable> {
+ myJavaKotlinMethodSampleTaskHandler.startTask()
+ }
+ assertThat(myJavaKotlinMethodSampleTaskHandler.stage).isNull()
+ assertThat(exception.message).isEqualTo(
+ "There was an error with the Java/Kotlin Method Sample (legacy) task. Error message: Cannot start the task as the InterimStage " +
+ "was null.")
+ }
+
+ @Test
+ fun testStopTaskSuccessfullyTerminatesRecording() {
+ TaskHandlerTestUtils.startSession(myExposureLevel, myProfilers, myTransportService, myTimer)
+ // First start the task successfully.
+ (myTransportService.getRegisteredCommand(Commands.Command.CommandType.START_TRACE) as StartTrace)
+ .startStatus = Trace.TraceStartStatus.newBuilder()
+ .setStatus(Trace.TraceStartStatus.Status.SUCCESS)
+ .build()
+ myJavaKotlinMethodSampleTaskHandler.setupStage()
+ myJavaKotlinMethodSampleTaskHandler.startTask()
+ assertThat(myJavaKotlinMethodSampleTaskHandler.stage!!.recordingModel.isRecording).isTrue()
+
+ // Wait for successful start event to be consumed.
+ myTimer.tick(FakeTimer.ONE_SECOND_IN_NS)
+ // Stop the task successfully.
+ (myTransportService.getRegisteredCommand(Commands.Command.CommandType.STOP_TRACE) as StopTrace)
+ .stopStatus = Trace.TraceStopStatus.newBuilder()
+ .setStatus(Trace.TraceStopStatus.Status.SUCCESS)
+ .build()
+ myJavaKotlinMethodSampleTaskHandler.stopTask()
+ // Wait for successful end event to be consumed.
+ myTimer.tick(FakeTimer.ONE_SECOND_IN_NS)
+ assertThat(myJavaKotlinMethodSampleTaskHandler.stage!!.recordingModel.isRecording).isFalse()
+ }
+
+ @Test
+ fun testLoadTaskInvokedOnEnterWithDeadSession() {
+ TaskHandlerTestUtils.startAndStopSession(myExposureLevel, myProfilers, myManager, myTransportService, myTimer)
+
+ // Before enter + loadTask, the stage should not be set yet.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+
+ // Create a fake CpuCaptureSessionArtifact that uses an ART Sampled (Java/Kotlin Method Sample) configuration.
+ val javaKotlinMethodSampleSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(),
+ 1L,
+ 100L,
+ createDefaultArtSampleTraceConfiguration())
+ val cpuTaskArgs = CpuTaskArgs(javaKotlinMethodSampleSessionArtifact)
+ // The session is not alive (dead) so loadTask and thus loadCapture should be called.
+ val argsSuccessfullyUsed = myJavaKotlinMethodSampleTaskHandler.enter(cpuTaskArgs)
+ assertThat(argsSuccessfullyUsed).isTrue()
+
+ // Verify that the artifact doSelect behavior is called by checking if the stage was set to CpuProfilerStage.
+ assertThat(myProfilers.stage).isInstanceOf(CpuProfilerStage::class.java)
+ }
+
+ @Test
+ fun testLoadTaskWithNonNullTaskArgs() {
+ TaskHandlerTestUtils.startAndStopSession(myExposureLevel, myProfilers, myManager, myTransportService, myTimer)
+
+ // Before enter + loadTask, the stage should not be set yet.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+
+ val javaKotlinMethodSampleSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(),
+ 1L,
+ 100L,
+ createDefaultArtSampleTraceConfiguration())
+ val cpuTaskArgs = CpuTaskArgs(javaKotlinMethodSampleSessionArtifact)
+ val argsSuccessfullyUsed = myJavaKotlinMethodSampleTaskHandler.loadTask(cpuTaskArgs)
+ assertThat(argsSuccessfullyUsed).isTrue()
+
+ // Verify that the artifact doSelect behavior was called by checking if the stage was set to CpuProfilerStage.
+ assertThat(myProfilers.stage).isInstanceOf(CpuProfilerStage::class.java)
+ }
+
+ @Test
+ fun testLoadTaskWithNullTaskArgs() {
+ TaskHandlerTestUtils.startAndStopSession(myExposureLevel, myProfilers, myManager, myTransportService, myTimer)
+
+ // Before enter + loadTask, the stage should not be set yet.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+
+ val exception = assertFailsWith<Throwable> {
+ myJavaKotlinMethodSampleTaskHandler.loadTask(null)
+ }
+
+ assertThat(exception.message).isEqualTo(
+ "There was an error with the Java/Kotlin Method Sample (legacy) task. Error message: The task arguments (TaskArgs) supplied are " +
+ "not of the expected type (CpuTaskArgs).")
+
+ // Verify that the artifact doSelect behavior was not called by checking if the stage was not set to CpuProfilerStage.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+ }
+
+ @Test
+ fun testCreateArgsSuccessfully() {
+ val selectedSession = Common.Session.newBuilder().setSessionId(1).setEndTimestamp(100).build()
+ val sessionIdToSessionItems = mapOf(
+ 1L to TaskHandlerTestUtils.createSessionItem(myProfilers, selectedSession, 1, listOf(
+ TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers, selectedSession, 1, 100L,
+ 5L, 500L,
+ createDefaultArtSampleTraceConfiguration()))),
+ )
+
+ val cpuTaskArgs = myJavaKotlinMethodSampleTaskHandler.createArgs(sessionIdToSessionItems, selectedSession)
+ assertThat(cpuTaskArgs).isNotNull()
+ assertThat(cpuTaskArgs).isInstanceOf(CpuTaskArgs::class.java)
+ assertThat(cpuTaskArgs!!.getCpuCaptureArtifact()).isNotNull()
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.configuration.hasArtOptions()).isTrue()
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.configuration.artOptions.traceMode).isEqualTo(TraceMode.SAMPLED)
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.fromTimestamp).isEqualTo(5L)
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.toTimestamp).isEqualTo(500L)
+ }
+
+ @Test
+ fun testCreateArgsFails() {
+ // By setting a session id that does not match any of the session items, the task artifact will not be found in the call to createArgs
+ // will fail to be constructed.
+ val selectedSession = Common.Session.newBuilder().setSessionId(0).setEndTimestamp(100).build()
+ val sessionIdToSessionItems = mapOf(
+ 1L to TaskHandlerTestUtils.createSessionItem(myProfilers, selectedSession, 1, listOf(
+ TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers, selectedSession, 1, 100L,
+ 5L, 500L,
+ createDefaultArtSampleTraceConfiguration()))),
+ )
+
+ val cpuTaskArgs = myJavaKotlinMethodSampleTaskHandler.createArgs(sessionIdToSessionItems, selectedSession)
+ // A return value of null indicates the task args were not constructed correctly (the underlying artifact was not found or supported by
+ // the task).
+ assertThat(cpuTaskArgs).isNull()
+ }
+
+ @Test
+ fun testGetTaskName() {
+ assertThat(myJavaKotlinMethodSampleTaskHandler.getTaskName()).isEqualTo("Java/Kotlin Method Sample (legacy)")
+ }
+
+ private fun createDefaultArtSampleTraceConfiguration() = Trace.TraceConfiguration.newBuilder().setArtOptions(
+ Trace.ArtOptions.newBuilder().setTraceMode(Trace.TraceMode.SAMPLED).build()).build()
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data(): Collection<ExposureLevel> {
+ return listOf(ExposureLevel.DEBUGGABLE, ExposureLevel.PROFILEABLE)
+ }
+ }
+}
\ No newline at end of file
diff --git a/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/JavaKotlinMethodTraceTaskHandlerTest.kt b/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/JavaKotlinMethodTraceTaskHandlerTest.kt
new file mode 100644
index 0000000..0d96010
--- /dev/null
+++ b/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/JavaKotlinMethodTraceTaskHandlerTest.kt
@@ -0,0 +1,266 @@
+/*
+ * 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.profilers.tasks.taskhandlers.singleartifact.cpu
+
+import com.android.tools.adtui.model.FakeTimer
+import com.android.tools.idea.transport.faketransport.FakeGrpcChannel
+import com.android.tools.idea.transport.faketransport.FakeTransportService
+import com.android.tools.idea.transport.faketransport.commands.StartTrace
+import com.android.tools.idea.transport.faketransport.commands.StopTrace
+import com.android.tools.profiler.proto.Commands
+import com.android.tools.profiler.proto.Common
+import com.android.tools.profiler.proto.Common.Process.ExposureLevel
+import com.android.tools.profiler.proto.Trace
+import com.android.tools.profiler.proto.Trace.TraceMode
+import com.android.tools.profilers.FakeIdeProfilerServices
+import com.android.tools.profilers.ProfilerClient
+import com.android.tools.profilers.StudioProfilers
+import com.android.tools.profilers.cpu.CpuProfilerStage
+import com.android.tools.profilers.event.FakeEventService
+import com.android.tools.profilers.memory.HeapProfdSessionArtifact
+import com.android.tools.profilers.sessions.SessionsManager
+import com.android.tools.profilers.tasks.args.singleartifact.cpu.CpuTaskArgs
+import com.android.tools.profilers.tasks.taskhandlers.TaskHandlerTestUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import kotlin.test.assertFailsWith
+
+@RunWith(Parameterized::class)
+class JavaKotlinMethodTraceTaskHandlerTest(private val myExposureLevel: ExposureLevel) {
+ private val myTimer = FakeTimer()
+ private val myTransportService = FakeTransportService(myTimer, false)
+
+ @get:Rule
+ var myGrpcChannel = FakeGrpcChannel("JavaKotlinMethodTraceTaskHandlerTestChannel", myTransportService, FakeEventService())
+
+ private lateinit var myProfilers: StudioProfilers
+ private lateinit var ideProfilerServices: FakeIdeProfilerServices
+ private lateinit var myManager: SessionsManager
+ private lateinit var myJavaKotlinMethodTraceTaskHandler: JavaKotlinMethodTraceTaskHandler
+
+ @Before
+ fun setup() {
+ ideProfilerServices = FakeIdeProfilerServices()
+ myProfilers = StudioProfilers(
+ ProfilerClient(myGrpcChannel.channel),
+ ideProfilerServices,
+ myTimer
+ )
+ myManager = myProfilers.sessionsManager
+ myJavaKotlinMethodTraceTaskHandler = JavaKotlinMethodTraceTaskHandler(myManager)
+ assertThat(myManager.sessionArtifacts).isEmpty()
+ assertThat(myManager.selectedSession).isEqualTo(Common.Session.getDefaultInstance())
+ assertThat(myManager.profilingSession).isEqualTo(Common.Session.getDefaultInstance())
+ ideProfilerServices.enableTaskBasedUx(true)
+ }
+
+ @Test
+ fun testSupportsArtifactWithJavaKotlinMethodTraceSessionArtifact() {
+ val javaKotlinMethodTraceSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(),
+ 1L, 100L,
+ createDefaultArtInstrumentedTraceConfiguration())
+ assertThat(myJavaKotlinMethodTraceTaskHandler.supportsArtifact(javaKotlinMethodTraceSessionArtifact)).isTrue()
+ }
+
+ @Test
+ fun testSupportsArtifactWithNonJavaKotlinMethodTraceSessionArtifact() {
+ val heapProfdSessionArtifact = HeapProfdSessionArtifact(myProfilers, Common.Session.getDefaultInstance(),
+ Common.SessionMetaData.getDefaultInstance(),
+ Trace.TraceInfo.getDefaultInstance())
+ assertThat(myJavaKotlinMethodTraceTaskHandler.supportsArtifact(heapProfdSessionArtifact)).isFalse()
+ }
+
+ @Test
+ fun testStartTaskInvokedOnEnterWithAliveSession() {
+ TaskHandlerTestUtils.startSession(myExposureLevel, myProfilers, myTransportService, myTimer)
+ val javaKotlinMethodTraceSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(),
+ 1L,
+ 100L,
+ createDefaultArtInstrumentedTraceConfiguration())
+ val cpuTaskArgs = CpuTaskArgs(javaKotlinMethodTraceSessionArtifact)
+ myJavaKotlinMethodTraceTaskHandler.enter(cpuTaskArgs)
+ // The session is alive, so startTask and thus startCapture should be called.
+ assertThat(myJavaKotlinMethodTraceTaskHandler.stage!!.recordingModel.isRecording)
+ }
+
+ @Test
+ fun testStartTaskWithSetStage() {
+ TaskHandlerTestUtils.startSession(myExposureLevel, myProfilers, myTransportService, myTimer)
+ // To start the task and thus the capture, the stage must be set up before. This will be taken care of via the setupStage() method call,
+ // on enter of the task handler, but this test is testing the explicit invocation of startTask.
+ myJavaKotlinMethodTraceTaskHandler.setupStage()
+ myJavaKotlinMethodTraceTaskHandler.startTask()
+ assertThat(myJavaKotlinMethodTraceTaskHandler.stage!!.recordingModel.isRecording).isTrue()
+ }
+
+ @Test
+ fun testStartTaskWithUnsetStage() {
+ // To start the task and thus the capture, the stage must be set up before. Here we will test the case where startTask is invoked
+ // without the stage being set precondition being met.
+ val exception = assertFailsWith<Throwable> {
+ myJavaKotlinMethodTraceTaskHandler.startTask()
+ }
+ assertThat(myJavaKotlinMethodTraceTaskHandler.stage).isNull()
+ assertThat(exception.message).isEqualTo(
+ "There was an error with the Java/Kotlin Method Trace task. Error message: Cannot start the task as the InterimStage was null.")
+ }
+
+ @Test
+ fun testStopTaskSuccessfullyTerminatesRecording() {
+ TaskHandlerTestUtils.startSession(myExposureLevel, myProfilers, myTransportService, myTimer)
+ // First start the task successfully.
+ (myTransportService.getRegisteredCommand(Commands.Command.CommandType.START_TRACE) as StartTrace)
+ .startStatus = Trace.TraceStartStatus.newBuilder()
+ .setStatus(Trace.TraceStartStatus.Status.SUCCESS)
+ .build()
+ myJavaKotlinMethodTraceTaskHandler.setupStage()
+ myJavaKotlinMethodTraceTaskHandler.startTask()
+ assertThat(myJavaKotlinMethodTraceTaskHandler.stage!!.recordingModel.isRecording).isTrue()
+
+ // Wait for successful start event to be consumed.
+ myTimer.tick(FakeTimer.ONE_SECOND_IN_NS)
+ // Stop the task successfully.
+ (myTransportService.getRegisteredCommand(Commands.Command.CommandType.STOP_TRACE) as StopTrace)
+ .stopStatus = Trace.TraceStopStatus.newBuilder()
+ .setStatus(Trace.TraceStopStatus.Status.SUCCESS)
+ .build()
+ myJavaKotlinMethodTraceTaskHandler.stopTask()
+ // Wait for successful end event to be consumed.
+ myTimer.tick(FakeTimer.ONE_SECOND_IN_NS)
+ assertThat(myJavaKotlinMethodTraceTaskHandler.stage!!.recordingModel.isRecording).isFalse()
+ }
+
+ @Test
+ fun testLoadTaskInvokedOnEnterWithDeadSession() {
+ TaskHandlerTestUtils.startAndStopSession(myExposureLevel, myProfilers, myManager, myTransportService, myTimer)
+
+ // Before enter + loadTask, the stage should not be set yet.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+
+ // Create a fake CpuCaptureSessionArtifact that uses an ART Instrumented (Java/Kotlin Method Sample) configuration.
+ val javaKotlinMethodTraceSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(),
+ 1L,
+ 100L,
+ createDefaultArtInstrumentedTraceConfiguration())
+ val cpuTaskArgs = CpuTaskArgs(javaKotlinMethodTraceSessionArtifact)
+ // The session is not alive (dead) so loadTask and thus loadCapture should be called.
+ val argsSuccessfullyUsed = myJavaKotlinMethodTraceTaskHandler.enter(cpuTaskArgs)
+ assertThat(argsSuccessfullyUsed).isTrue()
+
+ // Verify that the artifact doSelect behavior is called by checking if the stage was set to CpuProfilerStage.
+ assertThat(myProfilers.stage).isInstanceOf(CpuProfilerStage::class.java)
+ }
+
+ @Test
+ fun testLoadTaskWithNonNullTaskArgs() {
+ TaskHandlerTestUtils.startAndStopSession(myExposureLevel, myProfilers, myManager, myTransportService, myTimer)
+
+ // Before enter + loadTask, the stage should not be set yet.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+
+ val javaKotlinMethodTraceSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(),
+ 1L,
+ 100L,
+ createDefaultArtInstrumentedTraceConfiguration())
+ val cpuTaskArgs = CpuTaskArgs(javaKotlinMethodTraceSessionArtifact)
+ val argsSuccessfullyUsed = myJavaKotlinMethodTraceTaskHandler.loadTask(cpuTaskArgs)
+ assertThat(argsSuccessfullyUsed).isTrue()
+
+ // Verify that the artifact doSelect behavior was called by checking if the stage was set to CpuProfilerStage.
+ assertThat(myProfilers.stage).isInstanceOf(CpuProfilerStage::class.java)
+ }
+
+ @Test
+ fun testLoadTaskWithNullTaskArgs() {
+ TaskHandlerTestUtils.startAndStopSession(myExposureLevel, myProfilers, myManager, myTransportService, myTimer)
+
+ // Before enter + loadTask, the stage should not be set yet.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+
+ val exception = assertFailsWith<Throwable> {
+ myJavaKotlinMethodTraceTaskHandler.loadTask(null)
+ }
+
+ assertThat(exception.message).isEqualTo(
+ "There was an error with the Java/Kotlin Method Trace task. Error message: The task arguments (TaskArgs) supplied are not of the " +
+ "expected type (CpuTaskArgs).")
+
+ // Verify that the artifact doSelect behavior was not called by checking if the stage was not set to CpuProfilerStage.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+ }
+
+ @Test
+ fun testCreateArgsSuccessfully() {
+ val selectedSession = Common.Session.newBuilder().setSessionId(1).setEndTimestamp(100).build()
+ val sessionIdToSessionItems = mapOf(
+ 1L to TaskHandlerTestUtils.createSessionItem(myProfilers, selectedSession, 1, listOf(
+ TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers, selectedSession, 1, 100L,
+ 5L, 500L,
+ createDefaultArtInstrumentedTraceConfiguration()))),
+ )
+
+ val cpuTaskArgs = myJavaKotlinMethodTraceTaskHandler.createArgs(sessionIdToSessionItems, selectedSession)
+ assertThat(cpuTaskArgs).isNotNull()
+ assertThat(cpuTaskArgs).isInstanceOf(CpuTaskArgs::class.java)
+ assertThat(cpuTaskArgs!!.getCpuCaptureArtifact()).isNotNull()
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.configuration.hasArtOptions()).isTrue()
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.configuration.artOptions.traceMode).isEqualTo(TraceMode.INSTRUMENTED)
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.fromTimestamp).isEqualTo(5L)
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.toTimestamp).isEqualTo(500L)
+ }
+
+ @Test
+ fun testCreateArgsFails() {
+ // By setting a session id that does not match any of the session items, the task artifact will not be found in the call to createArgs
+ // will fail to be constructed.
+ val selectedSession = Common.Session.newBuilder().setSessionId(0).setEndTimestamp(100).build()
+ val sessionIdToSessionItems = mapOf(
+ 1L to TaskHandlerTestUtils.createSessionItem(myProfilers, selectedSession, 1, listOf(
+ TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers, selectedSession, 1, 100L,
+ 5L, 500L,
+ createDefaultArtInstrumentedTraceConfiguration()))),
+ )
+
+ val cpuTaskArgs = myJavaKotlinMethodTraceTaskHandler.createArgs(sessionIdToSessionItems, selectedSession)
+ // A return value of null indicates the task args were not constructed correctly (the underlying artifact was not found or supported by
+ // the task).
+ assertThat(cpuTaskArgs).isNull()
+ }
+
+ @Test
+ fun testGetTaskName() {
+ assertThat(myJavaKotlinMethodTraceTaskHandler.getTaskName()).isEqualTo("Java/Kotlin Method Trace")
+ }
+
+ private fun createDefaultArtInstrumentedTraceConfiguration() = Trace.TraceConfiguration.newBuilder().setArtOptions(
+ Trace.ArtOptions.newBuilder().setTraceMode(TraceMode.INSTRUMENTED).build()).build()
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data(): Collection<ExposureLevel> {
+ return listOf(ExposureLevel.DEBUGGABLE, ExposureLevel.PROFILEABLE)
+ }
+ }
+}
\ No newline at end of file
diff --git a/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/SystemTraceTaskHandlerTest.kt b/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/SystemTraceTaskHandlerTest.kt
new file mode 100644
index 0000000..be4d6d1
--- /dev/null
+++ b/profilers/testSrc/com/android/tools/profilers/tasks/taskhandlers/singleartifact/cpu/SystemTraceTaskHandlerTest.kt
@@ -0,0 +1,261 @@
+/*
+ * 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.profilers.tasks.taskhandlers.singleartifact.cpu
+
+import com.android.tools.adtui.model.FakeTimer
+import com.android.tools.idea.transport.faketransport.FakeGrpcChannel
+import com.android.tools.idea.transport.faketransport.FakeTransportService
+import com.android.tools.idea.transport.faketransport.commands.StartTrace
+import com.android.tools.idea.transport.faketransport.commands.StopTrace
+import com.android.tools.profiler.proto.Commands
+import com.android.tools.profiler.proto.Common
+import com.android.tools.profiler.proto.Common.Process.ExposureLevel
+import com.android.tools.profiler.proto.Trace
+import com.android.tools.profilers.FakeIdeProfilerServices
+import com.android.tools.profilers.ProfilerClient
+import com.android.tools.profilers.StudioProfilers
+import com.android.tools.profilers.cpu.CpuProfilerStage
+import com.android.tools.profilers.event.FakeEventService
+import com.android.tools.profilers.memory.HeapProfdSessionArtifact
+import com.android.tools.profilers.sessions.SessionsManager
+import com.android.tools.profilers.tasks.args.singleartifact.cpu.CpuTaskArgs
+import com.android.tools.profilers.tasks.taskhandlers.TaskHandlerTestUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import perfetto.protos.PerfettoConfig
+import kotlin.test.assertFailsWith
+
+@RunWith(Parameterized::class)
+class SystemTraceTaskHandlerTest(private val myExposureLevel: ExposureLevel) {
+ private val myTimer = FakeTimer()
+ private val myTransportService = FakeTransportService(myTimer, false)
+
+ @get:Rule
+ var myGrpcChannel = FakeGrpcChannel("SystemTraceTaskHandlerTestChannel", myTransportService, FakeEventService())
+
+ private lateinit var myProfilers: StudioProfilers
+ private lateinit var ideProfilerServices: FakeIdeProfilerServices
+ private lateinit var myManager: SessionsManager
+ private lateinit var mySystemTraceTaskHandler: SystemTraceTaskHandler
+
+ @Before
+ fun setup() {
+ ideProfilerServices = FakeIdeProfilerServices()
+ myProfilers = StudioProfilers(
+ ProfilerClient(myGrpcChannel.channel),
+ ideProfilerServices,
+ myTimer
+ )
+ myManager = myProfilers.sessionsManager
+ mySystemTraceTaskHandler = SystemTraceTaskHandler(myManager)
+ assertThat(myManager.sessionArtifacts).isEmpty()
+ assertThat(myManager.selectedSession).isEqualTo(Common.Session.getDefaultInstance())
+ assertThat(myManager.profilingSession).isEqualTo(Common.Session.getDefaultInstance())
+ ideProfilerServices.enableTaskBasedUx(true)
+ }
+
+ @Test
+ fun testSupportsArtifactWithSystemTraceSessionArtifact() {
+ val systemTraceSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(),
+ 1L, 100L,
+ createDefaultPerfettoTraceConfiguration())
+ assertThat(mySystemTraceTaskHandler.supportsArtifact(systemTraceSessionArtifact)).isTrue()
+ }
+
+ @Test
+ fun testSupportsArtifactWithNonSystemTraceSessionArtifact() {
+ val heapProfdSessionArtifact = HeapProfdSessionArtifact(myProfilers, Common.Session.getDefaultInstance(),
+ Common.SessionMetaData.getDefaultInstance(),
+ Trace.TraceInfo.getDefaultInstance())
+ assertThat(mySystemTraceTaskHandler.supportsArtifact(heapProfdSessionArtifact)).isFalse()
+ }
+
+ @Test
+ fun testStartTaskInvokedOnEnterWithAliveSession() {
+ TaskHandlerTestUtils.startSession(myExposureLevel, myProfilers, myTransportService, myTimer)
+ val systemTraceSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(), 1L,
+ 100L,
+ createDefaultPerfettoTraceConfiguration())
+ val cpuTaskArgs = CpuTaskArgs(systemTraceSessionArtifact)
+ mySystemTraceTaskHandler.enter(cpuTaskArgs)
+ // The session is alive, so startTask and thus startCapture should be called.
+ assertThat(mySystemTraceTaskHandler.stage!!.recordingModel.isRecording)
+ }
+
+ @Test
+ fun testStartTaskWithSetStage() {
+ TaskHandlerTestUtils.startSession(myExposureLevel, myProfilers, myTransportService, myTimer)
+ // To start the task and thus the capture, the stage must be set up before. This will be taken care of via the setupStage() method call,
+ // on enter of the task handler, but this test is testing the explicit invocation of startTask.
+ mySystemTraceTaskHandler.setupStage()
+ mySystemTraceTaskHandler.startTask()
+ assertThat(mySystemTraceTaskHandler.stage!!.recordingModel.isRecording).isTrue()
+ }
+
+ @Test
+ fun testStartTaskWithUnsetStage() {
+ // To start the task and thus the capture, the stage must be set up before. Here we will test the case where startTask is invoked
+ // without the stage being set precondition being met.
+ val exception = assertFailsWith<Throwable> {
+ mySystemTraceTaskHandler.startTask()
+ }
+ assertThat(mySystemTraceTaskHandler.stage).isNull()
+ assertThat(exception.message).isEqualTo("There was an error with the System Trace task. Error message: Cannot start the task as the " +
+ "InterimStage was null.")
+ }
+
+ @Test
+ fun testStopTaskSuccessfullyTerminatesRecording() {
+ TaskHandlerTestUtils.startSession(myExposureLevel, myProfilers, myTransportService, myTimer)
+ // First start the task successfully.
+ (myTransportService.getRegisteredCommand(Commands.Command.CommandType.START_TRACE) as StartTrace)
+ .startStatus = Trace.TraceStartStatus.newBuilder()
+ .setStatus(Trace.TraceStartStatus.Status.SUCCESS)
+ .build()
+ mySystemTraceTaskHandler.setupStage()
+ mySystemTraceTaskHandler.startTask()
+ assertThat(mySystemTraceTaskHandler.stage!!.recordingModel.isRecording).isTrue()
+
+ // Wait for successful start event to be consumed.
+ myTimer.tick(FakeTimer.ONE_SECOND_IN_NS)
+ // Stop the task successfully.
+ (myTransportService.getRegisteredCommand(Commands.Command.CommandType.STOP_TRACE) as StopTrace)
+ .stopStatus = Trace.TraceStopStatus.newBuilder()
+ .setStatus(Trace.TraceStopStatus.Status.SUCCESS)
+ .build()
+ mySystemTraceTaskHandler.stopTask()
+ // Wait for successful end event to be consumed.
+ myTimer.tick(FakeTimer.ONE_SECOND_IN_NS)
+ assertThat(mySystemTraceTaskHandler.stage!!.recordingModel.isRecording).isFalse()
+ }
+
+ @Test
+ fun testLoadTaskInvokedOnEnterWithDeadSession() {
+ TaskHandlerTestUtils.startAndStopSession(myExposureLevel, myProfilers, myManager, myTransportService, myTimer)
+
+ // Before enter + loadTask, the stage should not be set yet.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+
+ // Create a fake CpuCaptureSessionArtifact that uses a Perfetto (System Trace) configuration.
+ val systemTraceSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(), 1L,
+ 100L,
+ createDefaultPerfettoTraceConfiguration())
+ val cpuTaskArgs = CpuTaskArgs(systemTraceSessionArtifact)
+ // The session is not alive (dead) so loadTask and thus loadCapture should be called.
+ val argsSuccessfullyUsed = mySystemTraceTaskHandler.enter(cpuTaskArgs)
+ assertThat(argsSuccessfullyUsed).isTrue()
+
+ // Verify that the artifact doSelect behavior is called by checking if the stage was set to CpuProfilerStage.
+ assertThat(myProfilers.stage).isInstanceOf(CpuProfilerStage::class.java)
+ }
+
+ @Test
+ fun testLoadTaskWithNonNullTaskArgs() {
+ TaskHandlerTestUtils.startAndStopSession(myExposureLevel, myProfilers, myManager, myTransportService, myTimer)
+
+ // Before enter + loadTask, the stage should not be set yet.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+
+ val systemTraceSessionArtifact = TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers,
+ Common.Session.getDefaultInstance(), 1L,
+ 100L,
+ createDefaultPerfettoTraceConfiguration())
+ val cpuTaskArgs = CpuTaskArgs(systemTraceSessionArtifact)
+ val argsSuccessfullyUsed = mySystemTraceTaskHandler.loadTask(cpuTaskArgs)
+ assertThat(argsSuccessfullyUsed).isTrue()
+
+ // Verify that the artifact doSelect behavior was called by checking if the stage was set to CpuProfilerStage.
+ assertThat(myProfilers.stage).isInstanceOf(CpuProfilerStage::class.java)
+ }
+
+ @Test
+ fun testLoadTaskWithNullTaskArgs() {
+ TaskHandlerTestUtils.startAndStopSession(myExposureLevel, myProfilers, myManager, myTransportService, myTimer)
+
+ // Before enter + loadTask, the stage should not be set yet.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+
+ val exception = assertFailsWith<Throwable> {
+ mySystemTraceTaskHandler.loadTask(null)
+ }
+
+ assertThat(exception.message).isEqualTo("There was an error with the System Trace task. Error message: The task arguments (TaskArgs) " +
+ "supplied are not of the expected type (CpuTaskArgs).")
+
+ // Verify that the artifact doSelect behavior was not called by checking if the stage was not set to CpuProfilerStage.
+ assertThat(myProfilers.stage).isNotInstanceOf(CpuProfilerStage::class.java)
+ }
+
+ @Test
+ fun testCreateArgsSuccessfully() {
+ val selectedSession = Common.Session.newBuilder().setSessionId(1).setEndTimestamp(100).build()
+ val sessionIdToSessionItems = mapOf(
+ 1L to TaskHandlerTestUtils.createSessionItem(myProfilers, selectedSession, 1, listOf(
+ TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers, selectedSession, 1, 100L,
+ 5L, 500L,
+ createDefaultPerfettoTraceConfiguration()))),
+ )
+
+ val cpuTaskArgs = mySystemTraceTaskHandler.createArgs(sessionIdToSessionItems, selectedSession)
+ assertThat(cpuTaskArgs).isNotNull()
+ assertThat(cpuTaskArgs).isInstanceOf(CpuTaskArgs::class.java)
+ assertThat(cpuTaskArgs!!.getCpuCaptureArtifact()).isNotNull()
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.configuration.hasPerfettoOptions()).isTrue()
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.fromTimestamp).isEqualTo(5L)
+ assertThat(cpuTaskArgs.getCpuCaptureArtifact().artifactProto.toTimestamp).isEqualTo(500L)
+ }
+
+ @Test
+ fun testCreateArgsFails() {
+ // By setting a session id that does not match any of the session items, the task artifact will not be found in the call to createArgs
+ // will fail to be constructed.
+ val selectedSession = Common.Session.newBuilder().setSessionId(0).setEndTimestamp(100).build()
+ val sessionIdToSessionItems = mapOf(
+ 1L to TaskHandlerTestUtils.createSessionItem(myProfilers, selectedSession, 1, listOf(
+ TaskHandlerTestUtils.createCpuCaptureSessionArtifactWithConfig(myProfilers, selectedSession, 1, 100L,
+ 5L, 500L,
+ createDefaultPerfettoTraceConfiguration()))),
+ )
+
+ val cpuTaskArgs = mySystemTraceTaskHandler.createArgs(sessionIdToSessionItems, selectedSession)
+ // A return value of null indicates the task args were not constructed correctly (the underlying artifact was not found or supported by
+ // the task).
+ assertThat(cpuTaskArgs).isNull()
+ }
+
+ @Test
+ fun testGetTaskName() {
+ assertThat(mySystemTraceTaskHandler.getTaskName()).isEqualTo("System Trace")
+ }
+
+ private fun createDefaultPerfettoTraceConfiguration() = Trace.TraceConfiguration.newBuilder().setPerfettoOptions(
+ PerfettoConfig.TraceConfig.getDefaultInstance()).build()
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data(): Collection<ExposureLevel> {
+ return listOf(ExposureLevel.DEBUGGABLE, ExposureLevel.PROFILEABLE)
+ }
+ }
+}
\ No newline at end of file
diff --git a/project-system-gradle/src/META-INF/project-system-gradle-plugin-androidstudio.xml b/project-system-gradle/src/META-INF/project-system-gradle-plugin-androidstudio.xml
index 185f598..d4d9973 100644
--- a/project-system-gradle/src/META-INF/project-system-gradle-plugin-androidstudio.xml
+++ b/project-system-gradle/src/META-INF/project-system-gradle-plugin-androidstudio.xml
@@ -24,6 +24,12 @@
<projectService serviceInterface="com.intellij.openapi.externalSystem.autoimport.ExternalSystemProjectTracker"
serviceImplementation="com.android.tools.idea.projectsystem.gradle.RefreshOnlyAutoImportProjectTracker"
overrides="true"/>
+ <projectService serviceInterface="org.jetbrains.plugins.gradle.settings.GradleSettings"
+ serviceImplementation="com.android.tools.idea.gradle.project.AndroidStudioGradleSettings"
+ overrides="true"/>
+ <applicationService serviceInterface="org.jetbrains.plugins.gradle.service.GradleInstallationManager"
+ serviceImplementation="com.android.tools.idea.gradle.project.AndroidStudioGradleInstallationManager"
+ overrides="true"/>
<postStartupActivity implementation="com.android.tools.idea.gradle.project.AndroidStudioProjectActivity" />
diff --git a/project-system-gradle/src/META-INF/project-system-gradle-plugin.xml b/project-system-gradle/src/META-INF/project-system-gradle-plugin.xml
index 7a4dc7a..290bd29 100644
--- a/project-system-gradle/src/META-INF/project-system-gradle-plugin.xml
+++ b/project-system-gradle/src/META-INF/project-system-gradle-plugin.xml
@@ -110,6 +110,9 @@
<projectViewNodeDecorator id="android.build.node.decorator" implementation="com.android.tools.idea.gradle.projectView.BuildNodeDecorator"/>
<generatedSourcesFilter implementation="com.android.tools.idea.gradle.roots.AndroidGeneratedSourcesFilter"/>
<editorNotificationProvider implementation="com.android.tools.idea.gradle.notification.GeneratedFileNotificationProvider"/>
+ <editorNotificationProvider implementation="com.android.tools.idea.gradle.service.notification.ProjectJarFileTracker$JarFileNotificationProvider"/>
+ <projectService serviceImplementation="com.android.tools.idea.gradle.service.notification.ProjectJarFileTracker"/>
+
<cachesInvalidator implementation="com.android.tools.idea.gradle.project.sync.idea.data.IdeaSyncCachesInvalidator"/>
<completion.contributor language="any"
implementationClass="com.android.tools.idea.gradle.completions.GradleDependencyCompletionContributor" />
@@ -233,6 +236,9 @@
<newResourceCreationHandler
implementation="com.android.tools.idea.gradle.actions.GradleNewResourceCreationHandler" />
</extensions>
+ <extensions defaultExtensionNs="com.android.tools.idea.model">
+ <mergedManifestInfoToken implementation="com.android.tools.idea.model.MergedManifestInfoGradleToken"/>
+ </extensions>
<actions>
<action id="Android.SyncProject" class="com.android.tools.idea.gradle.actions.SyncProjectAction" icon="StudioIcons.Shell.Toolbar.GRADLE_SYNC">
<add-to-group group-id="Android.MainToolBarActionGroup" anchor="before" relative-to-action="AndroidDeviceManagerPlaceholder"/>
diff --git a/project-system-gradle/src/com/android/tools/idea/gradle/actions/AndroidStudioGradleAction.java b/project-system-gradle/src/com/android/tools/idea/gradle/actions/AndroidStudioGradleAction.java
index 2d1fd8d..d11ac41 100644
--- a/project-system-gradle/src/com/android/tools/idea/gradle/actions/AndroidStudioGradleAction.java
+++ b/project-system-gradle/src/com/android/tools/idea/gradle/actions/AndroidStudioGradleAction.java
@@ -17,6 +17,7 @@
import com.android.tools.idea.gradle.project.GradleProjectInfo;
import com.intellij.ide.impl.TrustedProjects;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
@@ -37,6 +38,12 @@
super(text, description, icon);
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@Override
public final void update(@NotNull AnActionEvent e) {
if (!isGradleProject(e)) {
diff --git a/project-system-gradle/src/com/android/tools/idea/gradle/actions/BuildApkAction.java b/project-system-gradle/src/com/android/tools/idea/gradle/actions/BuildApkAction.java
index 8069b5f..e8afa8e 100644
--- a/project-system-gradle/src/com/android/tools/idea/gradle/actions/BuildApkAction.java
+++ b/project-system-gradle/src/com/android/tools/idea/gradle/actions/BuildApkAction.java
@@ -20,6 +20,7 @@
import com.android.tools.idea.gradle.project.build.invoker.GradleBuildInvoker;
import com.android.tools.idea.gradle.project.build.invoker.TestCompileType;
import com.android.tools.idea.gradle.util.GradleProjectSystemUtil;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.DumbAwareAction;
@@ -35,6 +36,12 @@
super(ACTION_TEXT);
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
Project project = e.getProject();
diff --git a/project-system-gradle/src/com/android/tools/idea/gradle/actions/BuildBundleAction.java b/project-system-gradle/src/com/android/tools/idea/gradle/actions/BuildBundleAction.java
index 4a6064c..008424a 100644
--- a/project-system-gradle/src/com/android/tools/idea/gradle/actions/BuildBundleAction.java
+++ b/project-system-gradle/src/com/android/tools/idea/gradle/actions/BuildBundleAction.java
@@ -22,6 +22,7 @@
import com.android.tools.idea.gradle.project.build.invoker.GradleBuildInvoker;
import com.android.tools.idea.gradle.util.GradleProjectSystemUtil;
import com.android.tools.idea.project.AndroidNotification;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.DumbAwareAction;
@@ -37,6 +38,12 @@
super(ACTION_TEXT);
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
Project project = e.getProject();
diff --git a/project-system-gradle/src/com/android/tools/idea/gradle/actions/LibraryPropertiesAction.java b/project-system-gradle/src/com/android/tools/idea/gradle/actions/LibraryPropertiesAction.java
index 685ec37..da280d1 100644
--- a/project-system-gradle/src/com/android/tools/idea/gradle/actions/LibraryPropertiesAction.java
+++ b/project-system-gradle/src/com/android/tools/idea/gradle/actions/LibraryPropertiesAction.java
@@ -19,6 +19,7 @@
import com.android.tools.idea.gradle.project.library.LibraryPropertiesDialog;
import com.intellij.icons.AllIcons;
import com.intellij.ide.projectView.impl.nodes.NamedLibraryElementNode;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
@@ -38,6 +39,12 @@
super("Library Properties...", null, AllIcons.Actions.Properties);
}
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
e.getPresentation().setVisible(findLibrary(e) != null);
diff --git a/android/src/com/android/tools/idea/gradle/plugin/AndroidPluginInfo.java b/project-system-gradle/src/com/android/tools/idea/gradle/plugin/AndroidPluginInfo.java
similarity index 100%
rename from android/src/com/android/tools/idea/gradle/plugin/AndroidPluginInfo.java
rename to project-system-gradle/src/com/android/tools/idea/gradle/plugin/AndroidPluginInfo.java
diff --git a/android/src/com/android/tools/idea/gradle/project/AndroidStudioGradleInstallationManager.java b/project-system-gradle/src/com/android/tools/idea/gradle/project/AndroidStudioGradleInstallationManager.java
similarity index 100%
rename from android/src/com/android/tools/idea/gradle/project/AndroidStudioGradleInstallationManager.java
rename to project-system-gradle/src/com/android/tools/idea/gradle/project/AndroidStudioGradleInstallationManager.java
diff --git a/android/src/com/android/tools/idea/gradle/project/AndroidStudioGradleSettings.java b/project-system-gradle/src/com/android/tools/idea/gradle/project/AndroidStudioGradleSettings.java
similarity index 95%
rename from android/src/com/android/tools/idea/gradle/project/AndroidStudioGradleSettings.java
rename to project-system-gradle/src/com/android/tools/idea/gradle/project/AndroidStudioGradleSettings.java
index 1fe50c3..993fab4 100644
--- a/android/src/com/android/tools/idea/gradle/project/AndroidStudioGradleSettings.java
+++ b/project-system-gradle/src/com/android/tools/idea/gradle/project/AndroidStudioGradleSettings.java
@@ -8,7 +8,7 @@
import org.jetbrains.plugins.gradle.settings.GradleSettings;
import org.jetbrains.plugins.gradle.settings.TestRunner;
-public class AndroidStudioGradleSettings extends GradleSettings{
+public class AndroidStudioGradleSettings extends GradleSettings {
public AndroidStudioGradleSettings(@NotNull Project project) {
super(project);
}
diff --git a/android/src/com/android/tools/idea/gradle/project/build/invoker/BuildStopper.java b/project-system-gradle/src/com/android/tools/idea/gradle/project/build/invoker/BuildStopper.java
similarity index 100%
rename from android/src/com/android/tools/idea/gradle/project/build/invoker/BuildStopper.java
rename to project-system-gradle/src/com/android/tools/idea/gradle/project/build/invoker/BuildStopper.java
diff --git a/project-system-gradle/src/com/android/tools/idea/gradle/project/sync/idea/AndroidGradleProjectResolver.kt b/project-system-gradle/src/com/android/tools/idea/gradle/project/sync/idea/AndroidGradleProjectResolver.kt
index a00e9bf..4b43e5f 100644
--- a/project-system-gradle/src/com/android/tools/idea/gradle/project/sync/idea/AndroidGradleProjectResolver.kt
+++ b/project-system-gradle/src/com/android/tools/idea/gradle/project/sync/idea/AndroidGradleProjectResolver.kt
@@ -20,7 +20,6 @@
import com.android.ide.gradle.model.artifacts.AdditionalClassifierArtifactsModel
import com.android.repository.Revision
import com.android.tools.analytics.UsageTracker.log
-import com.android.tools.analytics.withProjectId
import com.android.tools.idea.IdeInfo
import com.android.tools.idea.flags.StudioFlags
import com.android.tools.idea.gradle.LibraryFilePaths
@@ -83,8 +82,6 @@
import com.android.utils.appendCapitalized
import com.android.utils.findGradleSettingsFile
import com.google.common.annotations.VisibleForTesting
-import com.google.wireless.android.sdk.stats.AndroidStudioEvent
-import com.google.wireless.android.sdk.stats.AndroidStudioEvent.EventCategory
import com.google.wireless.android.sdk.stats.AndroidStudioEvent.EventKind
import com.google.wireless.android.sdk.stats.AndroidStudioEvent.GradleSyncFailure
import com.google.wireless.android.sdk.stats.AndroidStudioEvent.GradleSyncIssueType
@@ -129,7 +126,6 @@
import org.jetbrains.annotations.SystemIndependent
import org.jetbrains.kotlin.android.configure.patchFromMppModel
import org.jetbrains.kotlin.idea.gradleJava.configuration.KotlinMppGradleProjectResolver
-import org.jetbrains.kotlin.idea.gradleTooling.KotlinGradleModel
import org.jetbrains.kotlin.idea.gradleTooling.KotlinMPPGradleModel
import org.jetbrains.kotlin.idea.gradleTooling.model.kapt.KaptGradleModel
import org.jetbrains.kotlin.idea.gradleTooling.model.kapt.KaptModelBuilderService
diff --git a/project-system-gradle/src/com/android/tools/idea/gradle/service/notification/ProjectJarFileTracker.kt b/project-system-gradle/src/com/android/tools/idea/gradle/service/notification/ProjectJarFileTracker.kt
new file mode 100644
index 0000000..3ddd171
--- /dev/null
+++ b/project-system-gradle/src/com/android/tools/idea/gradle/service/notification/ProjectJarFileTracker.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.gradle.service.notification
+
+import com.android.tools.idea.gradle.project.sync.GradleSyncInvoker
+import com.android.tools.idea.projectsystem.PROJECT_SYSTEM_SYNC_TOPIC
+import com.android.tools.idea.projectsystem.ProjectSystemSyncManager
+import com.google.wireless.android.sdk.stats.GradleSyncStats
+import com.intellij.openapi.fileEditor.FileEditor
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.vfs.VirtualFileManager
+import com.intellij.openapi.vfs.newvfs.BulkFileListener
+import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent
+import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent
+import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent
+import com.intellij.openapi.vfs.newvfs.events.VFileEvent
+import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent
+import com.intellij.ui.EditorNotificationPanel
+import com.intellij.ui.EditorNotificationProvider
+import com.intellij.ui.EditorNotifications
+import org.jetbrains.annotations.VisibleForTesting
+import java.util.function.Function
+
+
+class ProjectJarFileTracker(val project: Project) {
+ val editorNotifications:EditorNotifications = EditorNotifications.getInstance(project)
+ @VisibleForTesting
+ var jarFilesChanged = false
+ private set
+
+ private fun resetFlag(){
+ jarFilesChanged = false
+ }
+
+ init {
+ project.messageBus.connect(project).subscribe<BulkFileListener>(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
+ private val JAR_EXT = ".jar"
+
+ override fun before(events: List<VFileEvent>) {
+ for (event in events) {
+ if (event is VFileMoveEvent) {
+ onChanged(event.file)
+ } else if (event is VFileDeleteEvent) {
+ onChanged(event.file)
+ }
+ }
+ }
+
+ override fun after(events: List<VFileEvent>) {
+ for (event in events) {
+ when (event) {
+ is VFileCreateEvent, is VFileCopyEvent, is VFileMoveEvent -> onChanged(event.file)
+ }
+ }
+ }
+
+ private fun onChanged(file: VirtualFile?) {
+ if (file?.name?.endsWith(JAR_EXT) == true) {
+ jarFilesChanged = true
+ editorNotifications.updateAllNotifications()
+ }
+ }
+ })
+
+ project.messageBus.connect().subscribe(PROJECT_SYSTEM_SYNC_TOPIC,
+ ProjectSystemSyncManager.SyncResultListener { result ->
+ if (result == ProjectSystemSyncManager.SyncResult.SUCCESS) {
+ resetFlag()
+ }
+ })
+ }
+
+
+ class JarFileNotificationProvider: EditorNotificationProvider {
+
+ override fun collectNotificationData(project: Project, file: VirtualFile): Function<FileEditor, EditorNotificationPanel?>? {
+ val tracker = getInstance(project)
+ if(tracker.jarFilesChanged){
+ return Function { fileEditor -> JarFileSyncNotificationPanel(project, fileEditor) }
+ }
+ return null
+ }
+ }
+
+ internal class JarFileSyncNotificationPanel(project: Project, editor: FileEditor) : EditorNotificationPanel(editor) {
+ init {
+ text("Jar files have been added/removed since last project sync. Sync may be necessary for the IDE to work properly.")
+ createActionLabel("Sync Now") {
+ GradleSyncInvoker.getInstance()
+ .requestProjectSync(
+ project,
+ GradleSyncInvoker.Request(GradleSyncStats.Trigger.TRIGGER_USER_STALE_CHANGES),
+ null
+ )
+ }
+ createActionLabel("Ignore these changes") {
+ getInstance(project).resetFlag()
+ this.isVisible = false
+ }
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun getInstance(project: Project): ProjectJarFileTracker {
+ return project.getService(ProjectJarFileTracker::class.java)
+ }
+ }
+}
diff --git a/project-system-gradle/src/com/android/tools/idea/model/MergedManifestInfoGradleToken.java b/project-system-gradle/src/com/android/tools/idea/model/MergedManifestInfoGradleToken.java
new file mode 100644
index 0000000..c874ef9
--- /dev/null
+++ b/project-system-gradle/src/com/android/tools/idea/model/MergedManifestInfoGradleToken.java
@@ -0,0 +1,40 @@
+/*
+ * 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.model;
+
+import com.android.manifmerger.ManifestMerger2;
+import com.android.tools.idea.gradle.plugin.AndroidPluginInfo;
+import com.android.tools.idea.projectsystem.GradleToken;
+import com.android.tools.idea.projectsystem.gradle.GradleProjectSystem;
+import com.intellij.openapi.project.Project;
+
+public class MergedManifestInfoGradleToken implements MergedManifestInfoToken<GradleProjectSystem>, GradleToken {
+
+ @Override
+ public ManifestMerger2.Invoker withProjectSystemFeatures(GradleProjectSystem projectSystem, ManifestMerger2.Invoker invoker) {
+ if (!isVersionAtLeast7_4_0(projectSystem.getProject())) {
+ invoker.withFeatures(ManifestMerger2.Invoker.Feature.DISABLE_STRIP_LIBRARY_TARGET_SDK);
+ }
+ return invoker;
+ }
+
+ private static boolean isVersionAtLeast7_4_0(Project project) {
+ AndroidPluginInfo androidPluginInfo = AndroidPluginInfo.findFromModel(project);
+ return androidPluginInfo != null &&
+ androidPluginInfo.getPluginVersion() != null &&
+ androidPluginInfo.getPluginVersion().isAtLeast(7, 4, 0);
+ }
+}
\ No newline at end of file
diff --git a/project-system-gradle/src/com/android/tools/idea/projectsystem/gradle/GradleModuleSystem.kt b/project-system-gradle/src/com/android/tools/idea/projectsystem/gradle/GradleModuleSystem.kt
index f7d2545..7e0f776 100644
--- a/project-system-gradle/src/com/android/tools/idea/projectsystem/gradle/GradleModuleSystem.kt
+++ b/project-system-gradle/src/com/android/tools/idea/projectsystem/gradle/GradleModuleSystem.kt
@@ -579,7 +579,7 @@
*/
override val useAndroidX: Boolean get() = agpBuildGlobalFlags.useAndroidX
- override val enableVcsInfo: Boolean get() = agpBuildGlobalFlags.enableVcsInfo
+ val enableVcsInfo: Boolean get() = agpBuildGlobalFlags.enableVcsInfo
override val submodules: Collection<Module>
get() = moduleHierarchyProvider.submodules
diff --git a/project-system-gradle/src/com/android/tools/idea/projectsystem/gradle/GradleProjectSystem.kt b/project-system-gradle/src/com/android/tools/idea/projectsystem/gradle/GradleProjectSystem.kt
index 9c325e8..fcd85a4 100644
--- a/project-system-gradle/src/com/android/tools/idea/projectsystem/gradle/GradleProjectSystem.kt
+++ b/project-system-gradle/src/com/android/tools/idea/projectsystem/gradle/GradleProjectSystem.kt
@@ -86,7 +86,7 @@
import java.io.File
import java.nio.file.Path
-class GradleProjectSystem(val project: Project) : AndroidProjectSystem {
+open class GradleProjectSystem(val project: Project) : AndroidProjectSystem {
private val moduleHierarchyProvider: GradleModuleHierarchyProvider = GradleModuleHierarchyProvider(project)
private val mySyncManager: ProjectSystemSyncManager = GradleProjectSystemSyncManager(project)
private val myBuildManager: ProjectSystemBuildManager = GradleProjectSystemBuildManager(project)
@@ -168,9 +168,9 @@
)
}
- override fun validateRunConfiguration(runConfiguration: RunConfiguration): List<ValidationError> {
+ override fun validateRunConfiguration(runConfiguration: RunConfiguration, quickFixCallback: Runnable?): List<ValidationError> {
val context = runConfiguration.getGradleContext() ?: return super.validateRunConfiguration(runConfiguration)
- return GradleApkProvider.doValidate(context.androidFacet, context.isTestConfiguration, context.alwaysDeployApkFromBundle)
+ return GradleApkProvider.doValidate(context.androidFacet, context.isTestConfiguration, context.alwaysDeployApkFromBundle, quickFixCallback)
}
internal fun getBuiltApksForSelectedVariant(
diff --git a/project-system-gradle/src/com/android/tools/idea/run/GradleApkProvider.java b/project-system-gradle/src/com/android/tools/idea/run/GradleApkProvider.java
index 88bd8c9..5e8017e 100644
--- a/project-system-gradle/src/com/android/tools/idea/run/GradleApkProvider.java
+++ b/project-system-gradle/src/com/android/tools/idea/run/GradleApkProvider.java
@@ -611,7 +611,8 @@
@NotNull
public static ImmutableList<ValidationError> doValidate(@NotNull AndroidFacet androidFacet,
boolean isTest,
- boolean alwaysDeployApkFromBundle) {
+ boolean alwaysDeployApkFromBundle,
+ @Nullable Runnable quickFixCallback) {
ImmutableList.Builder<ValidationError> result = ImmutableList.builder();
GradleAndroidModel gradleAndroidModel = GradleAndroidModel.get(androidFacet);
@@ -654,8 +655,10 @@
final String message =
AndroidBundle.message("run.error.apk.not.signed", gradleAndroidModel.getSelectedVariant().getDisplayName());
- ConfigurationQuickFix quickFix = new UnsignedApkQuickFix(module, gradleAndroidModel.getSelectedVariant().getBuildType());
- result.add(ValidationError.fatal(message, quickFix));
+ UnsignedApkQuickFix quickfix = UnsignedApkQuickFix.create(module,
+ gradleAndroidModel.getSelectedVariant().getBuildType(),
+ quickFixCallback);
+ result.add(ValidationError.fatal(message, quickfix));
return result.build();
}
diff --git a/project-system-gradle/src/com/android/tools/idea/run/UnsignedApkQuickFix.kt b/project-system-gradle/src/com/android/tools/idea/run/UnsignedApkQuickFix.kt
index 54dd639..445e447 100644
--- a/project-system-gradle/src/com/android/tools/idea/run/UnsignedApkQuickFix.kt
+++ b/project-system-gradle/src/com/android/tools/idea/run/UnsignedApkQuickFix.kt
@@ -20,12 +20,14 @@
import com.android.tools.idea.gradle.dsl.api.android.SigningConfigModel
import com.android.tools.idea.gradle.dsl.api.ext.ReferenceTo
import com.android.tools.idea.gradle.project.sync.GradleSyncInvoker
+import com.android.tools.idea.gradle.project.sync.GradleSyncListener
import com.google.common.annotations.VisibleForTesting
import com.google.wireless.android.sdk.stats.GradleSyncStats
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.module.Module
import com.intellij.openapi.options.ConfigurationQuickFix
+import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.ui.HyperlinkLabel
@@ -37,41 +39,107 @@
import javax.swing.JComponent
import javax.swing.JPanel
-/**
- * Runnable to fix unsigned APK error.
- */
-class UnsignedApkQuickFix @VisibleForTesting constructor(
- private val module: Module,
- private val selectedBuildTypeName: String,
- private val makeSigningConfigSelector: (GradleBuildModel) -> SigningConfigSelector) : ConfigurationQuickFix {
+/** Runnable to fix unsigned APK error. */
+@Suppress("UnstableApiUsage")
+class UnsignedApkQuickFix
+@VisibleForTesting
+constructor(
+ val module: Module,
+ val selectedBuildTypeName: String,
+ val callback: Runnable?,
+ private val makeSigningConfigSelector: (GradleBuildModel) -> SigningConfigSelector
+) : ConfigurationQuickFix {
/**
- * Instantiates a dialog as the quick fix action for unsigned APK error. When user closed the dialog with the OK button, the selected
- * signing config is picked.
+ * Instantiates a dialog as the quick fix action for unsigned APK error. When user closed the
+ * dialog with the OK button, the selected signing config is picked.
*
* @param module an IDEA module
* @param selectedBuildTypeName name of the currently selected build type, e.g. debug
+ * @param callback a Runnable to execute after Gradle sync finishes
*/
- constructor(module: Module, selectedBuildTypeName: String) : this(
- module, selectedBuildTypeName, { gradleBuildModel -> SigningConfigSelectorDialog(gradleBuildModel.android().signingConfigs()) }
+ constructor(
+ module: Module,
+ selectedBuildTypeName: String,
+ callback: Runnable?
+ ) : this(
+ module,
+ selectedBuildTypeName,
+ callback,
+ { gradleBuildModel -> SigningConfigSelectorDialog(gradleBuildModel.android().signingConfigs()) }
)
override fun applyFix(dataContext: DataContext) {
- val gradleBuildModel = GradleModelProvider.getInstance().getProjectModel(module.project).getModuleBuildModel(module)
+ val gradleBuildModel =
+ GradleModelProvider.getInstance().getProjectModel(module.project).getModuleBuildModel(module)
if (gradleBuildModel != null) {
val signingConfigSelector = makeSigningConfigSelector(gradleBuildModel)
if (signingConfigSelector.showAndGet()) {
- gradleBuildModel.android().buildTypes().find { it.name() == selectedBuildTypeName }?.let { selectedBuildType ->
- selectedBuildType.signingConfig().setValue(ReferenceTo(signingConfigSelector.selectedConfig()))
- // Write signingConfig to Gradle.
- WriteCommandAction.runWriteCommandAction(module.project, "Select Signing Config", null, { gradleBuildModel.applyChanges() })
- // Trigger Gradle sync for the signingConfig to take effect.
- GradleSyncInvoker.getInstance().requestProjectSync(
- module.project, GradleSyncInvoker.Request(GradleSyncStats.Trigger.TRIGGER_QF_SIGNING_CONFIG_SELECTED), null)
- }
+ gradleBuildModel
+ .android()
+ .buildTypes()
+ .find { it.name() == selectedBuildTypeName }
+ ?.let { selectedBuildType ->
+ selectedBuildType
+ .signingConfig()
+ .setValue(ReferenceTo(signingConfigSelector.selectedConfig()))
+ // Write signingConfig to Gradle.
+ WriteCommandAction.runWriteCommandAction(
+ module.project,
+ "Select Signing Config",
+ null,
+ { gradleBuildModel.applyChanges() }
+ )
+ // Trigger Gradle sync for the signingConfig to take effect.
+ GradleSyncInvoker.getInstance()
+ .requestProjectSync(
+ module.project,
+ GradleSyncInvoker.Request(
+ GradleSyncStats.Trigger.TRIGGER_QF_SIGNING_CONFIG_SELECTED
+ ),
+ object : GradleSyncListener {
+ override fun syncSucceeded(project: Project) {
+ callback?.run()
+ }
+
+ override fun syncFailed(project: Project, errorMessage: String) {
+ callback?.run()
+ }
+ }
+ )
+ }
}
+ } else {
+ throw IllegalStateException(
+ "Gradle build model should not be null for module: ${module.name}."
+ )
}
- else {
- throw IllegalStateException("Gradle build model should not be null for module: ${module.name}.")
+ }
+
+ companion object {
+ @VisibleForTesting var unsignedApkQuickFix: UnsignedApkQuickFix? = null
+
+ /**
+ * To avoid repeatedly creating a new QuickFix (and losing the calling SettingsEditor's
+ * callback), only instantiate a new UnsignedApkQuickFix if the cached one doesn't match. Null
+ * callbacks also will not overwrite the existing cache if the module and build type remain the
+ * same.
+ */
+ @JvmStatic
+ fun create(
+ module: Module,
+ buildType: String,
+ quickFixCallback: Runnable?
+ ): UnsignedApkQuickFix? {
+ if (
+ unsignedApkQuickFix == null ||
+ unsignedApkQuickFix?.module != module ||
+ unsignedApkQuickFix?.selectedBuildTypeName != buildType ||
+ (quickFixCallback != null && unsignedApkQuickFix?.callback != quickFixCallback)
+ ) {
+ unsignedApkQuickFix = UnsignedApkQuickFix(module, buildType, quickFixCallback)
+ }
+
+ return unsignedApkQuickFix
}
}
}
@@ -84,25 +152,27 @@
*/
fun showAndGet(): Boolean
- /**
- * @return the selected signing config model
- */
+ /** @return the selected signing config model */
fun selectedConfig(): SigningConfigModel
}
/**
- * Dialog for selecting an existing signing config, useful for quick-fixing unsigned APK Run config error.
+ * Dialog for selecting an existing signing config, useful for quick-fixing unsigned APK Run config
+ * error.
*/
-class SigningConfigSelectorDialog(signingConfigs: Collection<SigningConfigModel>) : DialogWrapper(false), SigningConfigSelector {
+class SigningConfigSelectorDialog(signingConfigs: Collection<SigningConfigModel>) :
+ DialogWrapper(false), SigningConfigSelector {
private val rootPanel = JPanel(BorderLayout())
- @VisibleForTesting
- val signingConfigComboBox = ComboBox<SigningConfigModel>()
+ @VisibleForTesting val signingConfigComboBox = ComboBox<SigningConfigModel>()
init {
title = "Select Signing Config"
- signingConfigs.forEach(Consumer { item: SigningConfigModel -> signingConfigComboBox.addItem(item) })
- signingConfigComboBox.renderer = SimpleListCellRenderer.create("<unnamed>", SigningConfigModel::name)
+ signingConfigs.forEach(
+ Consumer { item: SigningConfigModel -> signingConfigComboBox.addItem(item) }
+ )
+ signingConfigComboBox.renderer =
+ SimpleListCellRenderer.create("<unnamed>", SigningConfigModel::name)
init()
}
@@ -110,12 +180,13 @@
override fun createCenterPanel(): JComponent {
return rootPanel.apply {
- add(JPanel(FlowLayout()).apply {
- add(JBLabel("Debug keys should be strictly used for development purposes only."))
- add(HyperlinkLabel("Learn more").apply {
- setHyperlinkTarget(DOC_URL)
- })
- }, BorderLayout.NORTH)
+ add(
+ JPanel(FlowLayout()).apply {
+ add(JBLabel("Debug keys should be strictly used for development purposes only."))
+ add(HyperlinkLabel("Learn more").apply { setHyperlinkTarget(DOC_URL) })
+ },
+ BorderLayout.NORTH
+ )
add(signingConfigComboBox, BorderLayout.CENTER)
}
}
@@ -123,4 +194,4 @@
companion object {
const val DOC_URL = "https://developer.android.com/studio/publish/app-signing#debug-mode"
}
-}
\ No newline at end of file
+}
diff --git a/project-system-gradle/src/org/jetbrains/android/actions/GenerateSignedApkAction.java b/project-system-gradle/src/org/jetbrains/android/actions/GenerateSignedApkAction.java
index bf9038a..5686a94 100644
--- a/project-system-gradle/src/org/jetbrains/android/actions/GenerateSignedApkAction.java
+++ b/project-system-gradle/src/org/jetbrains/android/actions/GenerateSignedApkAction.java
@@ -21,6 +21,7 @@
import com.android.tools.idea.gradle.project.GradleProjectInfo;
import com.android.tools.idea.project.AndroidProjectInfo;
import com.android.tools.idea.projectsystem.ProjectSystemUtil;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
@@ -34,6 +35,13 @@
import org.jetbrains.annotations.VisibleForTesting;
public class GenerateSignedApkAction extends AnAction {
+
+ @NotNull
+ @Override
+ public ActionUpdateThread getActionUpdateThread() {
+ return ActionUpdateThread.BGT;
+ }
+
public GenerateSignedApkAction() {
super(AndroidBundle.message(StudioFlags.RUNDEBUG_ANDROID_BUILD_BUNDLE_ENABLED.get() ? "android.generate.signed.apk.action.bundle.text" : "android.generate.signed.apk.action.text"));
}
diff --git a/project-system-gradle/src/org/jetbrains/android/exportSignedPackage/ApkStep.java b/project-system-gradle/src/org/jetbrains/android/exportSignedPackage/ApkStep.java
index 1bffbda..7d8a956 100644
--- a/project-system-gradle/src/org/jetbrains/android/exportSignedPackage/ApkStep.java
+++ b/project-system-gradle/src/org/jetbrains/android/exportSignedPackage/ApkStep.java
@@ -109,7 +109,11 @@
@Override
public void _init() {
if (myInited) return;
- final AndroidFacet facet = myWizard.getFacet();
+ _init(myWizard.getFacet());
+ }
+
+ @VisibleForTesting
+ void _init(AndroidFacet facet) {
Module module = facet.getModule();
PropertiesComponent properties = PropertiesComponent.getInstance(module.getProject());
@@ -210,7 +214,7 @@
@Override
public void _commit(boolean finishChosen) throws CommitStepException {
- final String apkPath = myApkPathField.getText().trim();
+ final String apkPath = myApkPathField.getText().stripLeading();
if (apkPath.isEmpty()) {
throw new CommitStepException(AndroidBundle.message("android.extract.package.specify.apk.path.error"));
}
diff --git a/project-system-gradle/src/org/jetbrains/android/exportSignedPackage/GradleSignStep.java b/project-system-gradle/src/org/jetbrains/android/exportSignedPackage/GradleSignStep.java
index 43ffd66..72c07bd 100644
--- a/project-system-gradle/src/org/jetbrains/android/exportSignedPackage/GradleSignStep.java
+++ b/project-system-gradle/src/org/jetbrains/android/exportSignedPackage/GradleSignStep.java
@@ -46,7 +46,8 @@
public class GradleSignStep extends ExportSignedPackageWizardStep {
@NonNls private static final String PROPERTY_APK_PATH = "ExportApk.ApkPath";
@NonNls private static final String PROPERTY_BUNDLE_PATH = "ExportBundle.BundlePath";
- @NonNls private static final String PROPERTY_BUILD_VARIANTS = "ExportApk.BuildVariants";
+ @VisibleForTesting
+ @NonNls static final String PROPERTY_BUILD_VARIANTS = "ExportApk.BuildVariants";
private JPanel myContentPanel;
private TextFieldWithBrowseButton myApkPathField;
@@ -69,7 +70,12 @@
@Override
public void _init() {
- myAndroidModel = GradleAndroidModel.get(myWizard.getFacet());
+ _init(GradleAndroidModel.get(myWizard.getFacet()));
+ }
+
+ @VisibleForTesting
+ void _init(GradleAndroidModel androidModel) {
+ myAndroidModel = androidModel;
PropertiesComponent properties = PropertiesComponent.getInstance(myWizard.getProject());
@@ -111,7 +117,7 @@
throw new CommitStepException(AndroidBundle.message("android.apk.sign.gradle.no.model"));
}
- final String apkFolder = myApkPathField.getText().trim();
+ final String apkFolder = myApkPathField.getText().stripLeading();
if (apkFolder.isEmpty()) {
throw new CommitStepException(AndroidBundle.message("android.apk.sign.gradle.missing.destination", myWizard.getTargetType()));
}
diff --git a/android/gradle/testSrc/com/android/tools/idea/gradle/project/build/invoker/BuildStopperTest.java b/project-system-gradle/testSrc/com/android/tools/idea/gradle/project/build/invoker/BuildStopperTest.java
similarity index 100%
rename from android/gradle/testSrc/com/android/tools/idea/gradle/project/build/invoker/BuildStopperTest.java
rename to project-system-gradle/testSrc/com/android/tools/idea/gradle/project/build/invoker/BuildStopperTest.java
diff --git a/project-system-gradle/testSrc/com/android/tools/idea/gradle/service/notification/ProjectJarFileTrackerTest.java b/project-system-gradle/testSrc/com/android/tools/idea/gradle/service/notification/ProjectJarFileTrackerTest.java
new file mode 100644
index 0000000..edb1f1a
--- /dev/null
+++ b/project-system-gradle/testSrc/com/android/tools/idea/gradle/service/notification/ProjectJarFileTrackerTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.gradle.service.notification;
+
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.tools.idea.gradle.service.notification.ProjectJarFileTracker;
+import com.android.tools.idea.gradle.service.notification.ProjectJarFileTracker.JarFileNotificationProvider;
+import com.android.tools.idea.testing.IdeComponents;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.testFramework.LightPlatformTestCase;
+import com.intellij.testFramework.PlatformTestUtil;
+import com.intellij.ui.EditorNotificationPanel;
+import com.intellij.ui.EditorNotifications;
+import java.io.IOException;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class ProjectJarFileTrackerTest extends LightPlatformTestCase {
+ @Mock private FileEditor myFileEditor;
+ @Mock private EditorNotifications myEditorNotifications;
+ private JarFileNotificationProvider myNotificationProvider;
+
+ private ProjectJarFileTracker myProjectJarFileTracker;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ initMocks(this);
+
+ new IdeComponents(getProject(), getTestRootDisposable()).replaceProjectService(EditorNotifications.class, myEditorNotifications);
+
+ myProjectJarFileTracker = ProjectJarFileTracker.getInstance(getProject());
+ myNotificationProvider = new JarFileNotificationProvider();
+ }
+
+ @Test
+ public void testAddFile() throws IOException {
+ VirtualFile projectDir = PlatformTestUtil.getOrCreateProjectBaseDir(getProject());
+ VirtualFile jarFile = WriteAction.computeAndWait(() -> projectDir.createChildData(projectDir, "test.jar"));
+ assertTrue(myProjectJarFileTracker.getJarFilesChanged());
+ EditorNotificationPanel panel = myNotificationProvider.collectNotificationData(getProject(), jarFile).apply(myFileEditor);
+ assertEquals(panel.getText(), "Jar files have been added/removed since last project sync. Sync may be necessary for the IDE to work properly.");
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/project-system-integration-tests/testSrc/com/android/tools/idea/projectsystem/gradle/GradleTokensTest.kt b/project-system-integration-tests/testSrc/com/android/tools/idea/projectsystem/gradle/GradleTokensTest.kt
index ce11acc..e530f6d 100644
--- a/project-system-integration-tests/testSrc/com/android/tools/idea/projectsystem/gradle/GradleTokensTest.kt
+++ b/project-system-integration-tests/testSrc/com/android/tools/idea/projectsystem/gradle/GradleTokensTest.kt
@@ -37,7 +37,7 @@
val matches = system.installation.ideaLog.waitForMatchingLine(".*VerifyGradleTokens - ([0-9]+)/([0-9]+) problems? found.*", 900, TimeUnit.SECONDS)
assertThat(matches.groupCount()).isEqualTo(2)
assertThat(matches.group(1)).isEqualTo("0")
- assertThat(matches.group(2)).isEqualTo("3")
+ assertThat(matches.group(2)).isEqualTo("5")
}
}
}
\ No newline at end of file
diff --git a/project-system/src/META-INF/project-system-plugin.xml b/project-system/src/META-INF/project-system-plugin.xml
index 4c8347c..d807885 100644
--- a/project-system/src/META-INF/project-system-plugin.xml
+++ b/project-system/src/META-INF/project-system-plugin.xml
@@ -20,6 +20,9 @@
area="IDEA_PROJECT"/>
<extensionPoint qualifiedName="com.android.androidStartupActivity"
interface="com.android.tools.idea.AndroidStartupActivity"/>
+ <extensionPoint qualifiedName="com.android.tools.idea.model.mergedManifestInfoToken"
+ interface="com.android.tools.idea.model.MergedManifestInfoToken"
+ area="IDEA_PROJECT"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<projectService serviceImplementation="com.android.tools.idea.projectsystem.ProjectSystemService"/>
diff --git a/project-system/src/com/android/tools/idea/model/MergedManifestInfoToken.java b/project-system/src/com/android/tools/idea/model/MergedManifestInfoToken.java
new file mode 100644
index 0000000..64203cc
--- /dev/null
+++ b/project-system/src/com/android/tools/idea/model/MergedManifestInfoToken.java
@@ -0,0 +1,39 @@
+/*
+ * 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.model;
+
+import com.android.manifmerger.ManifestMerger2;
+import com.android.tools.idea.projectsystem.AndroidProjectSystem;
+import com.android.tools.idea.projectsystem.Token;
+import com.intellij.openapi.extensions.ExtensionPointName;
+
+public interface MergedManifestInfoToken<P extends AndroidProjectSystem> extends Token {
+ ExtensionPointName<MergedManifestInfoToken<AndroidProjectSystem>>
+ EP_NAME = new ExtensionPointName<>("com.android.tools.idea.model.mergedManifestInfoToken");
+
+ /**
+ * Allows the {@param projectSystem} to affect the manifest merger invoker builder {@param invoker} according
+ * to build-system-specific project aspects.
+ * <p>
+ * Note that this method cannot remove features from the invoker builder; if any features need to be conditionally
+ * included in any build system, those features must be handled within these methods for all build systems.
+ *
+ * @param projectSystem
+ * @param invoker
+ * @return the modified invoker
+ */
+ ManifestMerger2.Invoker withProjectSystemFeatures(P projectSystem, ManifestMerger2.Invoker invoker);
+}
diff --git a/project-system/src/com/android/tools/idea/projectsystem/AndroidModuleSystem.kt b/project-system/src/com/android/tools/idea/projectsystem/AndroidModuleSystem.kt
index 742d260..146f055 100644
--- a/project-system/src/com/android/tools/idea/projectsystem/AndroidModuleSystem.kt
+++ b/project-system/src/com/android/tools/idea/projectsystem/AndroidModuleSystem.kt
@@ -366,8 +366,6 @@
/** Whether AndroidX libraries should be used instead of legacy support libraries. */
val useAndroidX: Boolean get() = false // TODO(270044829): fix tests to make this true by default
- val enableVcsInfo: Boolean get() = false
-
/** Whether [desugarLibraryConfigFiles] can be determined for this AGP version */
val desugarLibraryConfigFilesKnown: Boolean get() = false
diff --git a/project-system/src/com/android/tools/idea/projectsystem/AndroidProjectSystem.kt b/project-system/src/com/android/tools/idea/projectsystem/AndroidProjectSystem.kt
index 7593315..14d4ed4 100644
--- a/project-system/src/com/android/tools/idea/projectsystem/AndroidProjectSystem.kt
+++ b/project-system/src/com/android/tools/idea/projectsystem/AndroidProjectSystem.kt
@@ -93,7 +93,11 @@
fun getApkProvider(runConfiguration: RunConfiguration): ApkProvider? = null
fun validateRunConfiguration(runConfiguration: RunConfiguration): List<ValidationError> {
- return listOf(ValidationError.fatal("Run configuration ${runConfiguration.name} is not supported in this project"));
+ return validateRunConfiguration(runConfiguration, null)
+ }
+
+ fun validateRunConfiguration(runConfiguration: RunConfiguration, quickFixCallback: Runnable?): List<ValidationError> {
+ return listOf(ValidationError.fatal("Run configuration ${runConfiguration.name} is not supported in this project"))
}
/**
diff --git a/streaming/screen-sharing-agent/app/src/main/cpp/accessors/display_manager.cc b/streaming/screen-sharing-agent/app/src/main/cpp/accessors/display_manager.cc
index e3c7a3a..03935f8 100644
--- a/streaming/screen-sharing-agent/app/src/main/cpp/accessors/display_manager.cc
+++ b/streaming/screen-sharing-agent/app/src/main/cpp/accessors/display_manager.cc
@@ -127,10 +127,13 @@
}
void DisplayManager::UnregisterDisplayListener(Jni jni, DisplayManager::DisplayListener* listener) {
- InitializeStatics(jni);
- if (display_listener_dispatcher_ == nullptr) {
- return;
+ {
+ scoped_lock lock(static_initialization_mutex);
+ if (display_listener_dispatcher_ == nullptr) {
+ return;
+ }
}
+
for (;;) {
auto old_listeners = display_listeners_.load();
auto new_listeners = new vector<DisplayListener*>(*old_listeners);
@@ -138,10 +141,10 @@
if (pos != new_listeners->end()) {
new_listeners->erase(pos);
}
- if (new_listeners->empty()) {
- display_listener_dispatcher_->Stop();
- }
if (display_listeners_.compare_exchange_strong(old_listeners, new_listeners)) {
+ if (new_listeners->empty()) {
+ display_listener_dispatcher_->Stop();
+ }
delete old_listeners;
break;
}
@@ -149,6 +152,25 @@
}
}
+void DisplayManager::UnregisterAllDisplayListeners(Jni jni) {
+ {
+ scoped_lock lock(static_initialization_mutex);
+ if (display_listener_dispatcher_ == nullptr) {
+ return;
+ }
+ }
+
+ auto empty_listeners = new vector<DisplayListener*>();
+ for (;;) {
+ auto old_listeners = display_listeners_.load();
+ if (display_listeners_.compare_exchange_strong(old_listeners, empty_listeners)) {
+ display_listener_dispatcher_->Stop();
+ delete old_listeners;
+ break;
+ }
+ }
+}
+
void DisplayManager::OnDisplayAdded(Jni jni, int32_t display_id) {
InitializeStatics(jni);
Log::D("DisplayManager::OnDisplayAdded %d", display_id);
diff --git a/streaming/screen-sharing-agent/app/src/main/cpp/accessors/display_manager.h b/streaming/screen-sharing-agent/app/src/main/cpp/accessors/display_manager.h
index 44d9580..360f314 100644
--- a/streaming/screen-sharing-agent/app/src/main/cpp/accessors/display_manager.h
+++ b/streaming/screen-sharing-agent/app/src/main/cpp/accessors/display_manager.h
@@ -43,6 +43,7 @@
static std::vector<int32_t> GetDisplayIds(Jni jni);
static void RegisterDisplayListener(Jni jni, DisplayListener* listener);
static void UnregisterDisplayListener(Jni jni, DisplayListener* listener);
+ static void UnregisterAllDisplayListeners(Jni jni);
static void OnDisplayAdded(Jni jni, int32_t display_id);
static void OnDisplayChanged(Jni jni, int32_t display_id);
diff --git a/streaming/screen-sharing-agent/app/src/main/cpp/agent.cc b/streaming/screen-sharing-agent/app/src/main/cpp/agent.cc
index 5c176d5..85ace6e 100644
--- a/streaming/screen-sharing-agent/app/src/main/cpp/agent.cc
+++ b/streaming/screen-sharing-agent/app/src/main/cpp/agent.cc
@@ -231,13 +231,13 @@
void Agent::Shutdown() {
if (!shutting_down_.exchange(true)) {
- if (controller_ != nullptr) {
- controller_->Stop();
- }
- close(control_socket_fd_);
for (auto& it : display_streamers_) {
it.second.Stop();
}
+ DisplayManager::UnregisterAllDisplayListeners(Jvm::GetJni());
+ if (controller_ != nullptr) {
+ controller_->Stop();
+ }
close(video_socket_fd_);
RestoreEnvironment();
}
diff --git a/streaming/screen-sharing-agent/app/src/main/cpp/controller.cc b/streaming/screen-sharing-agent/app/src/main/cpp/controller.cc
index 55c9db0..34f1a76 100644
--- a/streaming/screen-sharing-agent/app/src/main/cpp/controller.cc
+++ b/streaming/screen-sharing-agent/app/src/main/cpp/controller.cc
@@ -116,6 +116,8 @@
Controller::~Controller() {
Stop();
+ input_stream_.Close();
+ output_stream_.Close();
delete pointer_helper_;
delete key_character_map_;
}
@@ -124,8 +126,7 @@
if (device_supports_multiple_states_) {
DeviceStateManager::RemoveDeviceStateListener(&device_state_listener_);
}
- input_stream_.Close();
- output_stream_.Close();
+ stopped = true;
}
void Controller::Initialize() {
@@ -179,15 +180,17 @@
try {
for (;;) {
- if (max_synced_clipboard_length_ != 0) {
- SendClipboardChangedNotification();
- }
+ if (!stopped) {
+ if (max_synced_clipboard_length_ != 0) {
+ SendClipboardChangedNotification();
+ }
- if (device_supports_multiple_states_) {
- SendDeviceStateNotification();
- }
+ if (device_supports_multiple_states_) {
+ SendDeviceStateNotification();
+ }
- SendPendingDisplayEvents();
+ SendPendingDisplayEvents();
+ }
SetReceiveTimeoutMillis(SOCKET_RECEIVE_TIMEOUT_MILLIS, socket_fd_); // Set a receive timeout to avoid blocking for a long time.
int32_t message_type;
@@ -198,7 +201,9 @@
}
SetReceiveTimeoutMillis(0, socket_fd_); // Remove receive timeout for reading the rest of the message.
unique_ptr<ControlMessage> message = ControlMessage::Deserialize(message_type, input_stream_);
- ProcessMessage(*message);
+ if (!stopped) {
+ ProcessMessage(*message);
+ }
}
} catch (EndOfFile& e) {
Log::D("Controller::Run: End of command stream");
diff --git a/streaming/screen-sharing-agent/app/src/main/cpp/controller.h b/streaming/screen-sharing-agent/app/src/main/cpp/controller.h
index e5e2dfa..335172f 100644
--- a/streaming/screen-sharing-agent/app/src/main/cpp/controller.h
+++ b/streaming/screen-sharing-agent/app/src/main/cpp/controller.h
@@ -111,6 +111,7 @@
int socket_fd_; // Owned.
Base128InputStream input_stream_;
Base128OutputStream output_stream_;
+ volatile bool stopped = false;
PointerHelper* pointer_helper_; // Owned.
JObjectArray pointer_properties_; // MotionEvent.PointerProperties[]
JObjectArray pointer_coordinates_; // MotionEvent.PointerCoords[]
diff --git a/streaming/screen-sharing-agent/app/src/main/cpp/log.cc b/streaming/screen-sharing-agent/app/src/main/cpp/log.cc
index 5957d1c..0992afc 100644
--- a/streaming/screen-sharing-agent/app/src/main/cpp/log.cc
+++ b/streaming/screen-sharing-agent/app/src/main/cpp/log.cc
@@ -26,7 +26,19 @@
using namespace std;
-static constexpr char TAG[] = "studio.screen.sharing";
+namespace {
+
+constexpr char TAG[] = "studio.screen.sharing";
+
+// Calls the System.exit method.
+[[noreturn]] void Exit(int exitCode) {
+ Jni jni = Jvm::GetJni();
+ JClass system = jni.GetClass("java/lang/System");
+ jmethodID exit_method = system.GetStaticMethod(jni, "exit", "(I)V");
+ system.CallStaticVoidMethod(exit_method, exitCode);
+}
+
+}
Log::Level Log::level_ = Log::Level::INFO;
@@ -81,8 +93,8 @@
va_start(args, message);
vfprintf(stderr, message, args);
va_end(args);
- Agent::RestoreEnvironment();
- exit(EXIT_FAILURE);
+ Agent::Shutdown();
+ Exit(EXIT_FAILURE);
}
void Log::Fatal(ExitCode exit_code, const char* message, ...) {
@@ -93,8 +105,8 @@
va_start(args, message);
vfprintf(stderr, message, args);
va_end(args);
- Agent::RestoreEnvironment();
- exit(exit_code);
+ Agent::Shutdown();
+ Exit(exit_code);
}
} // namespace screensharing
diff --git a/streaming/screen-sharing-agent/app/src/main/cpp/main.cc b/streaming/screen-sharing-agent/app/src/main/cpp/main.cc
index c2dbe71..e746c11 100644
--- a/streaming/screen-sharing-agent/app/src/main/cpp/main.cc
+++ b/streaming/screen-sharing-agent/app/src/main/cpp/main.cc
@@ -37,6 +37,4 @@
Agent::Run(args);
Log::I("Screen sharing agent stopped");
- // Exit explicitly to bypass the final JVM cleanup that for some unclear reason sometimes crashes with SIGSEGV.
- exit(EXIT_SUCCESS);
}
diff --git a/streaming/screen-sharing-agent/app/src/main/cpp/session_environment.cc b/streaming/screen-sharing-agent/app/src/main/cpp/session_environment.cc
index 1112950..239a166 100644
--- a/streaming/screen-sharing-agent/app/src/main/cpp/session_environment.cc
+++ b/streaming/screen-sharing-agent/app/src/main/cpp/session_environment.cc
@@ -32,17 +32,6 @@
constexpr int BATTERY_PLUGGED_USB = 2;
constexpr int BATTERY_PLUGGED_WIRELESS = 4;
-// Names an location of the screen sharing agent's files.
-#define SCREEN_SHARING_AGENT_JAR_NAME "screen-sharing-agent.jar"
-#define SCREEN_SHARING_AGENT_SO_NAME "libscreen-sharing-agent.so"
-#define DEVICE_PATH_BASE "/data/local/tmp/.studio"
-
-// Removes files of the screen sharing agent from the persistent storage.
-void RemoveAgentFiles() {
- remove(DEVICE_PATH_BASE "/" SCREEN_SHARING_AGENT_JAR_NAME);
- remove(DEVICE_PATH_BASE "/" SCREEN_SHARING_AGENT_SO_NAME);
-}
-
} // namespace
SessionEnvironment::SessionEnvironment(bool turn_off_display)
@@ -64,8 +53,6 @@
Log::W("Unable to get display token to turn off display");
}
}
-
- RemoveAgentFiles();
}
SessionEnvironment::~SessionEnvironment() {
diff --git a/streaming/src/com/android/tools/idea/streaming/core/RunningDevicePanel.kt b/streaming/src/com/android/tools/idea/streaming/core/RunningDevicePanel.kt
index 5d599ad..47cfbdd 100644
--- a/streaming/src/com/android/tools/idea/streaming/core/RunningDevicePanel.kt
+++ b/streaming/src/com/android/tools/idea/streaming/core/RunningDevicePanel.kt
@@ -21,10 +21,12 @@
import com.android.tools.adtui.util.ActionToolbarUtil
import com.android.tools.idea.streaming.SERIAL_NUMBER_KEY
import com.intellij.ide.ui.customization.CustomActionsSchema
+import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionToolbar
import com.intellij.openapi.actionSystem.DataProvider
import com.intellij.openapi.actionSystem.DefaultActionGroup
+import com.intellij.openapi.util.Disposer
import com.intellij.ui.EditorNotificationPanel
import com.intellij.ui.IdeBorderFactory
import com.intellij.ui.JBColor
@@ -44,7 +46,7 @@
val id: DeviceId,
mainToolbarId: String,
secondaryToolbarId: String,
-) : BorderLayoutPanel(), DataProvider {
+) : BorderLayoutPanel(), DataProvider, Disposable {
/** Plain text name of the device. */
internal abstract val title: String
@@ -118,6 +120,10 @@
}
}
+ override fun dispose() {
+ destroyContent()
+ }
+
@Suppress("SameParameterValue")
private fun createToolbar(toolbarId: String, horizontal: Boolean): ActionToolbar {
val actions = listOf(CustomActionsSchema.getInstance().getCorrectedAction(toolbarId)!!)
diff --git a/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowFactory.kt b/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowFactory.kt
index 248032f..0504ecc 100644
--- a/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowFactory.kt
+++ b/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowFactory.kt
@@ -40,11 +40,8 @@
override fun init(toolWindow: ToolWindow) {
if (StudioFlags.DEVICE_MIRRORING_TAB_DND.get()) {
toolWindow.component.putClientProperty(ToolWindowContentUi.ALLOW_DND_FOR_TABS, true)
- StreamingToolWindowManager(toolWindow)
}
- else {
- StreamingToolWindowManagerNoDnd(toolWindow)
- }
+ StreamingToolWindowManager(toolWindow)
}
private class MoveToWindowAction(private val toolWindow: ToolWindow) : ToolWindowWindowAction() {
diff --git a/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowManager.kt b/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowManager.kt
index d062326..95870e7 100644
--- a/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowManager.kt
+++ b/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowManager.kt
@@ -116,7 +116,9 @@
import java.awt.Component
import java.awt.EventQueue
import java.awt.event.KeyEvent
+import java.text.Collator
import java.time.Duration
+import java.util.Arrays
private const val DEVICE_FRAME_VISIBLE_PROPERTY = "com.android.tools.idea.streaming.emulator.frame.visible"
private const val DEVICE_FRAME_VISIBLE_DEFAULT = true
@@ -128,6 +130,10 @@
private val ATTENTION_REQUEST_EXPIRATION = Duration.ofSeconds(30)
+private val COLLATOR = Collator.getInstance()
+
+private val TAB_COMPARATOR = compareBy<Content, Any?>(COLLATOR) { it.tabName ?: "" }.thenBy { ID_KEY.get(it) }
+
/**
* Manages contents of the Running Devices tool window. Listens to device connections and
* disconnections and maintains [RunningDevicePanel]s, one per running AVD or a mirrored physical
@@ -390,7 +396,7 @@
}
for (deviceClient in deviceClients.values) {
if (findContentBySerialNumberOfPhysicalDevice(deviceClient.deviceSerialNumber) == null) {
- addPanel(DeviceToolWindowPanel(project, deviceClient))
+ addPanel(DeviceToolWindowPanel(toolWindow.disposable, project, deviceClient))
}
}
@@ -419,7 +425,9 @@
// Restore content of visible panels.
for (content in contentManager.selectedContents) {
val panel = content.component as? RunningDevicePanel ?: continue
- panel.createContent(deviceFrameVisible, savedUiState[panel.id])
+ if (!panel.hasContent) {
+ panel.createContent(deviceFrameVisible, savedUiState[panel.id])
+ }
}
}
@@ -460,7 +468,7 @@
private fun addEmulatorPanel(emulator: EmulatorController) {
emulator.addConnectionStateListener(connectionStateListener)
- addPanel(EmulatorToolWindowPanel(project, emulator))
+ addPanel(EmulatorToolWindowPanel(toolWindow.disposable, project, emulator))
}
private fun addPanel(panel: RunningDevicePanel) {
@@ -480,13 +488,29 @@
panel.zoomToolbarVisible = zoomToolbarIsVisible
- if (findContentByDeviceId(panel.id) != null) {
- thisLogger().error("An attempt to add a duplicate panel ${TraceUtils.getSimpleId(content)} ${content.displayName}\n" +
- TraceUtils.getCurrentStack() +
- "Panel creation history:\n${FlightRecorder.getAndClear().joinToString("\n")}")
+ if (StudioFlags.DEVICE_MIRRORING_TAB_DND.get()) {
+ if (findContentByDeviceId(panel.id) != null) {
+ thisLogger().error("An attempt to add a duplicate panel ${TraceUtils.getSimpleId(content)} ${content.displayName}\n" +
+ TraceUtils.getCurrentStack() +
+ "Panel creation history:\n${FlightRecorder.getAndClear().joinToString("\n")}")
+ return
+ }
+
+ // Add panel to the end.
+ contentManager.addContent(content)
+ }
+ else {
+ val index = Arrays.binarySearch(contentManager.contents, content, TAB_COMPARATOR).inv()
+ if (index < 0) {
+ thisLogger().error("An attempt to add a duplicate panel ${TraceUtils.getSimpleId(panel)} ${panel.title}\n" +
+ FlightRecorder.getAndClear())
+ return
+ }
+
+ // Insert panel in alphabetical order of the title.
+ contentManager.addContent(content, index)
}
- contentManager.addContent(content)
FlightRecorder.log { "${TraceUtils.getSimpleId(this)}: added panel ${TraceUtils.getSimpleId(content)} ${content.displayName}\n" +
TraceUtils.getCurrentStack() }
@@ -737,7 +761,7 @@
if (contentShown) {
updateMirroringHandlesFlow()
deviceClient.establishAgentConnectionWithoutVideoStreamAsync() // Start the agent and connect to it proactively.
- val panel = DeviceToolWindowPanel(project, deviceClient)
+ val panel = DeviceToolWindowPanel(toolWindow.disposable, project, deviceClient)
addPanel(panel)
if (activationLevel >= ActivationLevel.SELECT_TAB) {
selectContent(panel, requestFocus = activationLevel >= ActivationLevel.ACTIVATE_TAB)
diff --git a/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowManagerNoDnd.kt b/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowManagerNoDnd.kt
deleted file mode 100644
index 3c47651..0000000
--- a/streaming/src/com/android/tools/idea/streaming/core/StreamingToolWindowManagerNoDnd.kt
+++ /dev/null
@@ -1,1153 +0,0 @@
-/*
- * Copyright (C) 2019 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.streaming.core
-
-import com.android.adblib.serialNumber
-import com.android.annotations.concurrency.AnyThread
-import com.android.annotations.concurrency.UiThread
-import com.android.sdklib.SdkVersionInfo
-import com.android.sdklib.deviceprovisioner.DeviceHandle
-import com.android.sdklib.deviceprovisioner.DeviceProvisioner
-import com.android.sdklib.deviceprovisioner.DeviceState
-import com.android.sdklib.deviceprovisioner.DeviceType
-import com.android.sdklib.deviceprovisioner.ReservationState
-import com.android.sdklib.deviceprovisioner.mapStateNotNull
-import com.android.sdklib.internal.avd.AvdInfo
-import com.android.tools.idea.adb.wireless.PairDevicesUsingWiFiAction
-import com.android.tools.idea.avdmanager.AvdLaunchListener
-import com.android.tools.idea.avdmanager.AvdLaunchListener.RequestType
-import com.android.tools.idea.avdmanager.AvdManagerConnection
-import com.android.tools.idea.concurrency.AndroidCoroutineScope
-import com.android.tools.idea.concurrency.addCallback
-import com.android.tools.idea.deviceprovisioner.DeviceProvisionerService
-import com.android.tools.idea.flags.StudioFlags
-import com.android.tools.idea.run.DeviceHeadsUpListener
-import com.android.tools.idea.streaming.DeviceMirroringSettings
-import com.android.tools.idea.streaming.EmulatorSettings
-import com.android.tools.idea.streaming.MirroringHandle
-import com.android.tools.idea.streaming.MirroringManager
-import com.android.tools.idea.streaming.MirroringState
-import com.android.tools.idea.streaming.RUNNING_DEVICES_TOOL_WINDOW_ID
-import com.android.tools.idea.streaming.core.RunningDevicePanel.UiState
-import com.android.tools.idea.streaming.device.DeviceClient
-import com.android.tools.idea.streaming.device.DeviceConfiguration
-import com.android.tools.idea.streaming.device.DeviceToolWindowPanel
-import com.android.tools.idea.streaming.device.composeDeviceName
-import com.android.tools.idea.streaming.device.dialogs.MirroringConfirmationDialog
-import com.android.tools.idea.streaming.emulator.EmulatorController
-import com.android.tools.idea.streaming.emulator.EmulatorController.ConnectionState
-import com.android.tools.idea.streaming.emulator.EmulatorController.ConnectionStateListener
-import com.android.tools.idea.streaming.emulator.EmulatorId
-import com.android.tools.idea.streaming.emulator.EmulatorToolWindowPanel
-import com.android.tools.idea.streaming.emulator.RunningEmulatorCatalog
-import com.android.utils.FlightRecorder
-import com.android.utils.TraceUtils
-import com.google.common.cache.CacheBuilder
-import com.intellij.collaboration.async.disposingScope
-import com.intellij.execution.configurations.GeneralCommandLine
-import com.intellij.execution.runners.ExecutionUtil
-import com.intellij.icons.AllIcons
-import com.intellij.ide.actions.ToggleToolbarAction
-import com.intellij.ide.util.PropertiesComponent
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.actionSystem.ActionButtonComponent
-import com.intellij.openapi.actionSystem.ActionManager
-import com.intellij.openapi.actionSystem.ActionPlaces
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.actionSystem.DefaultActionGroup
-import com.intellij.openapi.actionSystem.Separator
-import com.intellij.openapi.actionSystem.ToggleAction
-import com.intellij.openapi.application.EDT
-import com.intellij.openapi.components.service
-import com.intellij.openapi.diagnostic.thisLogger
-import com.intellij.openapi.project.DumbAware
-import com.intellij.openapi.project.DumbAwareAction
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.ui.ex.MessagesEx.showErrorDialog
-import com.intellij.openapi.ui.popup.JBPopupFactory
-import com.intellij.openapi.ui.popup.JBPopupFactory.ActionSelectionAid
-import com.intellij.openapi.util.Disposer
-import com.intellij.openapi.util.Key
-import com.intellij.openapi.util.text.StringUtil
-import com.intellij.openapi.wm.ToolWindow
-import com.intellij.openapi.wm.ToolWindowManager
-import com.intellij.openapi.wm.ex.ToolWindowEx
-import com.intellij.openapi.wm.ex.ToolWindowManagerListener
-import com.intellij.ui.content.Content
-import com.intellij.ui.content.ContentFactory
-import com.intellij.ui.content.ContentManagerEvent
-import com.intellij.ui.content.ContentManagerListener
-import com.intellij.ui.popup.list.ListPopupImpl
-import com.intellij.util.Alarm
-import com.intellij.util.IncorrectOperationException
-import com.intellij.util.concurrency.AppExecutorUtil.createBoundedApplicationPoolExecutor
-import com.intellij.util.concurrency.EdtExecutorService
-import com.intellij.util.ui.UIUtil
-import icons.StudioIcons
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.asCoroutineDispatcher
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.guava.asDeferred
-import kotlinx.coroutines.guava.await
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import java.awt.Component
-import java.awt.EventQueue
-import java.awt.event.KeyEvent
-import java.text.Collator
-import java.time.Duration
-
-private const val DEVICE_FRAME_VISIBLE_PROPERTY = "com.android.tools.idea.streaming.emulator.frame.visible"
-private const val DEVICE_FRAME_VISIBLE_DEFAULT = true
-private const val ZOOM_TOOLBAR_VISIBLE_PROPERTY = "com.android.tools.idea.streaming.zoom.toolbar.visible"
-private const val ZOOM_TOOLBAR_VISIBLE_DEFAULT = true
-private const val EMULATOR_DISCOVERY_INTERVAL_MILLIS = 1000
-
-private val ID_KEY = Key.create<DeviceId>("device-id")
-
-private val ATTENTION_REQUEST_EXPIRATION = Duration.ofSeconds(30)
-
-private val COLLATOR = Collator.getInstance()
-
-private val PANEL_COMPARATOR = compareBy<RunningDevicePanel, Any?>(COLLATOR) { it.title }.thenBy { it.id }
-
-/**
- * Manages contents of the Running Devices tool window. Listens to device connections and
- * disconnections and maintains [RunningDevicePanel]s, one per running AVD or a mirrored physical
- * device.
- */
-@UiThread
-internal class StreamingToolWindowManagerNoDnd @AnyThread constructor(
- private val toolWindow: ToolWindow,
-) : RunningEmulatorCatalog.Listener, DumbAware, Disposable {
-
- private val project
- @AnyThread get() = toolWindow.project
- private val properties = PropertiesComponent.getInstance(project)
- private val emulatorSettings = EmulatorSettings.getInstance()
- private val deviceMirroringSettings = DeviceMirroringSettings.getInstance()
- private val deviceProvisioner
- @AnyThread get() = project.service<DeviceProvisionerService>().deviceProvisioner
- private var initialized = false
- private var contentCreated = false
- private var mirroringConfirmationDialogShowing = false
- private var physicalDeviceWatcher: PhysicalDeviceWatcher
- private val panels = arrayListOf<RunningDevicePanel>()
- private var selectedPanel: RunningDevicePanel? = null
-
- /** When the tool window is hidden, the ID of the last selected device, otherwise null. */
- private var lastSelectedDeviceId: DeviceId? = null
-
- /** When the tool window is hidden, the state of the UI for all emulators, otherwise empty. */
- private val savedUiState = hashMapOf<DeviceId, UiState>()
- private val emulators = hashSetOf<EmulatorController>()
-
- private var onlineDevices = mapOf<String, ConnectedDeviceNoDnd>()
- /** Clients for mirrorable devices keyed by serial numbers. */
- private var deviceClients = mutableMapOf<String, DeviceClient>()
-
- /** Serial numbers of mirrored devices. */
- private var mirroredDevices = mutableSetOf<String>()
- /** Handles of devices excluded from mirroring keyed by serial numbers. */
- private var devicesExcludedFromMirroring = mutableMapOf<String, DeviceDescription>()
-
- /** Requested activation levels of devices that recently requested attention keyed by their serial numbers. */
- private val recentAttentionRequests =
- CacheBuilder.newBuilder().expireAfterWrite(ATTENTION_REQUEST_EXPIRATION).build<String, ActivationLevel>()
- /** Requested activation levels of AVDs keyed by their IDs. */
- private val recentEmulatorLaunches =
- CacheBuilder.newBuilder().expireAfterWrite(ATTENTION_REQUEST_EXPIRATION).build<String, ActivationLevel>()
-
- private val alarm = Alarm(Alarm.ThreadToUse.SWING_THREAD, this)
- @Suppress("UnstableApiUsage")
- private val toolWindowScope = disposingScope(Dispatchers.EDT)
-
- private val contentManagerListener = object : ContentManagerListener {
- override fun selectionChanged(event: ContentManagerEvent) {
- viewSelectionChanged()
- }
-
- override fun contentRemoveQuery(event: ContentManagerEvent) {
- val panel = event.content.component as? RunningDevicePanel ?: return
- when (panel) {
- is EmulatorToolWindowPanel -> panel.emulator.shutdown()
- is DeviceToolWindowPanel -> panelClosed(panel)
- }
-
- panels.remove(panel)
- savedUiState.remove(panel.id)
- if (panels.isEmpty()) {
- if (contentCreated) {
- createEmptyStatePanel()
- }
- hideLiveIndicator()
- }
- }
- }
-
- private val connectionStateListener = object : ConnectionStateListener {
- @AnyThread
- override fun connectionStateChanged(emulator: EmulatorController, connectionState: ConnectionState) {
- if (connectionState == ConnectionState.DISCONNECTED) {
- EventQueue.invokeLater { // This is safe because this code doesn't touch PSI or VFS.
- if (contentCreated && emulators.remove(emulator)) {
- removeEmulatorPanel(emulator)
- }
- }
- }
- }
- }
-
- private var deviceFrameVisible
- get() = properties.getBoolean(DEVICE_FRAME_VISIBLE_PROPERTY, DEVICE_FRAME_VISIBLE_DEFAULT)
- set(value) {
- properties.setValue(DEVICE_FRAME_VISIBLE_PROPERTY, value, DEVICE_FRAME_VISIBLE_DEFAULT)
- for (panel in panels) {
- panel.setDeviceFrameVisible(value)
- }
- }
-
- private var zoomToolbarIsVisible
- get() = properties.getBoolean(ZOOM_TOOLBAR_VISIBLE_PROPERTY, ZOOM_TOOLBAR_VISIBLE_DEFAULT)
- set(value) {
- properties.setValue(ZOOM_TOOLBAR_VISIBLE_PROPERTY, value, ZOOM_TOOLBAR_VISIBLE_DEFAULT)
- for (panel in panels) {
- panel.zoomToolbarVisible = value
- }
- }
-
- init {
- FlightRecorder.initialize(1000)
- Disposer.register(toolWindow.disposable, this)
-
- // Lazily initialize content since we can only have one frame.
- val messageBusConnection = project.messageBus.connect(this)
- messageBusConnection.subscribe(ToolWindowManagerListener.TOPIC, object : ToolWindowManagerListener {
-
- override fun stateChanged(toolWindowManager: ToolWindowManager) {
- val toolWindow = toolWindowManager.getToolWindow(RUNNING_DEVICES_TOOL_WINDOW_ID) ?: return
-
- toolWindowManager.invokeLater {
- if (!toolWindow.isDisposed) {
- if (toolWindow.isVisible) {
- createContent()
- }
- else {
- destroyContent()
- }
- }
- }
- }
- })
-
- messageBusConnection.subscribe(AvdLaunchListener.TOPIC,
- AvdLaunchListener { avd, commandLine, requestType, project ->
- if (project == toolWindow.project && isEmbeddedEmulator(commandLine)) {
- RunningEmulatorCatalog.getInstance().updateNow()
- EventQueue.invokeLater { // This is safe because this code doesn't touch PSI or VFS.
- showLiveIndicator()
- if (requestType != RequestType.INDIRECT) {
- onEmulatorHeadsUp(avd.name, ActivationLevel.ACTIVATE_TAB)
- }
- }
- }
- })
-
- messageBusConnection.subscribe(DeviceHeadsUpListener.TOPIC, MyDeviceHeadsUpListener())
-
- physicalDeviceWatcher = PhysicalDeviceWatcher(this)
- }
-
- override fun dispose() {
- destroyContent()
- }
-
- @AnyThread
- private fun onDeviceHeadsUp(serialNumber: String, activationLevel: ActivationLevel, project: Project) {
- if (project == toolWindow.project) {
- UIUtil.invokeLaterIfNeeded {
- val excludedDevice = devicesExcludedFromMirroring.remove(serialNumber)
- when {
- excludedDevice != null -> activateMirroring(serialNumber, excludedDevice.handle, excludedDevice.config, activationLevel)
- serialNumber in deviceClients -> onPhysicalDeviceHeadsUp(serialNumber, activationLevel)
- else -> addAttentionRequestAndTriggerEmulatorCatalogUpdate(serialNumber, activationLevel)
- }
- }
- }
- }
-
- private fun addAttentionRequestAndTriggerEmulatorCatalogUpdate(serialNumber: String, activationLevel: ActivationLevel) {
- recentAttentionRequests.put(serialNumber, activationLevel)
- alarm.addRequest(recentAttentionRequests::cleanUp, ATTENTION_REQUEST_EXPIRATION.toMillis())
- if (isLocalEmulator(serialNumber)) {
- val future = RunningEmulatorCatalog.getInstance().updateNow()
- future.addCallback(EdtExecutorService.getInstance(),
- success = { emulators ->
- if (emulators != null) {
- onEmulatorHeadsUp(serialNumber, emulators, activationLevel)
- }
- },
- failure = {})
- }
- }
-
- private fun onPhysicalDeviceHeadsUp(serialNumber: String, activationLevel: ActivationLevel) {
- if (toolWindow.isVisible) {
- val panel = findPanelBySerialNumber(serialNumber)
- if (panel != null) {
- selectPanel(panel)
- toolWindow.activate(activationLevel)
- }
- }
- else {
- recentAttentionRequests.put(serialNumber, activationLevel)
- toolWindow.activate(activationLevel)
- }
- }
-
- private fun onEmulatorHeadsUp(serialNumber: String, runningEmulators: Set<EmulatorController>, activationLevel: ActivationLevel) {
- val emulator = runningEmulators.find { it.emulatorId.serialNumber == serialNumber } ?: return
- // Ignore standalone emulators.
- if (emulator.emulatorId.isEmbedded) {
- onEmulatorHeadsUp(emulator.emulatorId.avdId, activationLevel)
- }
- }
-
- private fun onEmulatorHeadsUp(avdId: String, activationLevel: ActivationLevel) {
- toolWindow.activate(activationLevel)
-
- val panel = findPanelByAvdId(avdId)
- if (panel == null) {
- RunningEmulatorCatalog.getInstance().updateNow()
- recentEmulatorLaunches.put(avdId, activationLevel)
- alarm.addRequest(recentEmulatorLaunches::cleanUp, ATTENTION_REQUEST_EXPIRATION.toMillis())
- }
- else {
- selectPanel(panel)
- }
- }
-
- private fun selectPanel(panel: RunningDevicePanel) {
- if (selectedPanel != panel) {
- val contentManager = toolWindow.contentManager
- val content = contentManager.getContent(panel)
- contentManager.setSelectedContent(content)
- }
- }
-
- private fun createContent() {
- if (!initialized) {
- initialized = true
-
- val newTabAction = NewTabAction()
- newTabAction.registerCustomShortcutSet(KeyEvent.VK_T, KeyEvent.CTRL_DOWN_MASK, toolWindow.component)
- (toolWindow as ToolWindowEx).setTabActions(newTabAction)
-
- toolWindow.contentManager.addDataProvider { dataId -> getDataFromSelectedPanel(dataId) }
- val actionGroup = DefaultActionGroup()
- actionGroup.addAction(ToggleZoomToolbarAction())
- actionGroup.addAction(ToggleDeviceFrameAction())
- toolWindow.setAdditionalGearActions(actionGroup)
- }
-
- if (contentCreated) {
- return
- }
- contentCreated = true
-
- val emulatorCatalog = RunningEmulatorCatalog.getInstance()
- emulatorCatalog.updateNow()
- emulatorCatalog.addListener(this, EMULATOR_DISCOVERY_INTERVAL_MILLIS)
- // Ignore standalone emulators.
- emulators.addAll(emulatorCatalog.emulators.filter { it.emulatorId.isEmbedded })
-
- // Create the panel for the last selected device before other panels so that it becomes selected.
- when (val activeDeviceId = lastSelectedDeviceId) {
- is DeviceId.EmulatorDeviceId -> {
- val activeEmulator = emulators.find { it.emulatorId == activeDeviceId.emulatorId }
- if (activeEmulator != null && !activeEmulator.isShuttingDown) {
- addEmulatorPanel(activeEmulator)
- }
- }
-
- is DeviceId.PhysicalDeviceId -> {
- val deviceClient = deviceClients[activeDeviceId.serialNumber]
- if (deviceClient != null) {
- activateMirroring(activeDeviceId.serialNumber, deviceClient, ActivationLevel.ACTIVATE_TAB)
- }
- }
-
- else -> {}
- }
-
- for (emulator in emulators) {
- if (emulator.emulatorId.serialNumber != lastSelectedDeviceId?.serialNumber && !emulator.isShuttingDown) {
- addEmulatorPanel(emulator)
- }
- }
-
- for ((serialNumber, deviceClient) in deviceClients) {
- if (serialNumber != lastSelectedDeviceId?.serialNumber) {
- activateMirroring(serialNumber, deviceClient, ActivationLevel.CREATE_TAB)
- }
- }
-
- // Not maintained when the tool window is visible.
- lastSelectedDeviceId = null
-
- val contentManager = toolWindow.contentManager
- if (contentManager.contentCount == 0) {
- createEmptyStatePanel()
- }
-
- contentManager.addContentManagerListener(contentManagerListener)
- viewSelectionChanged()
- }
-
- private fun destroyContent() {
- if (!contentCreated) {
- return
- }
- contentCreated = false
-
- lastSelectedDeviceId = selectedPanel?.id
-
- RunningEmulatorCatalog.getInstance().removeListener(this)
- for (emulator in emulators) {
- emulator.removeConnectionStateListener(connectionStateListener)
- }
- emulators.clear()
- mirroredDevices.clear()
- selectedPanel?.let {
- savedUiState[it.id] = it.destroyContent()
- }
- selectedPanel = null
- panels.clear()
- recentAttentionRequests.invalidateAll()
- recentEmulatorLaunches.invalidateAll()
- val contentManager = toolWindow.contentManager
- contentManager.removeContentManagerListener(contentManagerListener)
- contentManager.removeAllContents(true)
- }
-
- private fun addEmulatorPanel(emulator: EmulatorController) {
- emulator.addConnectionStateListener(connectionStateListener)
- addPanel(EmulatorToolWindowPanel(project, emulator))
- }
-
- private fun addPanel(panel: RunningDevicePanel) {
- FlightRecorder.log { "${TraceUtils.getSimpleId(this)}.addPanel(${TraceUtils.getSimpleId(panel)} ${panel.title})\n" +
- TraceUtils.getCurrentStack() }
- val contentManager = toolWindow.contentManager
- var placeholderContent: Content? = null
- if (panels.isEmpty()) {
- showLiveIndicator()
- if (!contentManager.isEmpty) {
- // Remember the placeholder panel content to remove it later. Deleting it now would leave
- // the tool window empty and cause the contentRemoved method in ToolWindowContentUi to
- // hide it.
- placeholderContent = contentManager.getContent(0)
- }
- }
-
- val contentFactory = ContentFactory.getInstance()
- val content = contentFactory.createContent(panel, shortenTitleText(panel.title), false).apply {
- putUserData(ToolWindow.SHOW_CONTENT_ICON, true)
- tabName = panel.title
- description = panel.description
- icon = panel.icon
- popupIcon = panel.icon
- setPreferredFocusedComponent(panel::preferredFocusableComponent)
- putUserData(ID_KEY, panel.id)
- }
-
- panel.zoomToolbarVisible = zoomToolbarIsVisible
-
- val index = panels.binarySearch(panel, PANEL_COMPARATOR).inv()
- if (index < 0) {
- thisLogger().error("An attempt to add a duplicate panel ${TraceUtils.getSimpleId(panel)} ${panel.title}\n" +
- FlightRecorder.getAndClear())
- }
-
- if (index >= 0) {
- panels.add(index, panel)
- contentManager.addContent(content, index)
-
- if (selectedPanel != panel) {
- // Activate the newly added panel if it corresponds to a recently launched or used Emulator.
- val deviceId = panel.id
- if (deviceId is DeviceId.EmulatorDeviceId) {
- val avdId = deviceId.emulatorId.avdId
- if (recentEmulatorLaunches.getIfPresent(avdId) != null) {
- recentEmulatorLaunches.invalidate(avdId)
- contentManager.setSelectedContent(content)
- }
- }
- }
-
- placeholderContent?.let { contentManager.removeContent(it, true) } // Remove the placeholder panel if it was present.
- }
- }
-
- private fun removeEmulatorPanel(emulator: EmulatorController) {
- emulator.removeConnectionStateListener(connectionStateListener)
-
- val panel = findPanelByEmulatorId(emulator.emulatorId) ?: return
- removePanel(panel)
- }
-
- private fun removePhysicalDevicePanel(serialNumber: String) {
- val panel = findPanelBySerialNumber(serialNumber) as? DeviceToolWindowPanel ?: return
- removePhysicalDevicePanel(panel)
- }
-
- private fun removePhysicalDevicePanel(panel: DeviceToolWindowPanel) {
- val serialNumber = panel.id.serialNumber
- deviceClients.remove(serialNumber)?.let {
- Disposer.dispose(it)
- updateMirroringHandlesFlow()
- }
- mirroredDevices.remove(serialNumber)
- removePanel(panel)
- }
-
- private fun removeAllPhysicalDevicePanels() {
- panels.filterIsInstance<DeviceToolWindowPanel>().forEach(::removePhysicalDevicePanel)
- }
-
- private fun removePanel(panel: RunningDevicePanel) {
- val contentManager = toolWindow.contentManager
- val content = contentManager.getContent(panel)
- if (content != null) {
- contentManager.removeContent(content, true)
- }
- }
-
- private fun createEmptyStatePanel() {
- val panel = try {
- EmptyStatePanel(project, this)
- }
- catch (e: IncorrectOperationException) {
- // This object has been disposed already.
- return
- }
- val contentFactory = ContentFactory.getInstance()
- val content = contentFactory.createContent(panel, null, false).apply {
- isCloseable = false
- }
- val contentManager = toolWindow.contentManager
- try {
- contentManager.addContent(content)
- contentManager.setSelectedContent(content)
- }
- catch (e: IncorrectOperationException) {
- // Content manager has been disposed already.
- Disposer.dispose(content)
- }
- }
-
- private fun viewSelectionChanged() {
- val contentManager = toolWindow.contentManager
- val content = contentManager.selectedContent
- val id = content?.getUserData(ID_KEY)
- if (id != selectedPanel?.id) {
- selectedPanel?.let { panel ->
- savedUiState[panel.id] = panel.destroyContent()
- selectedPanel = null
- }
-
- if (id != null) {
- selectedPanel = findPanelByDeviceId(id)
- selectedPanel?.createContent(deviceFrameVisible, savedUiState.remove(id))
- ToggleToolbarAction.setToolbarVisible(toolWindow, PropertiesComponent.getInstance(project), null)
- }
- }
- }
-
- private fun getDataFromSelectedPanel(dataId: String): Any? {
- val selectedContent = toolWindow.contentManager.selectedContent ?: return null
- val panelId = selectedContent.getUserData(ID_KEY) ?: return null
- val panel = findPanelByDeviceId(panelId) ?: return null
- return panel.getData(dataId)
- }
-
- private fun findPanelByDeviceId(deviceId: DeviceId): RunningDevicePanel? {
- return panels.firstOrNull { it.id == deviceId }
- }
-
- private fun findPanelByEmulatorId(emulatorId: EmulatorId): RunningDevicePanel? {
- return panels.firstOrNull { it.id is DeviceId.EmulatorDeviceId && it.id.emulatorId == emulatorId }
- }
-
- private fun findPanelByAvdId(avdId: String): RunningDevicePanel? {
- return panels.firstOrNull { it.id is DeviceId.EmulatorDeviceId && it.id.emulatorId.avdId == avdId }
- }
-
- private fun findPanelBySerialNumber(serialNumber: String): RunningDevicePanel? {
- return panels.firstOrNull { it.id.serialNumber == serialNumber }
- }
-
- private fun showLiveIndicator() {
- toolWindow.setIcon(ExecutionUtil.getLiveIndicator(StudioIcons.Shell.ToolWindows.EMULATOR))
- }
-
- private fun hideLiveIndicator() {
- toolWindow.setIcon(StudioIcons.Shell.ToolWindows.EMULATOR)
- }
-
- @AnyThread
- override fun emulatorAdded(emulator: EmulatorController) {
- if (emulator.emulatorId.isEmbedded) {
- EventQueue.invokeLater { // This is safe because this code doesn't touch PSI or VFS.
- if (contentCreated && emulators.add(emulator)) {
- addEmulatorPanel(emulator)
- }
- }
- }
- }
-
- @AnyThread
- override fun emulatorRemoved(emulator: EmulatorController) {
- if (emulator.emulatorId.isEmbedded) {
- EventQueue.invokeLater { // This is safe because this code doesn't touch PSI or VFS.
- if (contentCreated && emulators.remove(emulator)) {
- removeEmulatorPanel(emulator)
- }
- }
- }
- }
-
- private fun panelClosed(panel: DeviceToolWindowPanel) {
- val deviceHandle = panel.deviceClient.deviceHandle
- if (deviceHandle.state.isOnline()) {
- val deactivationAction = if (isLocalEmulator(panel.deviceSerialNumber)) null else deviceHandle.deactivationAction
- deactivationAction?.let { CoroutineScope(Dispatchers.IO).launch { it.deactivate() } } ?: stopMirroring(panel.deviceSerialNumber)
- }
- }
-
- private fun deactivateMirroring(serialNumber: String) {
- if (contentCreated) {
- val panel = findPanelBySerialNumber(serialNumber) as? DeviceToolWindowPanel ?: return
- mirroredDevices.remove(serialNumber)
- removePanel(panel)
- }
- else {
- stopMirroring(serialNumber)
- }
- }
-
- private fun stopMirroring(serialNumber: String) {
- mirroredDevices.remove(serialNumber)
- val deviceClient = deviceClients.remove(serialNumber)
- if (deviceClient != null) {
- devicesExcludedFromMirroring[serialNumber] =
- DeviceDescription(deviceClient.deviceName, serialNumber, deviceClient.deviceHandle, deviceClient.deviceConfig)
- Disposer.dispose(deviceClient)
- updateMirroringHandlesFlow()
- }
- }
-
- private fun ToolWindow.activate(activationLevel: ActivationLevel) {
- if (isVisible) {
- if (activationLevel >= ActivationLevel.ACTIVATE_TAB) {
- activate(null)
- }
- }
- else {
- show {
- if (activationLevel >= ActivationLevel.ACTIVATE_TAB) {
- activate(null)
- }
- }
- }
- }
-
- private fun activateMirroring(deviceDescription: DeviceDescription) {
- val serialNumber = deviceDescription.serialNumber
- val deviceClient = getOrCreateDeviceClient(serialNumber, deviceDescription.handle, deviceDescription.config)
- if (serialNumber !in mirroredDevices) {
- startMirroringIfConfirmed(serialNumber, deviceClient, ActivationLevel.ACTIVATE_TAB)
- }
- }
-
- private fun activateMirroring(serialNumber: String, device: DeviceHandle, config: DeviceConfiguration, activationLevel: ActivationLevel) {
- recentAttentionRequests.invalidate(serialNumber)
- val deviceClient = getOrCreateDeviceClient(serialNumber, device, config)
- if (contentCreated) {
- activateMirroring(serialNumber, deviceClient, activationLevel)
- if (activationLevel >= ActivationLevel.SELECT_TAB) {
- onPhysicalDeviceHeadsUp(serialNumber, activationLevel)
- }
- }
- else if (activationLevel >= ActivationLevel.SHOW_TOOL_WINDOW) {
- recentAttentionRequests.put(serialNumber, activationLevel)
- toolWindow.activate(activationLevel)
- }
- }
-
- private fun activateMirroring(serialNumber: String, deviceClient: DeviceClient, activationLevel: ActivationLevel) {
- if (serialNumber !in mirroredDevices && serialNumber !in devicesExcludedFromMirroring) {
- startMirroringIfConfirmed(serialNumber, deviceClient, activationLevel)
- }
- }
-
- private fun startMirroringIfConfirmed(serialNumber: String, deviceClient: DeviceClient, activationLevel: ActivationLevel) {
- // Reservable devices are assumed to be privacy protected.
- if (deviceMirroringSettings.confirmationDialogShown || deviceClient.deviceHandle.reservationAction != null) {
- startMirroring(serialNumber, deviceClient, activationLevel)
- }
- else if (!mirroringConfirmationDialogShowing) { // Ignore a recursive call inside the dialog's event loop.
- mirroringConfirmationDialogShowing = true
- val title = "About to Start Mirroring of ${deviceClient.deviceName}"
- val dialogWrapper = MirroringConfirmationDialog(title).createWrapper(project).apply { show() }
- mirroringConfirmationDialogShowing = false
- when (dialogWrapper.exitCode) {
- MirroringConfirmationDialog.ACCEPT_EXIT_CODE -> {
- deviceMirroringSettings.confirmationDialogShown = true
- startMirroring(serialNumber, deviceClient, activationLevel)
- }
- MirroringConfirmationDialog.REJECT_EXIT_CODE -> stopMirroring(serialNumber)
- else -> return
- }
- }
- }
-
- private fun startMirroring(serialNumber: String, deviceClient: DeviceClient, activationLevel: ActivationLevel) {
- devicesExcludedFromMirroring.remove(serialNumber)
- if (serialNumber in onlineDevices) {
- if (contentCreated) {
- if (mirroredDevices.add(serialNumber)) {
- updateMirroringHandlesFlow()
- deviceClient.establishAgentConnectionWithoutVideoStreamAsync() // Start the agent and connect to it proactively.
- showLiveIndicator()
- val panel = DeviceToolWindowPanel(project, deviceClient)
- addPanel(panel)
- if (activationLevel >= ActivationLevel.SELECT_TAB) {
- selectPanel(panel, requestFocus = activationLevel >= ActivationLevel.ACTIVATE_TAB)
- }
- }
- }
- else if (activationLevel >= ActivationLevel.SHOW_TOOL_WINDOW) {
- recentAttentionRequests.put(serialNumber, activationLevel)
- toolWindow.activate(activationLevel)
- }
- }
- }
-
- private fun selectPanel(panel: DeviceToolWindowPanel, requestFocus: Boolean) {
- val contentManager = toolWindow.contentManager
- val content = contentManager.getContent(panel) ?: return
- contentManager.setSelectedContent(content, requestFocus)
- }
-
- private fun updateMirroringHandlesFlow() {
- if (project.isDisposed) {
- return
- }
- val mirroringHandles = mutableMapOf<DeviceHandle, MirroringHandle>()
- for (device in devicesExcludedFromMirroring.values) {
- if (device.handle.reservationAction == null) {
- mirroringHandles[device.handle] = MirroringActivator(device)
- }
- }
- for (client in deviceClients.values) {
- if (client.deviceHandle.reservationAction == null) {
- mirroringHandles[client.deviceHandle] = MirroringDeactivator(client.deviceSerialNumber)
- }
- }
- project.service<MirroringManager>().mirroringHandles.value = mirroringHandles
- }
-
- @AnyThread
- private fun deviceConnected(serialNumber: String, device: ConnectedDeviceNoDnd) {
- val config = DeviceConfiguration(device.state.properties, useTitleAsName = isLocalEmulator(serialNumber))
- UIUtil.invokeLaterIfNeeded { // This is safe because this code doesn't touch PSI or VFS.
- deviceConnected(serialNumber, device.handle, config)
- }
- }
-
- private fun deviceConnected(serialNumber: String, deviceHandle: DeviceHandle, config: DeviceConfiguration) {
- if (serialNumber in onlineDevices && serialNumber !in mirroredDevices) {
- if (deviceMirroringSettings.activateOnConnection || recentAttentionRequests.getIfPresent(serialNumber) != null) {
- val activationLevel = if (recentAttentionRequests.getIfPresent(serialNumber) != null) ActivationLevel.SELECT_TAB
- else ActivationLevel.SHOW_TOOL_WINDOW
- activateMirroring(serialNumber, deviceHandle, config, activationLevel)
- }
- else {
- // The device is excluded from mirroring.
- val deviceDescription = devicesExcludedFromMirroring[serialNumber]
- if (deviceDescription == null) {
- devicesExcludedFromMirroring[serialNumber] = DeviceDescription(config.deviceName, serialNumber, deviceHandle, config)
- updateMirroringHandlesFlow()
- }
- }
- }
- }
-
- private fun getOrCreateDeviceClient(serialNumber: String, deviceHandle: DeviceHandle, config: DeviceConfiguration): DeviceClient {
- val disposable = physicalDeviceWatcher
- var deviceClient = deviceClients[serialNumber]
- if (deviceClient == null) {
- deviceClient = DeviceClient(disposable, serialNumber, deviceHandle, config, config.deviceProperties.abi.toString(), project)
- deviceClients[serialNumber] = deviceClient
- updateMirroringHandlesFlow()
- }
- return deviceClient
- }
-
- private suspend fun showDeviceActionPopup(anchorComponent: Component?, dataContext: DataContext) {
- val actionGroup = createDeviceActions()
-
- val popup = JBPopupFactory.getInstance().createActionGroupPopup(
- null, actionGroup, dataContext, ActionSelectionAid.SPEEDSEARCH, true, null, -1, null,
- ActionPlaces.getActionGroupPopupPlace(ActionPlaces.TOOLWINDOW_TOOLBAR_BAR))
-
- if (anchorComponent == null) {
- popup.showInFocusCenter()
- }
- else {
- popup.showUnderneathOf(anchorComponent)
- }
- // Clear initial selection.
- (popup as? ListPopupImpl)?.list?.clearSelection()
- }
-
- private suspend fun createDeviceActions(): DefaultActionGroup {
- return DefaultActionGroup().apply {
- val deviceDescriptions = devicesExcludedFromMirroring.values.toTypedArray().sortedBy { it.deviceName }
- if (deviceDescriptions.isNotEmpty()) {
- add(Separator("Connected Devices"))
- for (deviceDescription in deviceDescriptions) {
- add(StartDeviceMirroringAction(deviceDescription))
- }
- add(Separator.getInstance())
- }
-
- val remoteDevices = deviceProvisioner.reservedAndStartableDevices()
- if (remoteDevices.isNotEmpty()) {
- add(Separator("Remote Devices"))
- for (device in remoteDevices) {
- add(StartRemoteDeviceAction(device))
- }
- add(Separator.getInstance())
- }
-
- val avds = getStartableAvds().sortedBy { it.displayName }
- if (avds.isNotEmpty()) {
- add(Separator("Virtual Devices"))
- for (avd in avds) {
- add(StartAvdAction(avd, project))
- }
- add(Separator.getInstance())
- }
-
- add(ActionManager.getInstance().getAction(PairDevicesUsingWiFiAction.ID))
- }
- }
-
- private suspend fun getStartableAvds(): List<AvdInfo> {
- return withContext(Dispatchers.IO) {
- val runningAvdFolders = RunningEmulatorCatalog.getInstance().emulators.map { it.emulatorId.avdFolder }.toSet()
- val avdManager = AvdManagerConnection.getDefaultAvdManagerConnection()
- avdManager.getAvds(false).filter { it.dataFolderPath !in runningAvdFolders }
- }
- }
-
- private inner class MyDeviceHeadsUpListener : DeviceHeadsUpListener {
-
- override fun userInvolvementRequired(deviceSerialNumber: String, project: Project) {
- onDeviceHeadsUp(deviceSerialNumber, ActivationLevel.ACTIVATE_TAB, project)
- }
-
- override fun launchingApp(deviceSerialNumber: String, project: Project) {
- val activate = if (isNonMirrorableLocalEmulator(deviceSerialNumber)) emulatorSettings.activateOnAppLaunch
- else deviceMirroringSettings.activateOnAppLaunch
- if (activate) {
- onDeviceHeadsUp(deviceSerialNumber, ActivationLevel.SELECT_TAB, project)
- }
- }
-
- override fun launchingTest(deviceSerialNumber: String, project: Project) {
- val activate = if (isNonMirrorableLocalEmulator(deviceSerialNumber)) emulatorSettings.activateOnTestLaunch
- else deviceMirroringSettings.activateOnTestLaunch
- if (activate) {
- onDeviceHeadsUp(deviceSerialNumber, ActivationLevel.SELECT_TAB, project)
- }
- }
-
- private fun isNonMirrorableLocalEmulator(deviceSerialNumber: String): Boolean {
- return isLocalEmulator(deviceSerialNumber) &&
- deviceSerialNumber !in deviceClients && deviceSerialNumber !in devicesExcludedFromMirroring
- }
- }
-
- private inner class ToggleDeviceFrameAction : ToggleAction("Show Device Frame"), DumbAware {
-
- override fun update(event: AnActionEvent) {
- super.update(event)
- val panel = selectedPanel
- event.presentation.isEnabledAndVisible = panel is EmulatorToolWindowPanel && panel.emulator.emulatorConfig.skinFolder != null
- }
-
- override fun isSelected(event: AnActionEvent): Boolean {
- return deviceFrameVisible
- }
-
- override fun setSelected(event: AnActionEvent, state: Boolean) {
- deviceFrameVisible = state
- }
-
- override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
- }
-
- private inner class ToggleZoomToolbarAction : ToggleAction("Show Zoom Controls"), DumbAware {
-
- override fun isSelected(event: AnActionEvent): Boolean {
- return zoomToolbarIsVisible
- }
-
- override fun setSelected(event: AnActionEvent, state: Boolean) {
- zoomToolbarIsVisible = state
- }
-
- override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
- }
-
- private inner class PhysicalDeviceWatcher(disposableParent: Disposable) : Disposable {
- private val coroutineScope: CoroutineScope
-
- init {
- Disposer.register(disposableParent, this)
- val executor = createBoundedApplicationPoolExecutor("EmulatorToolWindowManager.PhysicalDeviceWatcher", 1)
- coroutineScope = AndroidCoroutineScope(this, executor.asCoroutineDispatcher())
- coroutineScope.launch {
- deviceProvisioner.mirrorableDevicesBySerialNumber().collect { newOnlineDevices ->
- UIUtil.invokeLaterIfNeeded {
- onlineDevices = newOnlineDevices
- onlineDevicesChanged()
- }
- }
- }
- }
-
- private fun onlineDevicesChanged() {
- val removedExcluded = devicesExcludedFromMirroring.keys.retainAll(onlineDevices.keys)
- val removed = deviceClients.keys.minus(onlineDevices.keys)
- if (contentCreated) {
- for (device in removed) {
- removePhysicalDevicePanel(device)
- }
- }
- else {
- deviceClients.keys.removeAll(removed)
- }
- if (removedExcluded || removed.isNotEmpty()) {
- updateMirroringHandlesFlow()
- }
-
- for ((serialNumber, device) in onlineDevices) {
- if (serialNumber !in mirroredDevices && serialNumber !in devicesExcludedFromMirroring) {
- coroutineScope.launch {
- deviceConnected(serialNumber, device)
- }
- }
- }
-
- if (!contentCreated) {
- toolWindowScope.launch(Dispatchers.IO) {
- val embeddedEmulators = RunningEmulatorCatalog.getInstance().updateNow().await().filter { it.emulatorId.isEmbedded }
- withContext(Dispatchers.EDT) {
- if (deviceClients.isEmpty() && embeddedEmulators.isEmpty()) {
- hideLiveIndicator()
- }
- }
- }
- }
- }
-
- override fun dispose() {
- deviceClients.clear() // The clients have been disposed already.
- updateMirroringHandlesFlow()
- removeAllPhysicalDevicePanels()
- }
- }
-
- private inner class NewTabAction : DumbAwareAction("Add Device", "Show a new device", AllIcons.General.Add), DumbAware {
-
- override fun actionPerformed(event: AnActionEvent) {
- val component = event.inputEvent?.component
- val actionComponent = if (component is ActionButtonComponent) component else event.findComponentForAction(this)
- val dataContext = event.dataContext
-
- toolWindowScope.launch {
- showDeviceActionPopup(actionComponent, dataContext)
- }
- }
-
- override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
- }
-
- private inner class StartDeviceMirroringAction(
- private val device: DeviceDescription,
- ) : DumbAwareAction(device.deviceName, null, device.config.deviceProperties.icon) {
-
- override fun actionPerformed(event: AnActionEvent) {
- activateMirroring(device)
- }
-
- override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
- }
-
- private inner class StartRemoteDeviceAction(
- private val device: DeviceHandle,
- ) : DumbAwareAction(device.sourceTemplate?.properties?.composeDeviceName(), null, device.sourceTemplate?.properties?.icon) {
-
- override fun actionPerformed(event: AnActionEvent) {
- device.scope.launch { device.activationAction?.activate() }
- }
-
- override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
- }
-
- private inner class StartAvdAction(
- private val avd: AvdInfo,
- private val project: Project,
- ) : DumbAwareAction(avd.displayName, null, avd.icon) {
-
- override fun actionPerformed(event: AnActionEvent) {
- toolWindowScope.launch(Dispatchers.IO) {
- val avdManager = AvdManagerConnection.getDefaultAvdManagerConnection()
- try {
- avdManager.startAvd(project, avd, RequestType.DIRECT_RUNNING_DEVICES).asDeferred().await()
- }
- catch (e: Exception) {
- val message = e.message?.let { if (it.contains(avd.displayName)) it else "Unable to launch ${avd.displayName} - $it"} ?:
- "Unable to launch ${avd.displayName}"
- withContext(Dispatchers.EDT) {
- showErrorDialog(toolWindow.component, message)
- }
- }
- }
- }
-
- override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
- }
-
- private inner class MirroringActivator(private val device: DeviceDescription) : MirroringHandle {
-
- override val mirroringState: MirroringState
- get() = MirroringState.INACTIVE
-
- override fun toggleMirroring() {
- activateMirroring(device)
- }
-
- override fun toString(): String {
- return "MirroringActivator for ${device.serialNumber}"
- }
- }
-
- private inner class MirroringDeactivator(private val serialNumber: String) : MirroringHandle {
-
- override val mirroringState: MirroringState
- get() = MirroringState.ACTIVE
-
- override fun toggleMirroring() {
- deactivateMirroring(serialNumber)
- }
-
- override fun toString(): String {
- return "MirroringDeactivator for $serialNumber"
- }
- }
-
- private class DeviceDescription(val deviceName: String, val serialNumber: String, val handle: DeviceHandle,
- val config: DeviceConfiguration)
-
- private enum class ActivationLevel {
- /** Create tab, but don't select it and don't show the tool window if hidden. */
- CREATE_TAB,
- /** Create tab and show the tool window if hidden. */
- SHOW_TOOL_WINDOW,
- /** Create tab, show the tool window if hidden and select the new tab. */
- SELECT_TAB,
- /** Create tab, show the tool window if hidden, select the new tab and focus on it. */
- ACTIVATE_TAB,
- }
-}
-
-private class ConnectedDeviceNoDnd(val handle: DeviceHandle, val state: DeviceState.Connected)
-
-private fun DeviceProvisioner.mirrorableDevicesBySerialNumber(): Flow<Map<String, ConnectedDeviceNoDnd>> {
- return ConnectedDeviceNoDnds().map { connectedDevices ->
- connectedDevices.filter { it.state.isMirrorable() }.associateBy { it.state.serialNumber }
- }
-}
-
-private fun DeviceProvisioner.ConnectedDeviceNoDnds(): Flow<List<ConnectedDeviceNoDnd>> {
- return mapStateNotNull { handle, state -> (state as? DeviceState.Connected)?.let { ConnectedDeviceNoDnd(handle, it) } }
-}
-
-private fun DeviceProvisioner.reservedAndStartableDevices(): List<DeviceHandle> {
- return devices.value.filter {
- it.state.reservation?.state == ReservationState.ACTIVE && it.activationAction?.presentation?.value?.enabled == true
- }
-}
-
-private suspend fun DeviceState.Connected.isMirrorable(): Boolean {
- if (!isOnline()) {
- return false
- }
-
- val deviceSerialNumber = serialNumber
- when {
- isLocalEmulator(deviceSerialNumber) -> { // Local virtual device.
- if (!StudioFlags.DEVICE_MIRRORING_STANDALONE_EMULATORS.get()) {
- return false
- }
- val emulators = RunningEmulatorCatalog.getInstance().updateNow().suspendingGet()
- val emulator = emulators.find { "emulator-${it.emulatorId.serialPort}" == deviceSerialNumber }
- if (emulator == null || emulator.emulatorId.isEmbedded) {
- return false
- }
- }
- properties.isVirtual == true -> { // Remote virtual device.
- if (!StudioFlags.DEVICE_MIRRORING_REMOTE_EMULATORS.get()) {
- return false
- }
- }
- }
-
- val apiLevel = properties.androidVersion?.apiLevel ?: SdkVersionInfo.HIGHEST_KNOWN_STABLE_API
- // Mirroring is supported for API >= 26. Wear OS devices with API < 30 don't support VP8/VP9 video encoders.
- return apiLevel >= 26 && (properties.deviceType != DeviceType.WEAR || apiLevel >= 30) && properties.abi != null
-}
-
-private val DeviceState.Connected.serialNumber: String
- get() = connectedDevice.serialNumber
-
-private fun isLocalEmulator(deviceSerialNumber: String) =
- deviceSerialNumber.startsWith("emulator-")
-
-private fun isEmbeddedEmulator(commandLine: GeneralCommandLine) =
- commandLine.parametersList.parameters.contains("-qt-hide-window")
-
-private fun shortenTitleText(title: String): String =
- StringUtil.shortenTextWithEllipsis(title, 25, 6)
diff --git a/streaming/src/com/android/tools/idea/streaming/core/StreamingUtils.kt b/streaming/src/com/android/tools/idea/streaming/core/StreamingUtils.kt
index 68921e7..330b630 100644
--- a/streaming/src/com/android/tools/idea/streaming/core/StreamingUtils.kt
+++ b/streaming/src/com/android/tools/idea/streaming/core/StreamingUtils.kt
@@ -149,20 +149,6 @@
}
/**
- * If the device is connected by WiFi, extracts the device's own serial number from the serial
- * number returned by ADB, otherwise returns the original serial number. For example, converts
- * "adb-3211105H802MQD-wG1oxA._adb-tls-connect._tcp." to "3211105H802MQD".
- */
-fun normalizeDeviceSerialNumber(serialNumber: String) : String {
- val prefix = "adb-"
- if (serialNumber.startsWith(prefix)) {
- val end = serialNumber.indexOf('-', prefix.length)
- return if (end > prefix.length) serialNumber.substring(prefix.length, end) else serialNumber.substring(prefix.length)
- }
- return serialNumber
-}
-
-/**
* Returns this integer scaled and rounded to the closest integer.
*
* @param scale the scale factor
diff --git a/streaming/src/com/android/tools/idea/streaming/device/DeviceController.kt b/streaming/src/com/android/tools/idea/streaming/device/DeviceController.kt
index c050c01..ceda98a 100644
--- a/streaming/src/com/android/tools/idea/streaming/device/DeviceController.kt
+++ b/streaming/src/com/android/tools/idea/streaming/device/DeviceController.kt
@@ -241,12 +241,12 @@
* ```
*/
private fun parseDeviceStates(text: String): List<FoldingState> {
- val regex = Regex("DeviceState\\{identifier=(?<id>\\d+), name='(?<name>\\w+)', app_accessible=(?<accessible>true|false)}")
+ val regex = Regex("DeviceState\\{identifier=(?<id>\\d+), name='(?<name>\\w+)'(, app_accessible=(?<accessible>true|false))?}")
return regex.findAll(text).map {
val groups = it.groups
val id = groups["id"]?.value?.toInt() ?: throw IllegalArgumentException()
val name = groups["name"]?.value ?: throw IllegalArgumentException()
- val accessible = groups["accessible"]?.value == "true"
+ val accessible = groups["accessible"]?.value != "false"
FoldingState(id, deviceStateNameToFoldingStateName(name), accessible)
}.toList()
}
diff --git a/streaming/src/com/android/tools/idea/streaming/device/DeviceToolWindowPanel.kt b/streaming/src/com/android/tools/idea/streaming/device/DeviceToolWindowPanel.kt
index 9bcfc86..e818d47 100644
--- a/streaming/src/com/android/tools/idea/streaming/device/DeviceToolWindowPanel.kt
+++ b/streaming/src/com/android/tools/idea/streaming/device/DeviceToolWindowPanel.kt
@@ -28,8 +28,10 @@
import com.android.tools.idea.streaming.device.screenshot.DeviceScreenshotOptions
import com.android.tools.idea.ui.screenrecording.ScreenRecorderAction
import com.android.tools.idea.ui.screenshot.ScreenshotAction
+import com.android.utils.TraceUtils
import com.intellij.execution.runners.ExecutionUtil
import com.intellij.openapi.Disposable
+import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.ui.JBColor
@@ -41,6 +43,7 @@
* Provides view of one physical device in the Running Devices tool window.
*/
internal class DeviceToolWindowPanel(
+ disposableParent: Disposable,
private val project: Project,
val deviceClient: DeviceClient,
) : RunningDevicePanel(DeviceId.ofPhysicalDevice(deviceClient.deviceSerialNumber), DEVICE_MAIN_TOOLBAR_ID, STREAMING_SECONDARY_TOOLBAR_ID) {
@@ -97,6 +100,10 @@
}
}
+ init {
+ Disposer.register(disposableParent, this)
+ }
+
override fun setDeviceFrameVisible(visible: Boolean) {
// Showing device frame is not supported for physical devices.
}
@@ -105,7 +112,13 @@
* Populates the device panel with content.
*/
override fun createContent(deviceFrameVisible: Boolean, savedUiState: UiState?) {
+ if (contentDisposable != null) {
+ thisLogger().error(IllegalStateException("${title}: content already exists"))
+ return
+ }
+
val disposable = Disposer.newDisposable()
+ Disposer.register(this, disposable)
contentDisposable = disposable
val uiState = savedUiState as DeviceUiState? ?: DeviceUiState()
diff --git a/streaming/src/com/android/tools/idea/streaming/device/dialogs/MirroringConfirmationDialog.kt b/streaming/src/com/android/tools/idea/streaming/device/dialogs/MirroringConfirmationDialog.kt
index 7055d163..b20f8f0b 100644
--- a/streaming/src/com/android/tools/idea/streaming/device/dialogs/MirroringConfirmationDialog.kt
+++ b/streaming/src/com/android/tools/idea/streaming/device/dialogs/MirroringConfirmationDialog.kt
@@ -69,7 +69,7 @@
CloseDialogAction(dialogPanel, "Acknowledge", ACCEPT_EXIT_CODE, isDefault = true),
CloseDialogAction(dialogPanel, "Cancel", REJECT_EXIT_CODE)
)
- })
+ }).apply { pack() }
}
companion object {
diff --git a/streaming/src/com/android/tools/idea/streaming/emulator/EmulatorToolWindowPanel.kt b/streaming/src/com/android/tools/idea/streaming/emulator/EmulatorToolWindowPanel.kt
index be210be..d453242 100644
--- a/streaming/src/com/android/tools/idea/streaming/emulator/EmulatorToolWindowPanel.kt
+++ b/streaming/src/com/android/tools/idea/streaming/emulator/EmulatorToolWindowPanel.kt
@@ -54,6 +54,7 @@
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.diagnostic.Logger
+import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.util.Disposer
@@ -78,6 +79,7 @@
* Provides view of one AVD in the Running Devices tool window.
*/
internal class EmulatorToolWindowPanel(
+ disposableParent: Disposable,
private val project: Project,
val emulator: EmulatorController
) : RunningDevicePanel(DeviceId.ofEmulator(emulator.emulatorId), EMULATOR_MAIN_TOOLBAR_ID, STREAMING_SECONDARY_TOOLBAR_ID),
@@ -147,6 +149,10 @@
@get:TestOnly
var lastUiState: EmulatorUiState? = null
+ init {
+ Disposer.register(disposableParent, this)
+ }
+
override fun setDeviceFrameVisible(visible: Boolean) {
primaryDisplayView?.deviceFrameVisible = visible
}
@@ -166,8 +172,14 @@
* Populates the emulator panel with content.
*/
override fun createContent(deviceFrameVisible: Boolean, savedUiState: UiState?) {
+ if (contentDisposable != null) {
+ thisLogger().error(IllegalStateException("${title}: content already exists"))
+ return
+ }
+
lastUiState = null
val disposable = Disposer.newDisposable()
+ Disposer.register(this, disposable)
contentDisposable = disposable
clipboardSynchronizer = EmulatorClipboardSynchronizer(emulator, disposable)
diff --git a/streaming/testSrc/com/android/tools/idea/streaming/core/DeviceFileDropHandlerTest.kt b/streaming/testSrc/com/android/tools/idea/streaming/core/DeviceFileDropHandlerTest.kt
index fd30906..f2ef68f 100644
--- a/streaming/testSrc/com/android/tools/idea/streaming/core/DeviceFileDropHandlerTest.kt
+++ b/streaming/testSrc/com/android/tools/idea/streaming/core/DeviceFileDropHandlerTest.kt
@@ -166,11 +166,8 @@
val emulators = catalog.updateNow().get()
assertThat(emulators).hasSize(1)
val emulatorController = emulators.first()
- val panel = EmulatorToolWindowPanel(projectRule.project, emulatorController)
+ val panel = EmulatorToolWindowPanel(testRootDisposable, projectRule.project, emulatorController)
Disposer.register(testRootDisposable) {
- if (panel.primaryEmulatorView != null) {
- panel.destroyContent()
- }
emulator.stop()
}
panel.zoomToolbarVisible = true
diff --git a/streaming/testSrc/com/android/tools/idea/streaming/core/StreamingToolWindowManagerTest.kt b/streaming/testSrc/com/android/tools/idea/streaming/core/StreamingToolWindowManagerTest.kt
index f0cd29c..3090c46 100644
--- a/streaming/testSrc/com/android/tools/idea/streaming/core/StreamingToolWindowManagerTest.kt
+++ b/streaming/testSrc/com/android/tools/idea/streaming/core/StreamingToolWindowManagerTest.kt
@@ -91,6 +91,7 @@
import com.intellij.util.ui.UIUtil.dispatchAllInvocationEvents
import icons.StudioIcons
import kotlinx.coroutines.runBlocking
+import org.jetbrains.android.UndisposedAndroidObjectsCheckerRule
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -299,7 +300,6 @@
@Test
fun testZoomStatePreservation() {
-
val tempFolder = emulatorRule.avdRoot
val emulator = emulatorRule.newEmulator(FakeEmulator.createPhoneAvd(tempFolder))
diff --git a/streaming/testSrc/com/android/tools/idea/streaming/device/DeviceToolWindowPanelTest.kt b/streaming/testSrc/com/android/tools/idea/streaming/device/DeviceToolWindowPanelTest.kt
index cd69fba..7e4f8a8 100644
--- a/streaming/testSrc/com/android/tools/idea/streaming/device/DeviceToolWindowPanelTest.kt
+++ b/streaming/testSrc/com/android/tools/idea/streaming/device/DeviceToolWindowPanelTest.kt
@@ -337,15 +337,7 @@
private fun createToolWindowPanel(): DeviceToolWindowPanel {
val deviceClient =
DeviceClient(testRootDisposable, device.serialNumber, device.handle, device.configuration, device.deviceState.cpuAbi, project)
- val panel = DeviceToolWindowPanel(project, deviceClient)
- // The panel has to be destroyed before disposal of DeviceClient.
- val disposable = Disposer.newDisposable()
- Disposer.register(testRootDisposable, disposable)
- Disposer.register(disposable) {
- if (panel.deviceView != null) {
- panel.destroyContent()
- }
- }
+ val panel = DeviceToolWindowPanel(testRootDisposable, project, deviceClient)
panel.size = Dimension(280, 300)
panel.zoomToolbarVisible = true
return panel
diff --git a/streaming/testSrc/com/android/tools/idea/streaming/device/DeviceViewTest.kt b/streaming/testSrc/com/android/tools/idea/streaming/device/DeviceViewTest.kt
index b78abc8..0d31f51 100644
--- a/streaming/testSrc/com/android/tools/idea/streaming/device/DeviceViewTest.kt
+++ b/streaming/testSrc/com/android/tools/idea/streaming/device/DeviceViewTest.kt
@@ -681,6 +681,7 @@
"\\s*build_api_level_full: \"30\"\n" +
"\\s*mdns_connection_type: MDNS_NONE\n" +
"\\s*device_provisioner_id: \"FakeDevicePlugin\"\n" +
+ "\\s*connection_id: \"fakeConnectionId\"\n" +
"}\n" +
"ide_brand: ANDROID_STUDIO\n" +
"idea_is_internal: \\w+\n" +
@@ -713,6 +714,7 @@
"\\s*build_api_level_full: \"30\"\n" +
"\\s*mdns_connection_type: MDNS_NONE\n" +
"\\s*device_provisioner_id: \"FakeDevicePlugin\"\n" +
+ "\\s*connection_id: \"fakeConnectionId\"\n" +
"}\n" +
"ide_brand: ANDROID_STUDIO\n" +
"idea_is_internal: \\w+\n" +
@@ -768,6 +770,7 @@
"\\s*build_api_level_full: \"30\"\n" +
"\\s*mdns_connection_type: MDNS_NONE\n" +
"\\s*device_provisioner_id: \"FakeDevicePlugin\"\n" +
+ "\\s*connection_id: \"fakeConnectionId\"\n" +
"}\n" +
"ide_brand: ANDROID_STUDIO\n" +
"idea_is_internal: \\w+\n" +
@@ -821,6 +824,7 @@
"\\s*build_api_level_full: \"30\"\n" +
"\\s*mdns_connection_type: MDNS_NONE\n" +
"\\s*device_provisioner_id: \"FakeDevicePlugin\"\n" +
+ "\\s*connection_id: \"fakeConnectionId\"\n" +
"}\n" +
"ide_brand: ANDROID_STUDIO\n" +
"idea_is_internal: \\w+\n" +
diff --git a/streaming/testSrc/com/android/tools/idea/streaming/emulator/EmulatorToolWindowPanelTest.kt b/streaming/testSrc/com/android/tools/idea/streaming/emulator/EmulatorToolWindowPanelTest.kt
index d8c4f47..7cf53c4 100644
--- a/streaming/testSrc/com/android/tools/idea/streaming/emulator/EmulatorToolWindowPanelTest.kt
+++ b/streaming/testSrc/com/android/tools/idea/streaming/emulator/EmulatorToolWindowPanelTest.kt
@@ -813,13 +813,7 @@
val emulators = catalog.updateNow().get()
assertThat(emulators).hasSize(1)
val emulatorController = emulators.first()
- val panel = EmulatorToolWindowPanel(projectRule.project, emulatorController)
- Disposer.register(testRootDisposable) {
- if (panel.primaryEmulatorView != null) {
- panel.destroyContent()
- }
- emulator.stop()
- }
+ val panel = EmulatorToolWindowPanel(testRootDisposable, projectRule.project, emulatorController)
panel.zoomToolbarVisible = true
waitForCondition(5, SECONDS) { emulatorController.connectionState == EmulatorController.ConnectionState.CONNECTED }
return panel
diff --git a/streaming/testUtil/com/android/tools/idea/streaming/device/FakeScreenSharingAgent.kt b/streaming/testUtil/com/android/tools/idea/streaming/device/FakeScreenSharingAgent.kt
index 25cb4b7..f41b980 100644
--- a/streaming/testUtil/com/android/tools/idea/streaming/device/FakeScreenSharingAgent.kt
+++ b/streaming/testUtil/com/android/tools/idea/streaming/device/FakeScreenSharingAgent.kt
@@ -756,7 +756,7 @@
val supportedStates = """
Supported states: [
DeviceState{identifier=0, name='CLOSE', app_accessible=true},
- DeviceState{identifier=1, name='TENT', app_accessible=true},
+ DeviceState{identifier=1, name='TENT'},
DeviceState{identifier=2, name='HALF_FOLDED', app_accessible=true},
DeviceState{identifier=3, name='OPEN', app_accessible=true},
DeviceState{identifier=4, name='REAR_DISPLAY_STATE', app_accessible=true},
diff --git a/streaming/testUtil/com/android/tools/idea/streaming/device/FakeScreenSharingAgentRule.kt b/streaming/testUtil/com/android/tools/idea/streaming/device/FakeScreenSharingAgentRule.kt
index d395f7a..68636e4 100644
--- a/streaming/testUtil/com/android/tools/idea/streaming/device/FakeScreenSharingAgentRule.kt
+++ b/streaming/testUtil/com/android/tools/idea/streaming/device/FakeScreenSharingAgentRule.kt
@@ -188,7 +188,7 @@
private fun createDeviceProperties(): DeviceProperties {
return DeviceProperties.build {
readCommonProperties(deviceState.properties)
- populateDeviceInfoProto("FakeDevicePlugin", serialNumber, deviceState.properties)
+ populateDeviceInfoProto("FakeDevicePlugin", serialNumber, deviceState.properties, "fakeConnectionId")
readAdbSerialNumber(serialNumber)
icon = when (deviceType) {
DeviceType.WEAR -> StudioIcons.DeviceExplorer.PHYSICAL_DEVICE_WEAR
diff --git a/studio/BUILD b/studio/BUILD
index 7f7678b..01a57f7 100644
--- a/studio/BUILD
+++ b/studio/BUILD
@@ -173,6 +173,7 @@
"//tools/adt/idea/app-inspection/inspectors/backgroundtask/view:app-inspection.inspectors.backgroundtask.view",
"//tools/adt/idea/app-quality-insights/api:intellij.android.app-quality-insights.api",
"//tools/adt/idea/app-quality-insights/ide:intellij.android.app-quality-insights.ide",
+ "//tools/adt/idea/app-quality-insights/ide/gradle:intellij.android.app-quality-insights.ide.gradle",
"//tools/adt/idea/app-quality-insights/ui:intellij.android.app-quality-insights.ui",
"//tools/adt/idea/app-quality-insights/play-vitals/model:intellij.android.app-quality-insights.play-vitals.model",
"//tools/adt/idea/app-quality-insights/play-vitals/ide:intellij.android.app-quality-insights.play-vitals.ide",
diff --git a/studio/version.bzl b/studio/version.bzl
index 86abda5..018bab1 100644
--- a/studio/version.bzl
+++ b/studio/version.bzl
@@ -3,5 +3,5 @@
STUDIO_CODENAME = "Iguana"
STUDIO_VERSION = "Canary"
-STUDIO_MICRO_PATCH = "1.4"
-STUDIO_RELEASE_NUMBER = 4
+STUDIO_MICRO_PATCH = "1.5"
+STUDIO_RELEASE_NUMBER = 5
diff --git a/whats-new-assistant/testSrc/com.android.tools.idea.whatsnew.assistant/WhatsNewBundleCreatorTest.kt b/whats-new-assistant/testSrc/com.android.tools.idea.whatsnew.assistant/WhatsNewBundleCreatorTest.kt
index 541cb77..1a98a2f 100644
--- a/whats-new-assistant/testSrc/com.android.tools.idea.whatsnew.assistant/WhatsNewBundleCreatorTest.kt
+++ b/whats-new-assistant/testSrc/com.android.tools.idea.whatsnew.assistant/WhatsNewBundleCreatorTest.kt
@@ -19,39 +19,48 @@
import com.android.testutils.MockitoKt.whenever
import com.android.testutils.TestUtils
import com.android.tools.idea.assistant.AssistantBundleCreator
+import com.android.tools.idea.testing.AndroidProjectRule
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.util.io.FileUtil
-import org.jetbrains.android.AndroidTestCase
-import org.junit.Test
-import org.mockito.ArgumentMatchers
-import org.mockito.Mockito.mock
-import org.mockito.stubbing.Answer
-import java.io.File
-import java.io.InputStream
import java.net.URL
import java.nio.file.Path
import java.util.concurrent.TimeoutException
+import org.jetbrains.android.AndroidTestBase
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.mock
-class WhatsNewBundleCreatorTest : AndroidTestCase() {
+@RunWith(JUnit4::class)
+class WhatsNewBundleCreatorTest {
+ @get:Rule var projectRule = AndroidProjectRule.inMemory()
+
private lateinit var mockUrlProvider: WhatsNewURLProvider
private lateinit var localPath: Path
private val studioRevision = Revision.parseRevision("3.3.0")
- override fun setUp() {
- super.setUp()
-
+ @Before
+ fun setUp() {
// Mock url provider to simulate webserver and also class resource file
mockUrlProvider = mock(WhatsNewURLProvider::class.java)
- val serverFile = File(myFixture.testDataPath).resolve("whatsnewassistant/server-3.3.0.xml")
- whenever(mockUrlProvider.getWebConfig(ArgumentMatchers.anyString())).thenReturn(URL("file:" + serverFile.path))
+ val serverFile = getTestDataPath().resolve("whatsnewassistant/server-3.3.0.xml")
+ whenever(mockUrlProvider.getWebConfig(ArgumentMatchers.anyString()))
+ .thenReturn(URL("file:$serverFile"))
- val resourceFile = File(myFixture.testDataPath).resolve("whatsnewassistant/defaultresource-3.3.0.xml")
- whenever(mockUrlProvider.getResourceFileAsStream(ArgumentMatchers.any(), ArgumentMatchers.anyString()))
- .thenAnswer(Answer<InputStream> {
- URL("file:" + resourceFile.path).openStream()
- })
+ val resourceFile = getTestDataPath().resolve("whatsnewassistant/defaultresource-3.3.0.xml")
+ whenever(
+ mockUrlProvider.getResourceFileAsStream(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.anyString()
+ )
+ )
+ .thenAnswer { URL("file:$resourceFile").openStream() }
val tmpDir = TestUtils.createTempDirDeletedOnExit()
localPath = tmpDir.resolve("local-3.3.0.xml")
@@ -59,7 +68,7 @@
}
@Test
- fun testEnabled() {
+ fun enabled() {
val mockBundler = mock(AssistantBundleCreator::class.java)
whenever(mockBundler.bundleId).thenReturn(WhatsNewBundleCreator.BUNDLE_ID)
whenever(mockBundler.config).thenReturn(URL("file:test.file"))
@@ -70,9 +79,7 @@
WhatsNewBundleCreator.setTestCreator(null)
}
- /**
- * Test with a file that exists, simulating good internet connection
- */
+ /** Test with a file that exists, simulating good internet connection */
@Test
fun testDownloadSuccess() {
// Expected bundle file is server-3.3.0.xml
@@ -86,13 +93,13 @@
}
/**
- * Test with a file that does not exist, simulating no internet, and also
- * without an already downloaded/unpacked file, so the bundle file will
- * be from the class resource
+ * Test with a file that does not exist, simulating no internet, and also without an already
+ * downloaded/unpacked file, so the bundle file will be from the class resource
*/
@Test
- fun testDownloadDoesNotExist() {
- whenever(mockUrlProvider.getWebConfig(ArgumentMatchers.anyString())).thenReturn(URL("file:server-doesnotexist-3.3.0.xml"))
+ fun downloadDoesNotExist() {
+ whenever(mockUrlProvider.getWebConfig(ArgumentMatchers.anyString()))
+ .thenReturn(URL("file:server-doesnotexist-3.3.0.xml"))
// Expected bundle file is defaultresource-3.3.0.xml
val bundleCreator = WhatsNewBundleCreator(mockUrlProvider, studioRevision)
@@ -105,11 +112,11 @@
}
/**
- * First test a downloaded file, then with one that doesn't exist, simulating
- * losing internet connection after having it earlier
+ * First test a downloaded file, then with one that doesn't exist, simulating losing internet
+ * connection after having it earlier
*/
@Test
- fun testDownloadDoesNotExistWithExistingDownloaded() {
+ fun downloadDoesNotExistWithExistingDownloaded() {
// First expected bundle file is server-3.3.0.xml
val bundleCreator = WhatsNewBundleCreator(mockUrlProvider, studioRevision)
val bundle = bundleCreator.getBundle(ProjectManager.getInstance().defaultProject)
@@ -120,7 +127,8 @@
}
// Change server file to one that doesn't exist, meaning no connection
- whenever(mockUrlProvider.getWebConfig(ArgumentMatchers.anyString())).thenReturn(URL("file:server-doesnotexist-3.3.0.xml"))
+ whenever(mockUrlProvider.getWebConfig(ArgumentMatchers.anyString()))
+ .thenReturn(URL("file:server-doesnotexist-3.3.0.xml"))
// Expected bundle file is still server-3.3.0.xml because it was downloaded on the first fetch
val newBundle = bundleCreator.getBundle(ProjectManager.getInstance().defaultProject)
assertNotNull(newBundle)
@@ -130,14 +138,12 @@
}
}
- /**
- * Test that disabling the download flag will not fetch from "server"
- */
+ /** Test that disabling the download flag will not fetch from "server" */
@Test
- fun testDownloadFlagDisabled() {
+ fun downloadFlagDisabled() {
// Expected bundle file is defaultresource-3.3.0.xml
- val bundleCreator = WhatsNewBundleCreator(mockUrlProvider, studioRevision,
- WhatsNewConnectionOpener(), false)
+ val bundleCreator =
+ WhatsNewBundleCreator(mockUrlProvider, studioRevision, WhatsNewConnectionOpener(), false)
val bundle = bundleCreator.getBundle(ProjectManager.getInstance().defaultProject)
assertNotNull(bundle)
if (bundle != null) {
@@ -147,12 +153,16 @@
}
@Test
- fun testDownloadTimeout() {
+ fun downloadTimeout() {
val mockConnectionOpener = mock(WhatsNewConnectionOpener::class.java)
- whenever(mockConnectionOpener.openConnection(ArgumentMatchers.isNotNull<URL>(), ArgumentMatchers.anyInt())).thenThrow(TimeoutException())
+ whenever(
+ mockConnectionOpener.openConnection(ArgumentMatchers.isNotNull(), ArgumentMatchers.anyInt())
+ )
+ .thenThrow(TimeoutException())
// Expected bundle file is defaultresource-3.3.0.xml
- val bundleCreator = WhatsNewBundleCreator(mockUrlProvider, studioRevision, mockConnectionOpener, true)
+ val bundleCreator =
+ WhatsNewBundleCreator(mockUrlProvider, studioRevision, mockConnectionOpener, true)
val bundle = bundleCreator.getBundle(ProjectManager.getInstance().defaultProject)
assertNotNull(bundle)
if (bundle != null) {
@@ -161,16 +171,14 @@
}
}
- /**
- * Test that parseBundle correctly deletes local cache and retries once when the parse fails
- */
+ /** Test that parseBundle correctly deletes local cache and retries once when the parse fails */
@Test
- fun testParseBundleRetry() {
+ fun parseBundleRetry() {
// Trying to read empty xml will cause parser to throw exception...
- val emptyFile = File(myFixture.testDataPath).resolve("whatsnewassistant/empty.xml")
- FileUtil.copy(emptyFile, localPath.toFile())
- val bundleCreator = WhatsNewBundleCreator(mockUrlProvider, studioRevision,
- WhatsNewConnectionOpener(), false)
+ val emptyFile = getTestDataPath().resolve("whatsnewassistant/empty.xml")
+ FileUtil.copy(emptyFile.toFile(), localPath.toFile())
+ val bundleCreator =
+ WhatsNewBundleCreator(mockUrlProvider, studioRevision, WhatsNewConnectionOpener(), false)
// So parseBundle should delete the empty file and retry, resulting in defaultresource-3.3.0.xml
val bundle = bundleCreator.getBundle(ProjectManager.getInstance().defaultProject)
@@ -182,14 +190,15 @@
}
/**
- * Test that WNA bundle creator correctly identifies when an updated config has
- * a higher version field than the previous existing config
+ * Test that WNA bundle creator correctly identifies when an updated config has a higher version
+ * field than the previous existing config
*/
@Test
- fun testNewConfigVersion() {
- // Since download is disabled, the current file will be defaultresource-3.3.0.xml, version "3.3.0"
- val bundleCreator = WhatsNewBundleCreator(mockUrlProvider, studioRevision,
- WhatsNewConnectionOpener(), false)
+ fun newConfigVersion() {
+ // Since download is disabled, the current file will be defaultresource-3.3.0.xml, version
+ // "3.3.0"
+ val bundleCreator =
+ WhatsNewBundleCreator(mockUrlProvider, studioRevision, WhatsNewConnectionOpener(), false)
// Disabled download means last seen version is from default resource, so "3.3.0" = "3.3.0"
assertFalse(bundleCreator.isNewConfigVersion)
@@ -201,15 +210,16 @@
// And running once again should be false because last seen is now "3.3.10"
assertFalse(bundleCreator.isNewConfigVersion)
- // Disabling download again should use local-3.3.0.xml, cached from the download, version "3.3.10"
+ // Disabling download again should use local-3.3.0.xml, cached from the download, version
+ // "3.3.10"
bundleCreator.setAllowDownload(false)
assertFalse(bundleCreator.isNewConfigVersion)
}
@Test
- fun testHasResourceConfig() {
- val bundleCreator = WhatsNewBundleCreator(mockUrlProvider, studioRevision,
- WhatsNewConnectionOpener(), false)
+ fun hasResourceConfig() {
+ val bundleCreator =
+ WhatsNewBundleCreator(mockUrlProvider, studioRevision, WhatsNewConnectionOpener(), false)
// Both the resource file and the Studio version are 3.3.0
assertTrue(bundleCreator.hasResourceConfig())
@@ -222,4 +232,8 @@
bundleCreator.setStudioRevision(Revision.parseRevision("0.0.0rc0"))
assertTrue(bundleCreator.hasResourceConfig())
}
+
+ private fun getTestDataPath(): Path {
+ return Path.of(AndroidTestBase.getTestDataPath())
+ }
}