Show a warning for projects with kts build files

Android Studio is able to open projects with kts Gradle build files but
some functionality is not available for them. This CL causes a warning
to appear every time a project with kts build files is opened or the
first time it is synced and it contains a kts file.

Test: New tests added to confirm warning is shown when appropriate.
Bug: 130226195
Change-Id: Id2e3bd6103364499e8351ca083a623b36fb384f5
diff --git a/android/src/META-INF/android-plugin.xml b/android/src/META-INF/android-plugin.xml
index 567a2db..a782204 100644
--- a/android/src/META-INF/android-plugin.xml
+++ b/android/src/META-INF/android-plugin.xml
@@ -576,6 +576,7 @@
     <projectService serviceImplementation="com.android.tools.idea.gradle.project.sync.GradleFiles"/>
     <projectService serviceImplementation="com.android.tools.idea.gradle.project.build.GradleBuildState"/>
     <projectService serviceImplementation="com.android.tools.idea.project.AndroidNotification"/>
+    <projectService serviceImplementation="com.android.tools.idea.project.AndroidKtsSupportNotification"/>
     <projectService serviceImplementation="com.android.tools.idea.project.IndexingSuspender"/>
     <projectService serviceImplementation="com.android.tools.idea.gradle.project.sync.setup.module.common.DependencySetupIssues"/>
     <projectService serviceImplementation="com.android.tools.idea.gradle.project.sync.messages.GradleSyncMessages"/>
@@ -856,6 +857,7 @@
     <postSyncProjectSetupStep implementation="com.android.tools.idea.gradle.project.sync.setup.post.project.SdkToolsVersionSetupStep"/>
     <postSyncProjectSetupStep implementation="com.android.tools.idea.gradle.project.sync.setup.post.project.ExpiredPreviewBuildSetupStep"/>
     <postSyncProjectSetupStep implementation="com.android.tools.idea.gradle.project.sync.setup.post.project.IgnoredBuildScriptSetupStep"/>
+    <postSyncProjectSetupStep implementation="com.android.tools.idea.gradle.project.sync.setup.post.project.GradleKtsBuildFilesWarningStep"/>
 
     <postSyncModuleSetupStep implementation="com.android.tools.idea.gradle.project.sync.setup.post.module.TestArtifactSearchScopeSetupStep"/>
     <postSyncModuleSetupStep implementation="com.android.tools.idea.gradle.project.sync.setup.post.module.AndroidRunConfigurationSetupStep"/>
