@@ -214,10 +214,23 @@ static const char *MISMATCHED_VERSIONS_MESSAGE = "Android build version mismatch
214
214
215
215
static const char *GDEXTENSION_LIBS_PATH = " libs/gdextensionlibs.json" ;
216
216
217
+ // This template string must always match the content of 'platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml'.
218
+ static const String ICON_XML_TEMPLATE =
219
+ " <?xml version=\" 1.0\" encoding=\" utf-8\" ?>\n "
220
+ " <adaptive-icon xmlns:android=\" http://schemas.android.com/apk/res/android\" >\n "
221
+ " <background android:drawable=\" @mipmap/icon_background\" />\n "
222
+ " <foreground android:drawable=\" @mipmap/icon_foreground\" />\n "
223
+ " %s" // Placeholder for the optional monochrome tag.
224
+ " </adaptive-icon>" ;
225
+
226
+ static const String ICON_XML_PATH = " res/mipmap-anydpi-v26/icon.xml" ;
227
+ static const String THEMED_ICON_XML_PATH = " res/mipmap-anydpi-v26/themed_icon.xml" ;
228
+
217
229
static const int icon_densities_count = 6 ;
218
230
static const char *launcher_icon_option = PNAME(" launcher_icons/main_192x192" );
219
231
static const char *launcher_adaptive_icon_foreground_option = PNAME(" launcher_icons/adaptive_foreground_432x432" );
220
232
static const char *launcher_adaptive_icon_background_option = PNAME(" launcher_icons/adaptive_background_432x432" );
233
+ static const char *launcher_adaptive_icon_monochrome_option = PNAME(" launcher_icons/adaptive_monochrome_432x432" );
221
234
222
235
static const LauncherIcon launcher_icons[icon_densities_count] = {
223
236
{ " res/mipmap-xxxhdpi-v4/icon.png" , 192 },
@@ -246,6 +259,15 @@ static const LauncherIcon launcher_adaptive_icon_backgrounds[icon_densities_coun
246
259
{ " res/mipmap/icon_background.png" , 432 }
247
260
};
248
261
262
+ static const LauncherIcon launcher_adaptive_icon_monochromes[icon_densities_count] = {
263
+ { " res/mipmap-xxxhdpi-v4/icon_monochrome.png" , 432 },
264
+ { " res/mipmap-xxhdpi-v4/icon_monochrome.png" , 324 },
265
+ { " res/mipmap-xhdpi-v4/icon_monochrome.png" , 216 },
266
+ { " res/mipmap-hdpi-v4/icon_monochrome.png" , 162 },
267
+ { " res/mipmap-mdpi-v4/icon_monochrome.png" , 108 },
268
+ { " res/mipmap/icon_monochrome.png" , 432 }
269
+ };
270
+
249
271
static const int EXPORT_FORMAT_APK = 0 ;
250
272
static const int EXPORT_FORMAT_AAB = 1 ;
251
273
@@ -1640,12 +1662,13 @@ void EditorExportPlatformAndroid::_process_launcher_icons(const String &p_file_n
1640
1662
}
1641
1663
}
1642
1664
1643
- void EditorExportPlatformAndroid::load_icon_refs (const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background) {
1665
+ void EditorExportPlatformAndroid::load_icon_refs (const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background, Ref<Image> &monochrome ) {
1644
1666
String project_icon_path = GLOBAL_GET (" application/config/icon" );
1645
1667
1646
1668
icon.instantiate ();
1647
1669
foreground.instantiate ();
1648
1670
background.instantiate ();
1671
+ monochrome.instantiate ();
1649
1672
1650
1673
// Regular icon: user selection -> project icon -> default.
1651
1674
String path = static_cast <String>(p_preset->get (launcher_icon_option)).strip_edges ();
@@ -1673,14 +1696,24 @@ void EditorExportPlatformAndroid::load_icon_refs(const Ref<EditorExportPreset> &
1673
1696
print_verbose (" Loading adaptive background icon from " + path);
1674
1697
ImageLoader::load_image (path, background);
1675
1698
}
1699
+
1700
+ // Adaptive monochrome: user selection -> default.
1701
+ path = static_cast <String>(p_preset->get (launcher_adaptive_icon_monochrome_option)).strip_edges ();
1702
+ if (!path.is_empty ()) {
1703
+ print_verbose (" Loading adaptive monochrome icon from " + path);
1704
+ ImageLoader::load_image (path, monochrome);
1705
+ }
1676
1706
}
1677
1707
1678
1708
void EditorExportPlatformAndroid::_copy_icons_to_gradle_project (const Ref<EditorExportPreset> &p_preset,
1679
1709
const Ref<Image> &p_main_image,
1680
1710
const Ref<Image> &p_foreground,
1681
- const Ref<Image> &p_background) {
1711
+ const Ref<Image> &p_background,
1712
+ const Ref<Image> &p_monochrome) {
1682
1713
String gradle_build_dir = ExportTemplateManager::get_android_build_directory (p_preset);
1683
1714
1715
+ String monochrome_tag = " " ;
1716
+
1684
1717
// Prepare images to be resized for the icons. If some image ends up being uninitialized,
1685
1718
// the default image from the export template will be used.
1686
1719
@@ -1707,7 +1740,19 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref<Editor
1707
1740
launcher_adaptive_icon_backgrounds[i].dimensions , data);
1708
1741
store_file_at_path (gradle_build_dir.path_join (launcher_adaptive_icon_backgrounds[i].export_path ), data);
1709
1742
}
1743
+
1744
+ if (p_monochrome.is_valid () && !p_monochrome->is_empty ()) {
1745
+ print_verbose (" Processing launcher adaptive icon p_monochrome for dimension " + itos (launcher_adaptive_icon_monochromes[i].dimensions ) + " into " + launcher_adaptive_icon_monochromes[i].export_path );
1746
+ Vector<uint8_t > data;
1747
+ _process_launcher_icons (launcher_adaptive_icon_monochromes[i].export_path , p_monochrome,
1748
+ launcher_adaptive_icon_monochromes[i].dimensions , data);
1749
+ store_file_at_path (gradle_build_dir.path_join (launcher_adaptive_icon_monochromes[i].export_path ), data);
1750
+ monochrome_tag = " <monochrome android:drawable=\" @mipmap/icon_monochrome\" />\n " ;
1751
+ }
1710
1752
}
1753
+
1754
+ // Finalize the icon.xml by formatting the template with the optional monochrome tag.
1755
+ store_string_at_path (gradle_build_dir.path_join (ICON_XML_PATH), vformat (ICON_XML_TEMPLATE, monochrome_tag));
1711
1756
}
1712
1757
1713
1758
Vector<EditorExportPlatformAndroid::ABI> EditorExportPlatformAndroid::get_enabled_abis (const Ref<EditorExportPreset> &p_preset) {
@@ -1871,6 +1916,7 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio
1871
1916
r_options->push_back (ExportOption (PropertyInfo (Variant::STRING, launcher_icon_option, PROPERTY_HINT_FILE, " *.png" ), " " ));
1872
1917
r_options->push_back (ExportOption (PropertyInfo (Variant::STRING, launcher_adaptive_icon_foreground_option, PROPERTY_HINT_FILE, " *.png" ), " " ));
1873
1918
r_options->push_back (ExportOption (PropertyInfo (Variant::STRING, launcher_adaptive_icon_background_option, PROPERTY_HINT_FILE, " *.png" ), " " ));
1919
+ r_options->push_back (ExportOption (PropertyInfo (Variant::STRING, launcher_adaptive_icon_monochrome_option, PROPERTY_HINT_FILE, " *.png" ), " " ));
1874
1920
1875
1921
r_options->push_back (ExportOption (PropertyInfo (Variant::BOOL, " graphics/opengl_debug" ), false ));
1876
1922
@@ -3012,8 +3058,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
3012
3058
Ref<Image> main_image;
3013
3059
Ref<Image> foreground;
3014
3060
Ref<Image> background;
3061
+ Ref<Image> monochrome;
3015
3062
3016
- load_icon_refs (p_preset, main_image, foreground, background);
3063
+ load_icon_refs (p_preset, main_image, foreground, background, monochrome );
3017
3064
3018
3065
Vector<uint8_t > command_line_flags;
3019
3066
// Write command line flags into the command_line_flags variable.
@@ -3084,7 +3131,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
3084
3131
add_message (EXPORT_MESSAGE_ERROR, TTR (" Export" ), TTR (" Unable to overwrite res/*.xml files with project name." ));
3085
3132
}
3086
3133
// Copies the project icon files into the appropriate Gradle project directory.
3087
- _copy_icons_to_gradle_project (p_preset, main_image, foreground, background);
3134
+ _copy_icons_to_gradle_project (p_preset, main_image, foreground, background, monochrome );
3088
3135
// Write an AndroidManifest.xml file into the Gradle project directory.
3089
3136
_write_tmp_manifest (p_preset, p_give_internet, p_debug);
3090
3137
@@ -3369,6 +3416,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
3369
3416
String apk_expansion_pkey = p_preset->get (" apk_expansion/public_key" );
3370
3417
3371
3418
Vector<ABI> invalid_abis (enabled_abis);
3419
+
3420
+ // To temporarily store icon xml data.
3421
+ Vector<uint8_t > themed_icon_xml_data;
3422
+ int icon_xml_compression_method = -1 ;
3423
+
3372
3424
while (ret == UNZ_OK) {
3373
3425
// get filename
3374
3426
unz_file_info info;
@@ -3398,6 +3450,20 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
3398
3450
_fix_resources (p_preset, data);
3399
3451
}
3400
3452
3453
+ if (file == THEMED_ICON_XML_PATH) {
3454
+ // Store themed_icon.xml data.
3455
+ themed_icon_xml_data = data;
3456
+ skip = true ;
3457
+ }
3458
+
3459
+ if (file == ICON_XML_PATH) {
3460
+ if (monochrome.is_valid () && !monochrome->is_empty ()) {
3461
+ // Defer processing of icon.xml until after themed_icon.xml is read.
3462
+ icon_xml_compression_method = info.compression_method ;
3463
+ skip = true ;
3464
+ }
3465
+ }
3466
+
3401
3467
if (file.ends_with (" .png" ) && file.contains (" mipmap" )) {
3402
3468
for (int i = 0 ; i < icon_densities_count; ++i) {
3403
3469
if (main_image.is_valid () && !main_image->is_empty ()) {
@@ -3415,6 +3481,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
3415
3481
_process_launcher_icons (file, background, launcher_adaptive_icon_backgrounds[i].dimensions , data);
3416
3482
}
3417
3483
}
3484
+ if (monochrome.is_valid () && !monochrome->is_empty ()) {
3485
+ if (file == launcher_adaptive_icon_monochromes[i].export_path ) {
3486
+ _process_launcher_icons (file, monochrome, launcher_adaptive_icon_monochromes[i].dimensions , data);
3487
+ }
3488
+ }
3418
3489
}
3419
3490
}
3420
3491
@@ -3462,6 +3533,28 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
3462
3533
ret = unzGoToNextFile (pkg);
3463
3534
}
3464
3535
3536
+ // Process deferred icon.xml and replace it's data with themed_icon.xml.
3537
+ if (monochrome.is_valid () && !monochrome->is_empty ()) {
3538
+ print_line (" ADDING: " + ICON_XML_PATH + " (replacing with themed_icon.xml data)" );
3539
+
3540
+ const bool uncompressed = icon_xml_compression_method == 0 ;
3541
+ zip_fileinfo zipfi = get_zip_fileinfo ();
3542
+
3543
+ zipOpenNewFileInZip (unaligned_apk,
3544
+ ICON_XML_PATH.utf8 ().get_data (),
3545
+ &zipfi,
3546
+ nullptr ,
3547
+ 0 ,
3548
+ nullptr ,
3549
+ 0 ,
3550
+ nullptr ,
3551
+ uncompressed ? 0 : Z_DEFLATED,
3552
+ Z_DEFAULT_COMPRESSION);
3553
+
3554
+ zipWriteInFileInZip (unaligned_apk, themed_icon_xml_data.ptr (), themed_icon_xml_data.size ());
3555
+ zipCloseFileInZip (unaligned_apk);
3556
+ }
3557
+
3465
3558
if (!invalid_abis.is_empty ()) {
3466
3559
add_message (EXPORT_MESSAGE_ERROR, TTR (" Export" ), vformat (TTR (" Missing libraries in the export template for the selected architectures: %s. Please build a template with all required libraries, or uncheck the missing architectures in the export preset." ), join_abis (invalid_abis, " , " , false )));
3467
3560
CLEANUP_AND_RETURN (ERR_FILE_NOT_FOUND);
0 commit comments