IDEA-377018: add an ability to use different splash screens depending on subscription

GitOrigin-RevId: 396dacfc24e9ab814f2e9d6645c0a4ae938999b7
This commit is contained in:
Timur Aliberdov
2025-09-19 12:48:39 +00:00
committed by intellij-monorepo-bot
parent e12247fcff
commit c7fde2ed8c
4 changed files with 159 additions and 7 deletions
@@ -7,6 +7,7 @@ import com.intellij.diagnostic.StartUpMeasurer;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.application.IdeUrlTrackingParametersProvider;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.application.ex.ApplicationInfoEx;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.diagnostic.Logger;
@@ -20,6 +21,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
@@ -34,6 +37,7 @@ import java.util.*;
public final class ApplicationInfoImpl extends ApplicationInfoEx {
public static final String DEFAULT_PLUGINS_HOST = "https://plugins.jetbrains.com";
public static final String IDEA_PLUGINS_HOST_PROPERTY = "idea.plugins.host";
public static final String FREE_MODE_SPLASH_MARKER_FILE_NAME = "splash-free-mode.txt";
private static final String IDEA_APPLICATION_INFO_DEFAULT_DARK_LAF = "idea.application.info.default.dark.laf";
private static final String IDEA_APPLICATION_INFO_DEFAULT_CLASSIC_DARK_LAF = "idea.application.info.default.classic.dark.laf";
@@ -57,6 +61,7 @@ public final class ApplicationInfoImpl extends ApplicationInfoEx {
private String myShortCompanyName;
private String myCompanyUrl = "https://www.jetbrains.com/";
private @Nullable String splashImageUrl;
private @Nullable String freeModeSplashImageUrl;
private @Nullable String eapSplashImageUrl;
private String svgIconUrl;
private String mySvgEapIconUrl;
@@ -144,6 +149,11 @@ public final class ApplicationInfoImpl extends ApplicationInfoEx {
}
break;
case "logo-free-mode": {
freeModeSplashImageUrl = getAttributeValue(child, "url");
}
break;
case "icon": {
svgIconUrl = child.getAttributeValue("svg");
mySmallSvgIconUrl = child.getAttributeValue("svg-small");
@@ -432,7 +442,16 @@ public final class ApplicationInfoImpl extends ApplicationInfoEx {
@Override
public @Nullable String getSplashImageUrl() {
return isEap && eapSplashImageUrl != null ? eapSplashImageUrl : splashImageUrl;
if (isEap && eapSplashImageUrl != null) return eapSplashImageUrl;
if (freeModeSplashImageUrl != null) {
Path markerFile = PathManager.getConfigDir().resolve(FREE_MODE_SPLASH_MARKER_FILE_NAME);
if (Files.exists(markerFile)) {
return freeModeSplashImageUrl;
}
}
return splashImageUrl;
}
@Override
@@ -9,6 +9,7 @@
<xs:element type="buildType" name="build"/>
<xs:element type="logoType" name="logo"/>
<xs:element type="logoType" name="logo-eap" minOccurs="0"/>
<xs:element type="logoType" name="logo-free-mode" minOccurs="0"/>
<xs:element type="iconType" name="icon"/>
<xs:element type="iconEapType" name="icon-eap" minOccurs="0"/>
<xs:element type="namesType" name="names"/>
@@ -1,15 +1,20 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.application.impl;
import com.intellij.openapi.application.ApplicationInfo;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.util.xml.dom.XmlElement;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static com.intellij.openapi.application.impl.UtilKt.withTempConfigDirectory;
import static org.assertj.core.api.Assertions.assertThat;
public class ApplicationInfoTest {
@@ -31,10 +36,114 @@ public class ApplicationInfoTest {
});
}
@Test
public void splashImage_eapLogoPreferredInEap() {
ApplicationInfo info = createAppInfo(eap(true), logo, logoEap);
assertThat(info.isEAP()).isTrue();
String url = info.getSplashImageUrl();
assertThat(url).isEqualTo(logoEapUrl);
}
@Test
public void splashImage_regularLogoWhenNotEap() {
ApplicationInfo info = createAppInfo(eap(false), logo, logoEap);
assertThat(info.isEAP()).isFalse();
String url = info.getSplashImageUrl();
assertThat(url).isEqualTo(logoUrl);
}
@Test
public void splashImage_regularLogoInEapWithNoEapLogoProvided() {
ApplicationInfo info = createAppInfo(eap(true), logo);
assertThat(info.isEAP()).isTrue();
String url = info.getSplashImageUrl();
assertThat(url).isEqualTo(logoUrl);
}
@Test
public void splashImage_freeModeLogoPreferredWhenMarkerPresent() {
withTempConfigDirectory(configDir -> {
createFreeModeSplashMarkerFile(configDir);
ApplicationInfo info = createAppInfo(logo, logoFreeMode);
String url = info.getSplashImageUrl();
assertThat(url).isEqualTo(logoFreeModeUrl);
});
}
@Test
public void splashImage_regularLogoWhenNoFreeModeMarker() {
withTempConfigDirectory(configDir -> {
deleteFreeModeSplashMarkerFile(configDir);
ApplicationInfo info = createAppInfo(logo, logoFreeMode);
String url = info.getSplashImageUrl();
assertThat(url).isEqualTo(logoUrl);
});
}
@Test
public void splashImage_regularLogoWhenMarkerPresentButNoFreeModeLogoProvided() {
withTempConfigDirectory(configDir -> {
createFreeModeSplashMarkerFile(configDir);
ApplicationInfo info = createAppInfo(logo);
String url = info.getSplashImageUrl();
assertThat(url).isEqualTo(logoUrl);
});
}
@Test
public void splashImage_eapLogoBeatsFreeModeInEapWhenBothAvailableAndMarkerPresent() {
withTempConfigDirectory(configDir -> {
createFreeModeSplashMarkerFile(configDir);
ApplicationInfo info = createAppInfo(eap(true), logo, logoEap, logoFreeMode);
String url = info.getSplashImageUrl();
assertThat(url).isEqualTo(logoEapUrl);
});
}
@Test
public void splashImage_freeModeLogoWhenNotEapAndMarkerPresent() {
withTempConfigDirectory(configDir -> {
createFreeModeSplashMarkerFile(configDir);
ApplicationInfo info = createAppInfo(eap(false), logo, logoEap, logoFreeMode);
String url = info.getSplashImageUrl();
assertThat(url).isEqualTo(logoFreeModeUrl);
});
}
@Test
public void splashImage_regularLogoWhenNotEapAndNoMarker() {
withTempConfigDirectory(configDir -> {
deleteFreeModeSplashMarkerFile(configDir);
ApplicationInfo info = createAppInfo(eap(false), logo, logoEap, logoFreeMode);
String url = info.getSplashImageUrl();
assertThat(url).isEqualTo(logoUrl);
});
}
public static @NotNull ApplicationInfoImpl createAppInfo(@NotNull XmlElement @NotNull ... content) {
var children = new ArrayList<>(List.of(content));
children.add(new XmlElement("icon", Map.of("svg", "xxx.svg", "svg-small", "xxx.svg"), List.of(), null));
children.add(new XmlElement("plugins", Map.of(), List.of(), null));
return new ApplicationInfoImpl(new XmlElement("state", Map.of(), children, null));
}
private static void createFreeModeSplashMarkerFile(@NotNull Path configDir) throws IOException {
Files.writeString(configDir.resolve(ApplicationInfoImpl.FREE_MODE_SPLASH_MARKER_FILE_NAME), "");
}
private static void deleteFreeModeSplashMarkerFile(@NotNull Path configDir) throws IOException {
Files.deleteIfExists(configDir.resolve(ApplicationInfoImpl.FREE_MODE_SPLASH_MARKER_FILE_NAME));
}
private static XmlElement eap(boolean isEap) {
return new XmlElement("version", Map.of("eap", String.valueOf(isEap)), List.of(), null);
}
private static final String logoUrl = "/logo.png";
private static final String logoEapUrl = "/logo_eap.png";
private static final String logoFreeModeUrl = "/logo_free.png";
private final XmlElement logo = new XmlElement("logo", Map.of("url", logoUrl), List.of(), null);
private final XmlElement logoEap = new XmlElement("logo-eap", Map.of("url", logoEapUrl), List.of(), null);
private final XmlElement logoFreeMode = new XmlElement("logo-free-mode", Map.of("url", logoFreeModeUrl), List.of(), null);
}
@@ -2,19 +2,19 @@
package com.intellij.openapi.application.impl
import com.intellij.concurrency.ContextAwareRunnable
import com.intellij.openapi.application.Application
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.application.*
import com.intellij.testFramework.LeakHunter
import com.intellij.testFramework.utils.io.deleteRecursively
import com.intellij.util.ui.EDT
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.function.ThrowingConsumer
import java.nio.file.Path
import java.util.function.Supplier
import javax.swing.SwingUtilities
import kotlin.coroutines.resume
import kotlin.io.path.createTempDirectory
import kotlin.test.assertFalse
fun Application.withModality(action: () -> Unit) {
@@ -64,3 +64,26 @@ suspend fun pumpEDT() {
internal suspend fun processApplicationQueue() {
withContext(Dispatchers.EDT + ModalityState.any().asContextElement()) {}
}
internal fun withTempDirectory(block: ThrowingConsumer<Path>) {
val tempDirectory = createTempDirectory()
try {
block.accept(tempDirectory)
}
finally {
tempDirectory.deleteRecursively()
}
}
internal fun withTempConfigDirectory(block: ThrowingConsumer<Path>) {
withTempDirectory { tempDirectory ->
val oldConfigDir = PathManager.getConfigDir()
try {
PathManager.setExplicitConfigPath(tempDirectory)
block.accept(tempDirectory)
}
finally {
PathManager.setExplicitConfigPath(oldConfigDir);
}
}
}