diff --git a/android/src/com/android/tools/idea/gradle/project/sync/setup/post/project/GradleKtsBuildFilesWarningStep.java b/android/src/com/android/tools/idea/gradle/project/sync/setup/post/project/GradleKtsBuildFilesWarningStep.java
new file mode 100644
index 0000000..9da01a4
--- /dev/null
+++ b/android/src/com/android/tools/idea/gradle/project/sync/setup/post/project/GradleKtsBuildFilesWarningStep.java
@@ -0,0 +1,39 @@
+/*
+ * 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.gradle.project.sync.setup.post.project;
+
+import com.android.tools.idea.gradle.project.sync.setup.post.ProjectSetupStep;
+import com.android.tools.idea.gradle.util.GradleUtil;
+import com.android.tools.idea.project.AndroidKtsSupportNotification;
+import com.google.common.annotations.VisibleForTesting;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class GradleKtsBuildFilesWarningStep extends ProjectSetupStep {
+  @Override
+  public void setUpProject(@NotNull Project project, @Nullable ProgressIndicator indicator) {
+    doSetUpProject(project, GradleUtil.hasKtsBuildFiles(project));
+  }
+
+  @VisibleForTesting
+  void doSetUpProject(@NotNull Project project, boolean hasKts) {
+    if (hasKts) {
+      AndroidKtsSupportNotification.getInstance(project).showWarningIfNotShown();
+    }
+  }
+}
diff --git a/android/src/com/android/tools/idea/gradle/util/GradleUtil.java b/android/src/com/android/tools/idea/gradle/util/GradleUtil.java
index 87a40bb..f57fdfd 100644
--- a/android/src/com/android/tools/idea/gradle/util/GradleUtil.java
+++ b/android/src/com/android/tools/idea/gradle/util/GradleUtil.java
@@ -1030,6 +1030,10 @@
     return result;
   }
 
+  public static boolean hasKtsBuildFiles(@NotNull Project project) {
+    return projectBuildFilesTypes(project).contains(DOT_KTS);
+  }
+
   private static void addBuildFileType(@NotNull HashSet<String> result, @Nullable VirtualFile buildFile) {
     if (buildFile != null) {
       String buildFileExtension = buildFile.getExtension();
diff --git a/android/src/com/android/tools/idea/project/AndroidKtsSupportNotification.java b/android/src/com/android/tools/idea/project/AndroidKtsSupportNotification.java
new file mode 100644
index 0000000..1fdd0f0
--- /dev/null
+++ b/android/src/com/android/tools/idea/project/AndroidKtsSupportNotification.java
@@ -0,0 +1,69 @@
+/*
+ * 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.project;
+
+import static com.intellij.notification.NotificationDisplayType.NONE;
+import static com.intellij.notification.NotificationType.WARNING;
+
+import com.android.tools.idea.project.hyperlink.NotificationHyperlink;
+import com.intellij.notification.NotificationGroup;
+import com.intellij.notification.NotificationsConfiguration;
+import com.intellij.notification.impl.NotificationSettings;
+import com.intellij.notification.impl.NotificationsConfigurationImpl;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+
+public class AndroidKtsSupportNotification {
+  public static final String KTS_WARNING_MSG = "This project uses Gradle KTS build files which are not fully supported. Some functions may be affected.";
+  public static final String KTS_WARNING_TITLE = "Gradle KTS Build Files";
+  public static final NotificationGroup KTS_NOTIFICATION_GROUP = NotificationGroup.balloonGroup("Gradle KTS build files");
+
+  @NotNull private final Project myProject;
+  private boolean alreadyShown;
+
+  @NotNull
+  public static AndroidKtsSupportNotification getInstance(@NotNull Project project) {
+    return ServiceManager.getService(project, AndroidKtsSupportNotification.class);
+  }
+
+  public AndroidKtsSupportNotification(@NotNull Project project) {
+    myProject = project;
+    alreadyShown = false;
+  }
+
+  public void showWarningIfNotShown() {
+    if (!alreadyShown) {
+      AndroidNotification.getInstance(myProject).showBalloon(KTS_WARNING_TITLE, KTS_WARNING_MSG, WARNING, KTS_NOTIFICATION_GROUP,
+                                                             new DisableAndroidKtsNotificationHyperlink());
+      // Make sure that it was displayed, otherwise notification will not show until project is reopened.
+      NotificationSettings settings = NotificationsConfigurationImpl.getSettings(KTS_NOTIFICATION_GROUP.getDisplayId());
+      alreadyShown = settings.getDisplayType() != NONE || settings.isShouldLog();
+    }
+  }
+
+  public static class DisableAndroidKtsNotificationHyperlink extends NotificationHyperlink {
+
+    protected DisableAndroidKtsNotificationHyperlink() {
+      super("disableKtsNotification", "Disable this warning");
+    }
+
+    @Override
+    protected void execute(@NotNull Project project) {
+      NotificationsConfiguration.getNotificationsConfiguration().changeSettings(KTS_NOTIFICATION_GROUP.getDisplayId(), NONE, false, false);
+    }
+  }
+}
diff --git a/android/testSrc/com/android/tools/idea/gradle/project/sync/setup/post/project/GradleKtsBuildFilesWarningStepTest.java b/android/testSrc/com/android/tools/idea/gradle/project/sync/setup/post/project/GradleKtsBuildFilesWarningStepTest.java
new file mode 100644
index 0000000..8841a04
--- /dev/null
+++ b/android/testSrc/com/android/tools/idea/gradle/project/sync/setup/post/project/GradleKtsBuildFilesWarningStepTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.gradle.project.sync.setup.post.project;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.tools.idea.project.AndroidKtsSupportNotification;
+import com.android.tools.idea.testing.IdeComponents;
+import com.intellij.testFramework.IdeaTestCase;
+import org.jetbrains.annotations.NotNull;
+import org.mockito.Mock;
+
+/**
+ * Tests for {@link GradleKtsBuildFilesWarningStep}
+ */
+public class GradleKtsBuildFilesWarningStepTest extends IdeaTestCase {
+
+  @Mock private AndroidKtsSupportNotification myNotification;
+  @NotNull private GradleKtsBuildFilesWarningStep myWarningStep = new GradleKtsBuildFilesWarningStep();
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    initMocks(this);
+    new IdeComponents(myProject).replaceProjectService(AndroidKtsSupportNotification.class, myNotification);
+  }
+
+  /**
+   * Check that a warning is created when the project uses kts build files
+   */
+  public void testDoSetUpProjectWithKts() {
+    myWarningStep.doSetUpProject(myProject, true);
+    verify(myNotification).showWarningIfNotShown();
+  }
+
+  /**
+   * Check that a warning is *NOT* created when the project does not use kts build files
+   */
+  public void testDoSetUpProjectWithoutKts() {
+    myWarningStep.doSetUpProject(myProject, false);
+    verify(myNotification, never()).showWarningIfNotShown();
+  }
+}
diff --git a/android/testSrc/com/android/tools/idea/project/AndroidKtsSupportNotificationTest.java b/android/testSrc/com/android/tools/idea/project/AndroidKtsSupportNotificationTest.java
new file mode 100644
index 0000000..a44f3db
--- /dev/null
+++ b/android/testSrc/com/android/tools/idea/project/AndroidKtsSupportNotificationTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.project;
+
+import static com.android.tools.idea.project.AndroidKtsSupportNotification.KTS_NOTIFICATION_GROUP;
+import static com.android.tools.idea.project.AndroidKtsSupportNotification.KTS_WARNING_MSG;
+import static com.android.tools.idea.project.AndroidKtsSupportNotification.KTS_WARNING_TITLE;
+import static com.google.common.truth.Truth.assertThat;
+import static com.intellij.notification.NotificationType.WARNING;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.tools.idea.project.AndroidKtsSupportNotification.DisableAndroidKtsNotificationHyperlink;
+import com.android.tools.idea.project.hyperlink.NotificationHyperlink;
+import com.android.tools.idea.testing.IdeComponents;
+import com.intellij.testFramework.IdeaTestCase;
+import java.util.List;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+/**
+ * Tests for {@link AndroidKtsSupportNotification}
+ */
+public class AndroidKtsSupportNotificationTest extends IdeaTestCase {
+  @Mock private AndroidNotification myAndroidNotification;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    initMocks(this);
+    new IdeComponents(myProject).replaceProjectService(AndroidNotification.class, myAndroidNotification);
+  }
+
+  /**
+   * Verify an AndroidNotification is created with the expected parameters
+   */
+  public void testShowWarningIfNotShown() {
+    AndroidKtsSupportNotification myKtsNotification = new AndroidKtsSupportNotification(myProject);
+    myKtsNotification.showWarningIfNotShown();
+    verifyBalloon();
+  }
+
+  /**
+   * Verify that warning is generated only once even when called multiple times
+   */
+  public void testShowWarningIfNotShownTwice() {
+    AndroidKtsSupportNotification myKtsNotification = new AndroidKtsSupportNotification(myProject);
+    myKtsNotification.showWarningIfNotShown();
+    myKtsNotification.showWarningIfNotShown();
+    verifyBalloon();
+  }
+
+  private void verifyBalloon() {
+    ArgumentCaptor<NotificationHyperlink> hyperlinkCaptor = ArgumentCaptor.forClass(NotificationHyperlink.class);
+    verify(myAndroidNotification, times(1)).showBalloon(same(KTS_WARNING_TITLE), same(KTS_WARNING_MSG), same(WARNING), same(KTS_NOTIFICATION_GROUP), hyperlinkCaptor.capture());
+    List<NotificationHyperlink> hyperlinks = hyperlinkCaptor.getAllValues();
+    assertThat(hyperlinks).hasSize(1);
+    assertThat(hyperlinks.get(0)).isInstanceOf(DisableAndroidKtsNotificationHyperlink.class);
+  }
+}