1 /*************************************************************************/
2 /* export.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
10 /* */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the */
13 /* "Software"), to deal in the Software without restriction, including */
14 /* without limitation the rights to use, copy, modify, merge, publish, */
15 /* distribute, sublicense, and/or sell copies of the Software, and to */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions: */
18 /* */
19 /* The above copyright notice and this permission notice shall be */
20 /* included in all copies or substantial portions of the Software. */
21 /* */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29 /*************************************************************************/
30 #include "export.h"
31 #include "editor/editor_import_export.h"
32 #include "editor/editor_node.h"
33 #include "editor/editor_settings.h"
34 #include "globals.h"
35 #include "io/marshalls.h"
36 #include "io/zip_io.h"
37 #include "os/file_access.h"
38 #include "os/os.h"
39 #include "platform/android/logo.gen.h"
40 #include "version.h"
41 #include <string.h>
42
43 static const char *android_perms[] = {
44 "ACCESS_CHECKIN_PROPERTIES",
45 "ACCESS_COARSE_LOCATION",
46 "ACCESS_FINE_LOCATION",
47 "ACCESS_LOCATION_EXTRA_COMMANDS",
48 "ACCESS_MOCK_LOCATION",
49 "ACCESS_NETWORK_STATE",
50 "ACCESS_SURFACE_FLINGER",
51 "ACCESS_WIFI_STATE",
52 "ACCOUNT_MANAGER",
53 "ADD_VOICEMAIL",
54 "AUTHENTICATE_ACCOUNTS",
55 "BATTERY_STATS",
56 "BIND_ACCESSIBILITY_SERVICE",
57 "BIND_APPWIDGET",
58 "BIND_DEVICE_ADMIN",
59 "BIND_INPUT_METHOD",
60 "BIND_NFC_SERVICE",
61 "BIND_NOTIFICATION_LISTENER_SERVICE",
62 "BIND_PRINT_SERVICE",
63 "BIND_REMOTEVIEWS",
64 "BIND_TEXT_SERVICE",
65 "BIND_VPN_SERVICE",
66 "BIND_WALLPAPER",
67 "BLUETOOTH",
68 "BLUETOOTH_ADMIN",
69 "BLUETOOTH_PRIVILEGED",
70 "BRICK",
71 "BROADCAST_PACKAGE_REMOVED",
72 "BROADCAST_SMS",
73 "BROADCAST_STICKY",
74 "BROADCAST_WAP_PUSH",
75 "CALL_PHONE",
76 "CALL_PRIVILEGED",
77 "CAMERA",
78 "CAPTURE_AUDIO_OUTPUT",
79 "CAPTURE_SECURE_VIDEO_OUTPUT",
80 "CAPTURE_VIDEO_OUTPUT",
81 "CHANGE_COMPONENT_ENABLED_STATE",
82 "CHANGE_CONFIGURATION",
83 "CHANGE_NETWORK_STATE",
84 "CHANGE_WIFI_MULTICAST_STATE",
85 "CHANGE_WIFI_STATE",
86 "CLEAR_APP_CACHE",
87 "CLEAR_APP_USER_DATA",
88 "CONTROL_LOCATION_UPDATES",
89 "DELETE_CACHE_FILES",
90 "DELETE_PACKAGES",
91 "DEVICE_POWER",
92 "DIAGNOSTIC",
93 "DISABLE_KEYGUARD",
94 "DUMP",
95 "EXPAND_STATUS_BAR",
96 "FACTORY_TEST",
97 "FLASHLIGHT",
98 "FORCE_BACK",
99 "GET_ACCOUNTS",
100 "GET_PACKAGE_SIZE",
101 "GET_TASKS",
102 "GET_TOP_ACTIVITY_INFO",
103 "GLOBAL_SEARCH",
104 "HARDWARE_TEST",
105 "INJECT_EVENTS",
106 "INSTALL_LOCATION_PROVIDER",
107 "INSTALL_PACKAGES",
108 "INSTALL_SHORTCUT",
109 "INTERNAL_SYSTEM_WINDOW",
110 "INTERNET",
111 "KILL_BACKGROUND_PROCESSES",
112 "LOCATION_HARDWARE",
113 "MANAGE_ACCOUNTS",
114 "MANAGE_APP_TOKENS",
115 "MANAGE_DOCUMENTS",
116 "MASTER_CLEAR",
117 "MEDIA_CONTENT_CONTROL",
118 "MODIFY_AUDIO_SETTINGS",
119 "MODIFY_PHONE_STATE",
120 "MOUNT_FORMAT_FILESYSTEMS",
121 "MOUNT_UNMOUNT_FILESYSTEMS",
122 "NFC",
123 "PERSISTENT_ACTIVITY",
124 "PROCESS_OUTGOING_CALLS",
125 "READ_CALENDAR",
126 "READ_CALL_LOG",
127 "READ_CONTACTS",
128 "READ_EXTERNAL_STORAGE",
129 "READ_FRAME_BUFFER",
130 "READ_HISTORY_BOOKMARKS",
131 "READ_INPUT_STATE",
132 "READ_LOGS",
133 "READ_PHONE_STATE",
134 "READ_PROFILE",
135 "READ_SMS",
136 "READ_SOCIAL_STREAM",
137 "READ_SYNC_SETTINGS",
138 "READ_SYNC_STATS",
139 "READ_USER_DICTIONARY",
140 "REBOOT",
141 "RECEIVE_BOOT_COMPLETED",
142 "RECEIVE_MMS",
143 "RECEIVE_SMS",
144 "RECEIVE_WAP_PUSH",
145 "RECORD_AUDIO",
146 "REORDER_TASKS",
147 "RESTART_PACKAGES",
148 "SEND_RESPOND_VIA_MESSAGE",
149 "SEND_SMS",
150 "SET_ACTIVITY_WATCHER",
151 "SET_ALARM",
152 "SET_ALWAYS_FINISH",
153 "SET_ANIMATION_SCALE",
154 "SET_DEBUG_APP",
155 "SET_ORIENTATION",
156 "SET_POINTER_SPEED",
157 "SET_PREFERRED_APPLICATIONS",
158 "SET_PROCESS_LIMIT",
159 "SET_TIME",
160 "SET_TIME_ZONE",
161 "SET_WALLPAPER",
162 "SET_WALLPAPER_HINTS",
163 "SIGNAL_PERSISTENT_PROCESSES",
164 "STATUS_BAR",
165 "SUBSCRIBED_FEEDS_READ",
166 "SUBSCRIBED_FEEDS_WRITE",
167 "SYSTEM_ALERT_WINDOW",
168 "TRANSMIT_IR",
169 "UNINSTALL_SHORTCUT",
170 "UPDATE_DEVICE_STATS",
171 "USE_CREDENTIALS",
172 "USE_SIP",
173 "VIBRATE",
174 "WAKE_LOCK",
175 "WRITE_APN_SETTINGS",
176 "WRITE_CALENDAR",
177 "WRITE_CALL_LOG",
178 "WRITE_CONTACTS",
179 "WRITE_EXTERNAL_STORAGE",
180 "WRITE_GSERVICES",
181 "WRITE_HISTORY_BOOKMARKS",
182 "WRITE_PROFILE",
183 "WRITE_SECURE_SETTINGS",
184 "WRITE_SETTINGS",
185 "WRITE_SMS",
186 "WRITE_SOCIAL_STREAM",
187 "WRITE_SYNC_SETTINGS",
188 "WRITE_USER_DICTIONARY",
189 NULL
190 };
191
192 class EditorExportPlatformAndroid : public EditorExportPlatform {
193
194 OBJ_TYPE(EditorExportPlatformAndroid, EditorExportPlatform);
195
196 enum {
197 MAX_USER_PERMISSIONS = 20,
198 SCREEN_SMALL = 0,
199 SCREEN_NORMAL = 1,
200 SCREEN_LARGE = 2,
201 SCREEN_XLARGE = 3,
202 SCREEN_MAX = 4
203 };
204
205 String custom_release_package;
206 String custom_debug_package;
207
208 int version_code;
209 String version_name;
210 String package;
211 String name;
212 String icon;
213 String cmdline;
214 bool _signed;
215 bool apk_expansion;
216 bool remove_prev;
217 bool use_32_fb;
218 bool immersive;
219 bool export_arm;
220 bool export_arm64;
221 bool export_x86;
222 bool export_x86_64;
223 String apk_expansion_salt;
224 String apk_expansion_pkey;
225 int orientation;
226
227 String release_keystore;
228 String release_password;
229 String release_username;
230
231 struct APKExportData {
232
233 zipFile apk;
234 EditorProgress *ep;
235 };
236
237 struct Device {
238
239 String id;
240 String name;
241 String description;
242 int api_level;
243 };
244
245 Vector<Device> devices;
246 bool devices_changed;
247 Mutex *device_lock;
248 Thread *device_thread;
249 Ref<ImageTexture> logo;
250
251 Set<String> perms;
252 String user_perms[MAX_USER_PERMISSIONS];
253 bool screen_support[SCREEN_MAX];
254
255 volatile bool quit_request;
256
257 static void _device_poll_thread(void *ud);
258
259 String get_package_name();
260
261 String get_project_name() const;
262 void _fix_manifest(Vector<uint8_t> &p_manifest, bool p_give_internet);
263 void _fix_resources(Vector<uint8_t> &p_manifest);
264 static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total);
265 static bool _should_compress_asset(const String &p_path, const Vector<uint8_t> &p_data);
266
267 protected:
268 bool _set(const StringName &p_name, const Variant &p_value);
269 bool _get(const StringName &p_name, Variant &r_ret) const;
270 void _get_property_list(List<PropertyInfo> *p_list) const;
271
272 public:
get_name() const273 virtual String get_name() const { return "Android"; }
get_image_compression() const274 virtual ImageCompression get_image_compression() const { return IMAGE_COMPRESSION_ETC1; }
get_logo() const275 virtual Ref<Texture> get_logo() const { return logo; }
276
277 virtual bool poll_devices();
278 virtual int get_device_count() const;
279 virtual String get_device_name(int p_device) const;
280 virtual String get_device_info(int p_device) const;
281 virtual Error run(int p_device, int p_flags = 0);
282
requires_password(bool p_debug) const283 virtual bool requires_password(bool p_debug) const { return !p_debug; }
get_binary_extension() const284 virtual String get_binary_extension() const { return "apk"; }
285 virtual Error export_project(const String &p_path, bool p_debug, int p_flags = 0);
286
287 virtual bool can_export(String *r_error = NULL) const;
288
289 EditorExportPlatformAndroid();
290 ~EditorExportPlatformAndroid();
291 };
292
_set(const StringName & p_name,const Variant & p_value)293 bool EditorExportPlatformAndroid::_set(const StringName &p_name, const Variant &p_value) {
294
295 String n = p_name;
296
297 if (n == "one_click_deploy/clear_previous_install")
298 remove_prev = p_value;
299 else if (n == "custom_package/debug")
300 custom_debug_package = p_value;
301 else if (n == "custom_package/release")
302 custom_release_package = p_value;
303 else if (n == "version/code")
304 version_code = p_value;
305 else if (n == "version/name")
306 version_name = p_value;
307 else if (n == "command_line/extra_args")
308 cmdline = p_value;
309 else if (n == "package/unique_name")
310 package = p_value;
311 else if (n == "package/name")
312 name = p_value;
313 else if (n == "package/icon")
314 icon = p_value;
315 else if (n == "package/signed")
316 _signed = p_value;
317 else if (n == "architecture/arm")
318 export_arm = p_value;
319 else if (n == "architecture/arm64")
320 export_arm64 = p_value;
321 else if (n == "architecture/x86")
322 export_x86 = p_value;
323 else if (n == "architecture/x86_64")
324 export_x86_64 = p_value;
325 else if (n == "screen/use_32_bits_view")
326 use_32_fb = p_value;
327 else if (n == "screen/immersive_mode")
328 immersive = p_value;
329 else if (n == "screen/orientation")
330 orientation = p_value;
331 else if (n == "screen/support_small")
332 screen_support[SCREEN_SMALL] = p_value;
333 else if (n == "screen/support_normal")
334 screen_support[SCREEN_NORMAL] = p_value;
335 else if (n == "screen/support_large")
336 screen_support[SCREEN_LARGE] = p_value;
337 else if (n == "screen/support_xlarge")
338 screen_support[SCREEN_XLARGE] = p_value;
339 else if (n == "keystore/release")
340 release_keystore = p_value;
341 else if (n == "keystore/release_user")
342 release_username = p_value;
343 else if (n == "keystore/release_password")
344 release_password = p_value;
345 else if (n == "apk_expansion/enable")
346 apk_expansion = p_value;
347 else if (n == "apk_expansion/SALT")
348 apk_expansion_salt = p_value;
349 else if (n == "apk_expansion/public_key")
350 apk_expansion_pkey = p_value;
351 else if (n.begins_with("permissions/")) {
352
353 String what = n.get_slicec('/', 1).to_upper();
354 bool state = p_value;
355 if (state)
356 perms.insert(what);
357 else
358 perms.erase(what);
359 } else if (n.begins_with("user_permissions/")) {
360
361 int which = n.get_slicec('/', 1).to_int();
362 ERR_FAIL_INDEX_V(which, MAX_USER_PERMISSIONS, false);
363 user_perms[which] = p_value;
364
365 } else
366 return false;
367
368 return true;
369 }
370
_get(const StringName & p_name,Variant & r_ret) const371 bool EditorExportPlatformAndroid::_get(const StringName &p_name, Variant &r_ret) const {
372
373 String n = p_name;
374 if (n == "one_click_deploy/clear_previous_install")
375 r_ret = remove_prev;
376 else if (n == "custom_package/debug")
377 r_ret = custom_debug_package;
378 else if (n == "custom_package/release")
379 r_ret = custom_release_package;
380 else if (n == "version/code")
381 r_ret = version_code;
382 else if (n == "version/name")
383 r_ret = version_name;
384 else if (n == "command_line/extra_args")
385 r_ret = cmdline;
386 else if (n == "package/unique_name")
387 r_ret = package;
388 else if (n == "package/name")
389 r_ret = name;
390 else if (n == "package/icon")
391 r_ret = icon;
392 else if (n == "package/signed")
393 r_ret = _signed;
394 else if (n == "architecture/arm")
395 r_ret = export_arm;
396 else if (n == "architecture/arm64")
397 r_ret = export_arm64;
398 else if (n == "architecture/x86")
399 r_ret = export_x86;
400 else if (n == "architecture/x86_64")
401 r_ret = export_x86_64;
402 else if (n == "screen/use_32_bits_view")
403 r_ret = use_32_fb;
404 else if (n == "screen/immersive_mode")
405 r_ret = immersive;
406 else if (n == "screen/orientation")
407 r_ret = orientation;
408 else if (n == "screen/support_small")
409 r_ret = screen_support[SCREEN_SMALL];
410 else if (n == "screen/support_normal")
411 r_ret = screen_support[SCREEN_NORMAL];
412 else if (n == "screen/support_large")
413 r_ret = screen_support[SCREEN_LARGE];
414 else if (n == "screen/support_xlarge")
415 r_ret = screen_support[SCREEN_XLARGE];
416 else if (n == "keystore/release")
417 r_ret = release_keystore;
418 else if (n == "keystore/release_user")
419 r_ret = release_username;
420 else if (n == "keystore/release_password")
421 r_ret = release_password;
422 else if (n == "apk_expansion/enable")
423 r_ret = apk_expansion;
424 else if (n == "apk_expansion/SALT")
425 r_ret = apk_expansion_salt;
426 else if (n == "apk_expansion/public_key")
427 r_ret = apk_expansion_pkey;
428 else if (n.begins_with("permissions/")) {
429
430 String what = n.get_slicec('/', 1).to_upper();
431 r_ret = perms.has(what);
432 } else if (n.begins_with("user_permissions/")) {
433
434 int which = n.get_slicec('/', 1).to_int();
435 ERR_FAIL_INDEX_V(which, MAX_USER_PERMISSIONS, false);
436 r_ret = user_perms[which];
437 } else
438 return false;
439
440 return true;
441 }
442
_get_property_list(List<PropertyInfo> * p_list) const443 void EditorExportPlatformAndroid::_get_property_list(List<PropertyInfo> *p_list) const {
444
445 p_list->push_back(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"));
446 p_list->push_back(PropertyInfo(Variant::STRING, "custom_package/debug", PROPERTY_HINT_GLOBAL_FILE, "apk"));
447 p_list->push_back(PropertyInfo(Variant::STRING, "custom_package/release", PROPERTY_HINT_GLOBAL_FILE, "apk"));
448 p_list->push_back(PropertyInfo(Variant::STRING, "command_line/extra_args"));
449 p_list->push_back(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,2147483647,1"));
450 p_list->push_back(PropertyInfo(Variant::STRING, "version/name"));
451 p_list->push_back(PropertyInfo(Variant::STRING, "package/unique_name"));
452 p_list->push_back(PropertyInfo(Variant::STRING, "package/name"));
453 p_list->push_back(PropertyInfo(Variant::STRING, "package/icon", PROPERTY_HINT_FILE, "png"));
454 p_list->push_back(PropertyInfo(Variant::BOOL, "package/signed"));
455 p_list->push_back(PropertyInfo(Variant::BOOL, "architecture/arm"));
456 p_list->push_back(PropertyInfo(Variant::BOOL, "architecture/arm64"));
457 p_list->push_back(PropertyInfo(Variant::BOOL, "architecture/x86"));
458 p_list->push_back(PropertyInfo(Variant::BOOL, "architecture/x86_64"));
459 p_list->push_back(PropertyInfo(Variant::BOOL, "screen/use_32_bits_view"));
460 p_list->push_back(PropertyInfo(Variant::BOOL, "screen/immersive_mode"));
461 p_list->push_back(PropertyInfo(Variant::INT, "screen/orientation", PROPERTY_HINT_ENUM, "Landscape,Portrait"));
462 p_list->push_back(PropertyInfo(Variant::BOOL, "screen/support_small"));
463 p_list->push_back(PropertyInfo(Variant::BOOL, "screen/support_normal"));
464 p_list->push_back(PropertyInfo(Variant::BOOL, "screen/support_large"));
465 p_list->push_back(PropertyInfo(Variant::BOOL, "screen/support_xlarge"));
466 p_list->push_back(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "keystore"));
467 p_list->push_back(PropertyInfo(Variant::STRING, "keystore/release_user"));
468 p_list->push_back(PropertyInfo(Variant::STRING, "keystore/release_password"));
469 p_list->push_back(PropertyInfo(Variant::BOOL, "apk_expansion/enable"));
470 p_list->push_back(PropertyInfo(Variant::STRING, "apk_expansion/SALT"));
471 p_list->push_back(PropertyInfo(Variant::STRING, "apk_expansion/public_key", PROPERTY_HINT_MULTILINE_TEXT));
472
473 const char **perms = android_perms;
474 while (*perms) {
475
476 p_list->push_back(PropertyInfo(Variant::BOOL, "permissions/" + String(*perms).to_lower()));
477 perms++;
478 }
479
480 for (int i = 0; i < MAX_USER_PERMISSIONS; i++) {
481
482 p_list->push_back(PropertyInfo(Variant::STRING, "user_permissions/" + itos(i)));
483 }
484
485 //p_list->push_back( PropertyInfo( Variant::INT, "resources/pack_mode", PROPERTY_HINT_ENUM,"Copy,Single Exec.,Pack (.pck),Bundles (Optical)"));
486 }
487
_parse_string(const uint8_t * p_bytes,bool p_utf8)488 static String _parse_string(const uint8_t *p_bytes, bool p_utf8) {
489
490 uint32_t offset = 0;
491 uint32_t len = decode_uint16(&p_bytes[offset]);
492
493 if (p_utf8) {
494 //don't know how to read extended utf8, this will have to be for now
495 len >>= 8;
496 }
497 offset += 2;
498 //printf("len %i, unicode: %i\n",len,int(p_utf8));
499
500 if (p_utf8) {
501
502 Vector<uint8_t> str8;
503 str8.resize(len + 1);
504 for (int i = 0; i < len; i++) {
505 str8[i] = p_bytes[offset + i];
506 }
507 str8[len] = 0;
508 String str;
509 str.parse_utf8((const char *)str8.ptr());
510 return str;
511 } else {
512
513 String str;
514 for (int i = 0; i < len; i++) {
515 CharType c = decode_uint16(&p_bytes[offset + i * 2]);
516 if (c == 0)
517 break;
518 str += String::chr(c);
519 }
520 return str;
521 }
522 }
523
_fix_resources(Vector<uint8_t> & p_manifest)524 void EditorExportPlatformAndroid::_fix_resources(Vector<uint8_t> &p_manifest) {
525
526 const int UTF8_FLAG = 0x00000100;
527 print_line("*******************GORRRGLE***********************");
528
529 uint32_t header = decode_uint32(&p_manifest[0]);
530 uint32_t filesize = decode_uint32(&p_manifest[4]);
531 uint32_t string_block_len = decode_uint32(&p_manifest[16]);
532 uint32_t string_count = decode_uint32(&p_manifest[20]);
533 uint32_t string_flags = decode_uint32(&p_manifest[28]);
534 const uint32_t string_table_begins = 40;
535
536 Vector<String> string_table;
537
538 //printf("stirng block len: %i\n",string_block_len);
539 //printf("stirng count: %i\n",string_count);
540 //printf("flags: %x\n",string_flags);
541
542 for (int i = 0; i < string_count; i++) {
543
544 uint32_t offset = decode_uint32(&p_manifest[string_table_begins + i * 4]);
545 offset += string_table_begins + string_count * 4;
546
547 String str = _parse_string(&p_manifest[offset], string_flags & UTF8_FLAG);
548
549 if (str.begins_with("godot-project-name")) {
550
551 if (str == "godot-project-name") {
552 //project name
553 str = get_project_name();
554
555 } else {
556
557 String lang = str.substr(str.find_last("-") + 1, str.length()).replace("-", "_");
558 String prop = "application/name_" + lang;
559 if (Globals::get_singleton()->has(prop)) {
560 str = Globals::get_singleton()->get(prop);
561 } else {
562 str = get_project_name();
563 }
564 }
565 }
566
567 string_table.push_back(str);
568 }
569
570 //write a new string table, but use 16 bits
571 Vector<uint8_t> ret;
572 ret.resize(string_table_begins + string_table.size() * 4);
573
574 for (int i = 0; i < string_table_begins; i++) {
575
576 ret[i] = p_manifest[i];
577 }
578
579 int ofs = 0;
580 for (int i = 0; i < string_table.size(); i++) {
581
582 encode_uint32(ofs, &ret[string_table_begins + i * 4]);
583 ofs += string_table[i].length() * 2 + 2 + 2;
584 }
585
586 ret.resize(ret.size() + ofs);
587 uint8_t *chars = &ret[ret.size() - ofs];
588 for (int i = 0; i < string_table.size(); i++) {
589
590 String s = string_table[i];
591 encode_uint16(s.length(), chars);
592 chars += 2;
593 for (int j = 0; j < s.length(); j++) {
594 encode_uint16(s[j], chars);
595 chars += 2;
596 }
597 encode_uint16(0, chars);
598 chars += 2;
599 }
600
601 //pad
602 while (ret.size() % 4)
603 ret.push_back(0);
604
605 //change flags to not use utf8
606 encode_uint32(string_flags & ~0x100, &ret[28]);
607 //change length
608 encode_uint32(ret.size() - 12, &ret[16]);
609 //append the rest...
610 int rest_from = 12 + string_block_len;
611 int rest_to = ret.size();
612 int rest_len = (p_manifest.size() - rest_from);
613 ret.resize(ret.size() + (p_manifest.size() - rest_from));
614 for (int i = 0; i < rest_len; i++) {
615 ret[rest_to + i] = p_manifest[rest_from + i];
616 }
617 //finally update the size
618 encode_uint32(ret.size(), &ret[4]);
619
620 p_manifest = ret;
621 //printf("end\n");
622 }
623
get_project_name() const624 String EditorExportPlatformAndroid::get_project_name() const {
625
626 String aname;
627 if (this->name != "") {
628 aname = this->name;
629 } else {
630 aname = Globals::get_singleton()->get("application/name");
631 }
632
633 if (aname == "") {
634 aname = _MKSTR(VERSION_NAME);
635 }
636
637 return aname;
638 }
639
_fix_manifest(Vector<uint8_t> & p_manifest,bool p_give_internet)640 void EditorExportPlatformAndroid::_fix_manifest(Vector<uint8_t> &p_manifest, bool p_give_internet) {
641
642 const int CHUNK_AXML_FILE = 0x00080003;
643 const int CHUNK_RESOURCEIDS = 0x00080180;
644 const int CHUNK_STRINGS = 0x001C0001;
645 const int CHUNK_XML_END_NAMESPACE = 0x00100101;
646 const int CHUNK_XML_END_TAG = 0x00100103;
647 const int CHUNK_XML_START_NAMESPACE = 0x00100100;
648 const int CHUNK_XML_START_TAG = 0x00100102;
649 const int CHUNK_XML_TEXT = 0x00100104;
650 const int UTF8_FLAG = 0x00000100;
651
652 Vector<String> string_table;
653
654 uint32_t ofs = 0;
655
656 uint32_t header = decode_uint32(&p_manifest[ofs]);
657 uint32_t filesize = decode_uint32(&p_manifest[ofs + 4]);
658 ofs += 8;
659
660 // print_line("FILESIZE: "+itos(filesize)+" ACTUAL: "+itos(p_manifest.size()));
661
662 uint32_t string_count;
663 uint32_t styles_count;
664 uint32_t string_flags;
665 uint32_t string_data_offset;
666
667 uint32_t styles_offset;
668 uint32_t string_table_begins;
669 uint32_t string_table_ends;
670 Vector<uint8_t> stable_extra;
671
672 while (ofs < p_manifest.size()) {
673
674 uint32_t chunk = decode_uint32(&p_manifest[ofs]);
675 uint32_t size = decode_uint32(&p_manifest[ofs + 4]);
676
677 switch (chunk) {
678
679 case CHUNK_STRINGS: {
680
681 int iofs = ofs + 8;
682
683 string_count = decode_uint32(&p_manifest[iofs]);
684 styles_count = decode_uint32(&p_manifest[iofs + 4]);
685 uint32_t string_flags = decode_uint32(&p_manifest[iofs + 8]);
686 string_data_offset = decode_uint32(&p_manifest[iofs + 12]);
687 styles_offset = decode_uint32(&p_manifest[iofs + 16]);
688 /*
689 printf("string count: %i\n",string_count);
690 printf("flags: %i\n",string_flags);
691 printf("sdata ofs: %i\n",string_data_offset);
692 printf("styles ofs: %i\n",styles_offset);
693 */
694 uint32_t st_offset = iofs + 20;
695 string_table.resize(string_count);
696 uint32_t string_end = 0;
697
698 string_table_begins = st_offset;
699
700 for (int i = 0; i < string_count; i++) {
701
702 uint32_t string_at = decode_uint32(&p_manifest[st_offset + i * 4]);
703 string_at += st_offset + string_count * 4;
704
705 ERR_EXPLAIN("Unimplemented, can't read utf8 string table.");
706 ERR_FAIL_COND(string_flags & UTF8_FLAG);
707
708 if (string_flags & UTF8_FLAG) {
709
710 } else {
711 uint32_t len = decode_uint16(&p_manifest[string_at]);
712 Vector<CharType> ucstring;
713 ucstring.resize(len + 1);
714 for (int j = 0; j < len; j++) {
715 uint16_t c = decode_uint16(&p_manifest[string_at + 2 + 2 * j]);
716 ucstring[j] = c;
717 }
718 string_end = MAX(string_at + 2 + 2 * len, string_end);
719 ucstring[len] = 0;
720 string_table[i] = ucstring.ptr();
721 }
722
723 // print_line("String "+itos(i)+": "+string_table[i]);
724 }
725
726 for (int i = string_end; i < (ofs + size); i++) {
727 stable_extra.push_back(p_manifest[i]);
728 }
729
730 // printf("stable extra: %i\n",int(stable_extra.size()));
731 string_table_ends = ofs + size;
732
733 // print_line("STABLE SIZE: "+itos(size)+" ACTUAL: "+itos(string_table_ends));
734
735 } break;
736 case CHUNK_XML_START_TAG: {
737
738 int iofs = ofs + 8;
739 uint32_t line = decode_uint32(&p_manifest[iofs]);
740 uint32_t nspace = decode_uint32(&p_manifest[iofs + 8]);
741 uint32_t name = decode_uint32(&p_manifest[iofs + 12]);
742 uint32_t check = decode_uint32(&p_manifest[iofs + 16]);
743
744 String tname = string_table[name];
745
746 // printf("NSPACE: %i\n",nspace);
747 //printf("NAME: %i (%s)\n",name,tname.utf8().get_data());
748 //printf("CHECK: %x\n",check);
749 uint32_t attrcount = decode_uint32(&p_manifest[iofs + 20]);
750 iofs += 28;
751 //printf("ATTRCOUNT: %x\n",attrcount);
752 for (int i = 0; i < attrcount; i++) {
753 uint32_t attr_nspace = decode_uint32(&p_manifest[iofs]);
754 uint32_t attr_name = decode_uint32(&p_manifest[iofs + 4]);
755 uint32_t attr_value = decode_uint32(&p_manifest[iofs + 8]);
756 uint32_t attr_flags = decode_uint32(&p_manifest[iofs + 12]);
757 uint32_t attr_resid = decode_uint32(&p_manifest[iofs + 16]);
758
759 String value;
760 if (attr_value != 0xFFFFFFFF)
761 value = string_table[attr_value];
762 else
763 value = "Res #" + itos(attr_resid);
764 String attrname = string_table[attr_name];
765 String nspace;
766 if (attr_nspace != 0xFFFFFFFF)
767 nspace = string_table[attr_nspace];
768 else
769 nspace = "";
770
771 //printf("ATTR %i NSPACE: %i\n",i,attr_nspace);
772 //printf("ATTR %i NAME: %i (%s)\n",i,attr_name,attrname.utf8().get_data());
773 //printf("ATTR %i VALUE: %i (%s)\n",i,attr_value,value.utf8().get_data());
774 //printf("ATTR %i FLAGS: %x\n",i,attr_flags);
775 //printf("ATTR %i RESID: %x\n",i,attr_resid);
776
777 //replace project information
778 if (tname == "manifest" && attrname == "package") {
779
780 print_line("FOUND package");
781 string_table[attr_value] = get_package_name();
782 }
783
784 //print_line("tname: "+tname);
785 //print_line("nspace: "+nspace);
786 //print_line("attrname: "+attrname);
787 if (tname == "manifest" && /*nspace=="android" &&*/ attrname == "versionCode") {
788
789 print_line("FOUND versionCode");
790 encode_uint32(version_code, &p_manifest[iofs + 16]);
791 }
792
793 if (tname == "manifest" && /*nspace=="android" &&*/ attrname == "versionName") {
794
795 print_line("FOUND versionName");
796 if (attr_value == 0xFFFFFFFF) {
797 WARN_PRINT("Version name in a resource, should be plaintext")
798 } else
799 string_table[attr_value] = version_name;
800 }
801
802 if (tname == "activity" && /*nspace=="android" &&*/ attrname == "screenOrientation") {
803
804 encode_uint32(orientation == 0 ? 0 : 1, &p_manifest[iofs + 16]);
805 /*
806 print_line("FOUND screen orientation");
807 if (attr_value==0xFFFFFFFF) {
808 WARN_PRINT("Version name in a resource, should be plaintext")
809 } else {
810 string_table[attr_value]=(orientation==0?"landscape":"portrait");
811 }*/
812 }
813
814 if (tname == "supports-screens") {
815
816 if (attrname == "smallScreens") {
817
818 encode_uint32(screen_support[SCREEN_SMALL] ? 0xFFFFFFFF : 0, &p_manifest[iofs + 16]);
819
820 } else if (attrname == "normalScreens") {
821
822 encode_uint32(screen_support[SCREEN_NORMAL] ? 0xFFFFFFFF : 0, &p_manifest[iofs + 16]);
823
824 } else if (attrname == "largeScreens") {
825
826 encode_uint32(screen_support[SCREEN_LARGE] ? 0xFFFFFFFF : 0, &p_manifest[iofs + 16]);
827
828 } else if (attrname == "xlargeScreens") {
829
830 encode_uint32(screen_support[SCREEN_XLARGE] ? 0xFFFFFFFF : 0, &p_manifest[iofs + 16]);
831 }
832 }
833
834 iofs += 20;
835 }
836
837 } break;
838 case CHUNK_XML_END_TAG: {
839 int iofs = ofs + 8;
840 uint32_t name = decode_uint32(&p_manifest[iofs + 12]);
841 String tname = string_table[name];
842
843 if (tname == "manifest") {
844 print_line("Found manifest end");
845
846 // save manifest ending so we can restore it
847 Vector<uint8_t> manifest_end;
848 uint32_t manifest_cur_size = p_manifest.size();
849 uint32_t node_size = size;
850
851 manifest_end.resize(p_manifest.size() - ofs);
852 memcpy(manifest_end.ptr(), &p_manifest[ofs], manifest_end.size());
853
854 int32_t attr_name_string = string_table.find("name");
855 ERR_EXPLAIN("Template does not have 'name' attribute");
856 ERR_FAIL_COND(attr_name_string == -1);
857
858 int32_t ns_android_string = string_table.find("android");
859 ERR_EXPLAIN("Template does not have 'android' namespace");
860 ERR_FAIL_COND(ns_android_string == -1);
861
862 int32_t attr_uses_permission_string = string_table.find("uses-permission");
863 if (attr_uses_permission_string == -1) {
864 string_table.push_back("uses-permission");
865 attr_uses_permission_string = string_table.size() - 1;
866 }
867
868 Vector<String> apk_perms;
869 const char **aperms = android_perms;
870 while (*aperms) {
871 if (perms.has(*aperms)) {
872 apk_perms.push_back("android.permission." + String(*aperms));
873 }
874 aperms++;
875 }
876
877 for (int i = 0; i < MAX_USER_PERMISSIONS; i++) {
878 if (user_perms[i].strip_edges() != "" && user_perms[i].strip_edges() != "False")
879 apk_perms.push_back(user_perms[i].strip_edges());
880 }
881
882 if (p_give_internet) {
883 if (apk_perms.find("android.permission.INTERNET") == -1)
884 apk_perms.push_back("android.permission.INTERNET");
885 }
886
887 for (int i = 0; i < apk_perms.size(); ++i) {
888 print_line("Adding permission " + apk_perms[i]);
889
890 manifest_cur_size += 56 + 24; // node + end node
891 p_manifest.resize(manifest_cur_size);
892
893 // Add permission to the string pool
894 int32_t perm_string = string_table.find(apk_perms[i]);
895 if (perm_string == -1) {
896 string_table.push_back(apk_perms[i]);
897 perm_string = string_table.size() - 1;
898 }
899
900 // start tag
901 encode_uint16(0x102, &p_manifest[ofs]); // type
902 encode_uint16(16, &p_manifest[ofs + 2]); // headersize
903 encode_uint32(56, &p_manifest[ofs + 4]); // size
904 encode_uint32(0, &p_manifest[ofs + 8]); // lineno
905 encode_uint32(-1, &p_manifest[ofs + 12]); // comment
906 encode_uint32(-1, &p_manifest[ofs + 16]); // ns
907 encode_uint32(attr_uses_permission_string, &p_manifest[ofs + 20]); // name
908 encode_uint16(20, &p_manifest[ofs + 24]); // attr_start
909 encode_uint16(20, &p_manifest[ofs + 26]); // attr_size
910 encode_uint16(1, &p_manifest[ofs + 28]); // num_attrs
911 encode_uint16(0, &p_manifest[ofs + 30]); // id_index
912 encode_uint16(0, &p_manifest[ofs + 32]); // class_index
913 encode_uint16(0, &p_manifest[ofs + 34]); // style_index
914
915 // attribute
916 encode_uint32(ns_android_string, &p_manifest[ofs + 36]); // ns
917 encode_uint32(attr_name_string, &p_manifest[ofs + 40]); // 'name'
918 encode_uint32(perm_string, &p_manifest[ofs + 44]); // raw_value
919 encode_uint16(8, &p_manifest[ofs + 48]); // typedvalue_size
920 p_manifest[ofs + 50] = 0; // typedvalue_always0
921 p_manifest[ofs + 51] = 0x03; // typedvalue_type (string)
922 encode_uint32(perm_string, &p_manifest[ofs + 52]); // typedvalue reference
923
924 ofs += 56;
925
926 // end tag
927 encode_uint16(0x103, &p_manifest[ofs]); // type
928 encode_uint16(16, &p_manifest[ofs + 2]); // headersize
929 encode_uint32(24, &p_manifest[ofs + 4]); // size
930 encode_uint32(0, &p_manifest[ofs + 8]); // lineno
931 encode_uint32(-1, &p_manifest[ofs + 12]); // comment
932 encode_uint32(-1, &p_manifest[ofs + 16]); // ns
933 encode_uint32(attr_uses_permission_string, &p_manifest[ofs + 20]); // name
934
935 ofs += 24;
936 }
937
938 // copy footer back in
939 memcpy(&p_manifest[ofs], manifest_end.ptr(), manifest_end.size());
940 }
941 } break;
942 }
943 //printf("chunk %x: size: %d\n",chunk,size);
944
945 ofs += size;
946 }
947
948 //printf("end\n");
949
950 //create new andriodmanifest binary
951
952 Vector<uint8_t> ret;
953 ret.resize(string_table_begins + string_table.size() * 4);
954
955 for (int i = 0; i < string_table_begins; i++) {
956
957 ret[i] = p_manifest[i];
958 }
959
960 ofs = 0;
961 for (int i = 0; i < string_table.size(); i++) {
962
963 encode_uint32(ofs, &ret[string_table_begins + i * 4]);
964 ofs += string_table[i].length() * 2 + 2 + 2;
965 }
966
967 ret.resize(ret.size() + ofs);
968 string_data_offset = ret.size() - ofs;
969 uint8_t *chars = &ret[string_data_offset];
970 for (int i = 0; i < string_table.size(); i++) {
971
972 String s = string_table[i];
973 encode_uint16(s.length(), chars);
974 chars += 2;
975 for (int j = 0; j < s.length(); j++) {
976 encode_uint16(s[j], chars);
977 chars += 2;
978 }
979 encode_uint16(0, chars);
980 chars += 2;
981 }
982
983 for (int i = 0; i < stable_extra.size(); i++) {
984 ret.push_back(stable_extra[i]);
985 }
986
987 //pad
988 while (ret.size() % 4)
989 ret.push_back(0);
990
991 uint32_t new_stable_end = ret.size();
992
993 uint32_t extra = (p_manifest.size() - string_table_ends);
994 ret.resize(new_stable_end + extra);
995 for (int i = 0; i < extra; i++)
996 ret[new_stable_end + i] = p_manifest[string_table_ends + i];
997
998 while (ret.size() % 4)
999 ret.push_back(0);
1000 encode_uint32(ret.size(), &ret[4]); //update new file size
1001
1002 encode_uint32(new_stable_end - 8, &ret[12]); //update new string table size
1003 encode_uint32(string_table.size(), &ret[16]); //update new number of strings
1004 encode_uint32(string_data_offset - 8, &ret[28]); //update new string data offset
1005 //print_line("file size: "+itos(ret.size()));
1006
1007 p_manifest = ret;
1008
1009 #if 0
1010 uint32_t header[9];
1011 for(int i=0;i<9;i++) {
1012 header[i]=decode_uint32(&p_manifest[i*4]);
1013 }
1014
1015 //print_line("STO: "+itos(header[3]));
1016 uint32_t st_offset=9*4;
1017 //ERR_FAIL_COND(header[3]!=0x24)
1018 uint32_t string_count=header[4];
1019
1020
1021 string_table.resize(string_count);
1022
1023 for(int i=0;i<string_count;i++) {
1024
1025 uint32_t string_at = decode_uint32(&p_manifest[st_offset+i*4]);
1026 string_at+=st_offset+string_count*4;
1027 uint32_t len = decode_uint16(&p_manifest[string_at]);
1028 Vector<CharType> ucstring;
1029 ucstring.resize(len+1);
1030 for(int j=0;j<len;j++) {
1031 uint16_t c=decode_uint16(&p_manifest[string_at+2+2*j]);
1032 ucstring[j]=c;
1033 }
1034 ucstring[len]=0;
1035 string_table[i]=ucstring.ptr();
1036 }
1037
1038 #endif
1039 }
1040
save_apk_file(void * p_userdata,const String & p_path,const Vector<uint8_t> & p_data,int p_file,int p_total)1041 Error EditorExportPlatformAndroid::save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
1042
1043 APKExportData *ed = (APKExportData *)p_userdata;
1044 String dst_path = p_path;
1045 dst_path = dst_path.replace_first("res://", "assets/");
1046
1047 zipOpenNewFileInZip(ed->apk,
1048 dst_path.utf8().get_data(),
1049 NULL,
1050 NULL,
1051 0,
1052 NULL,
1053 0,
1054 NULL,
1055 _should_compress_asset(p_path, p_data) ? Z_DEFLATED : 0,
1056 Z_DEFAULT_COMPRESSION);
1057
1058 zipWriteInFileInZip(ed->apk, p_data.ptr(), p_data.size());
1059 zipCloseFileInZip(ed->apk);
1060 ed->ep->step("File: " + p_path, 3 + p_file * 100 / p_total);
1061 return OK;
1062 }
1063
_should_compress_asset(const String & p_path,const Vector<uint8_t> & p_data)1064 bool EditorExportPlatformAndroid::_should_compress_asset(const String &p_path, const Vector<uint8_t> &p_data) {
1065
1066 /*
1067 * By not compressing files with little or not benefit in doing so,
1068 * a performance gain is expected at runtime. Moreover, if the APK is
1069 * zip-aligned, assets stored as they are can be efficiently read by
1070 * Android by memory-mapping them.
1071 */
1072
1073 // -- Unconditional uncompress to mimic AAPT plus some other
1074
1075 static const char *unconditional_compress_ext[] = {
1076 // From https://github.com/android/platform_frameworks_base/blob/master/tools/aapt/Package.cpp
1077 // These formats are already compressed, or don't compress well:
1078 ".jpg", ".jpeg", ".png", ".gif",
1079 ".wav", ".mp2", ".mp3", ".ogg", ".aac",
1080 ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
1081 ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
1082 ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
1083 ".amr", ".awb", ".wma", ".wmv",
1084 // Godot-specific:
1085 ".webp", // Same reasoning as .png
1086 ".cfb", // Don't let small config files slow-down startup
1087 // Trailer for easier processing
1088 NULL
1089 };
1090
1091 for (const char **ext = unconditional_compress_ext; *ext; ++ext) {
1092 if (p_path.to_lower().ends_with(String(*ext))) {
1093 return false;
1094 }
1095 }
1096
1097 // -- Compressed resource?
1098
1099 if (p_data.size() >= 4 && p_data[0] == 'R' && p_data[1] == 'S' && p_data[2] == 'C' && p_data[3] == 'C') {
1100 // Already compressed
1101 return false;
1102 }
1103
1104 // --- TODO: Decide on texture resources according to their image compression setting
1105
1106 return true;
1107 }
1108
export_project(const String & p_path,bool p_debug,int p_flags)1109 Error EditorExportPlatformAndroid::export_project(const String &p_path, bool p_debug, int p_flags) {
1110
1111 String src_apk;
1112
1113 EditorProgress ep("export", "Exporting for Android", 105);
1114
1115 if (p_debug)
1116 src_apk = custom_debug_package;
1117 else
1118 src_apk = custom_release_package;
1119
1120 if (src_apk == "") {
1121 String err;
1122 if (p_debug) {
1123 src_apk = find_export_template("android_debug.apk", &err);
1124 } else {
1125 src_apk = find_export_template("android_release.apk", &err);
1126 }
1127 if (src_apk == "") {
1128 EditorNode::add_io_error(err);
1129 return ERR_FILE_NOT_FOUND;
1130 }
1131 }
1132
1133 FileAccess *src_f = NULL;
1134 zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
1135
1136 ep.step("Creating APK", 0);
1137
1138 unzFile pkg = unzOpen2(src_apk.utf8().get_data(), &io);
1139 if (!pkg) {
1140
1141 EditorNode::add_io_error("Could not find template APK to export:\n" + src_apk);
1142 return ERR_FILE_NOT_FOUND;
1143 }
1144
1145 ERR_FAIL_COND_V(!pkg, ERR_CANT_OPEN);
1146 int ret = unzGoToFirstFile(pkg);
1147
1148 zlib_filefunc_def io2 = io;
1149 FileAccess *dst_f = NULL;
1150 io2.opaque = &dst_f;
1151 String unaligned_path = EditorSettings::get_singleton()->get_settings_path() + "/tmp/tmpexport-unaligned.apk";
1152 zipFile unaligned_apk = zipOpen2(unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2);
1153
1154 while (ret == UNZ_OK) {
1155
1156 //get filename
1157 unz_file_info info;
1158 char fname[16384];
1159 ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0);
1160
1161 bool skip = false;
1162
1163 String file = fname;
1164
1165 Vector<uint8_t> data;
1166 data.resize(info.uncompressed_size);
1167
1168 //read
1169 unzOpenCurrentFile(pkg);
1170 unzReadCurrentFile(pkg, data.ptr(), data.size());
1171 unzCloseCurrentFile(pkg);
1172
1173 //write
1174
1175 if (file == "AndroidManifest.xml") {
1176
1177 _fix_manifest(data, p_flags & (EXPORT_DUMB_CLIENT | EXPORT_REMOTE_DEBUG));
1178 }
1179
1180 if (file == "resources.arsc") {
1181
1182 _fix_resources(data);
1183 }
1184
1185 if (file == "res/drawable/icon.png") {
1186 bool found = false;
1187
1188 if (this->icon != "" && this->icon.ends_with(".png")) {
1189
1190 FileAccess *f = FileAccess::open(this->icon, FileAccess::READ);
1191 if (f) {
1192
1193 data.resize(f->get_len());
1194 f->get_buffer(data.ptr(), data.size());
1195 memdelete(f);
1196 found = true;
1197 }
1198 }
1199
1200 if (!found) {
1201
1202 String appicon = Globals::get_singleton()->get("application/icon");
1203 if (appicon != "" && appicon.ends_with(".png")) {
1204 FileAccess *f = FileAccess::open(appicon, FileAccess::READ);
1205 if (f) {
1206 data.resize(f->get_len());
1207 f->get_buffer(data.ptr(), data.size());
1208 memdelete(f);
1209 }
1210 }
1211 }
1212 }
1213
1214 if (file == "lib/x86/libgodot_android.so" && !export_x86) {
1215 skip = true;
1216 }
1217
1218 if (file == "lib/x86_64/libgodot_android.so" && !export_x86_64) {
1219 skip = true;
1220 }
1221
1222 if (file.match("lib/armeabi*/libgodot_android.so") && !export_arm) {
1223 skip = true;
1224 }
1225
1226 if (file.match("lib/arm64*/libgodot_android.so") && !export_arm64) {
1227 skip = true;
1228 }
1229
1230 if (file.begins_with("META-INF") && _signed) {
1231 skip = true;
1232 }
1233
1234 print_line("ADDING: " + file);
1235
1236 if (!skip) {
1237
1238 // Respect decision on compression made by AAPT for the export template
1239 const bool uncompressed = info.compression_method == 0;
1240
1241 zipOpenNewFileInZip(unaligned_apk,
1242 file.utf8().get_data(),
1243 NULL,
1244 NULL,
1245 0,
1246 NULL,
1247 0,
1248 NULL,
1249 uncompressed ? 0 : Z_DEFLATED,
1250 Z_DEFAULT_COMPRESSION);
1251
1252 zipWriteInFileInZip(unaligned_apk, data.ptr(), data.size());
1253 zipCloseFileInZip(unaligned_apk);
1254 }
1255
1256 ret = unzGoToNextFile(pkg);
1257 }
1258
1259 ep.step("Adding Files..", 1);
1260 Error err = OK;
1261 Vector<String> cl = cmdline.strip_edges().split(" ");
1262 for (int i = 0; i < cl.size(); i++) {
1263 if (cl[i].strip_edges().length() == 0) {
1264 cl.remove(i);
1265 i--;
1266 }
1267 }
1268
1269 gen_export_flags(cl, p_flags);
1270
1271 if (p_flags & EXPORT_DUMB_CLIENT) {
1272
1273 /*String host = EditorSettings::get_singleton()->get("file_server/host");
1274 int port = EditorSettings::get_singleton()->get("file_server/post");
1275 String passwd = EditorSettings::get_singleton()->get("file_server/password");
1276 cl.push_back("-rfs");
1277 cl.push_back(host+":"+itos(port));
1278 if (passwd!="") {
1279 cl.push_back("-rfs_pass");
1280 cl.push_back(passwd);
1281 }*/
1282
1283 } else {
1284 //all files
1285
1286 if (apk_expansion) {
1287
1288 String apkfname = "main." + itos(version_code) + "." + get_package_name() + ".obb";
1289 String fullpath = p_path.get_base_dir().plus_file(apkfname);
1290 FileAccess *pf = FileAccess::open(fullpath, FileAccess::WRITE);
1291 if (!pf) {
1292 EditorNode::add_io_error("Could not write expansion package file: " + apkfname);
1293 return OK;
1294 }
1295 err = save_pack(pf);
1296 memdelete(pf);
1297
1298 cl.push_back("-use_apk_expansion");
1299 cl.push_back("-apk_expansion_md5");
1300 cl.push_back(FileAccess::get_md5(fullpath));
1301 cl.push_back("-apk_expansion_key");
1302 cl.push_back(apk_expansion_pkey.strip_edges());
1303
1304 } else {
1305
1306 APKExportData ed;
1307 ed.ep = &ep;
1308 ed.apk = unaligned_apk;
1309
1310 err = export_project_files(save_apk_file, &ed, false);
1311 }
1312 }
1313
1314 if (use_32_fb)
1315 cl.push_back("-use_depth_32");
1316
1317 if (immersive)
1318 cl.push_back("-use_immersive");
1319
1320 if (cl.size()) {
1321 //add comandline
1322 Vector<uint8_t> clf;
1323 clf.resize(4);
1324 encode_uint32(cl.size(), &clf[0]);
1325 for (int i = 0; i < cl.size(); i++) {
1326
1327 CharString txt = cl[i].utf8();
1328 int base = clf.size();
1329 clf.resize(base + 4 + txt.length());
1330 encode_uint32(txt.length(), &clf[base]);
1331 copymem(&clf[base + 4], txt.ptr(), txt.length());
1332 print_line(itos(i) + " param: " + cl[i]);
1333 }
1334
1335 zipOpenNewFileInZip(unaligned_apk,
1336 "assets/_cl_",
1337 NULL,
1338 NULL,
1339 0,
1340 NULL,
1341 0,
1342 NULL,
1343 0, // No compress (little size gain and potentially slower startup)
1344 Z_DEFAULT_COMPRESSION);
1345
1346 zipWriteInFileInZip(unaligned_apk, clf.ptr(), clf.size());
1347 zipCloseFileInZip(unaligned_apk);
1348 }
1349
1350 zipClose(unaligned_apk, NULL);
1351 unzClose(pkg);
1352
1353 if (err) {
1354 return err;
1355 }
1356
1357 if (_signed) {
1358
1359 String jarsigner = EditorSettings::get_singleton()->get("android/jarsigner");
1360 if (!FileAccess::exists(jarsigner)) {
1361 EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the editor settings.\nResulting apk is unsigned.");
1362 return OK;
1363 }
1364
1365 String keystore;
1366 String password;
1367 String user;
1368 if (p_debug) {
1369 keystore = EditorSettings::get_singleton()->get("android/debug_keystore");
1370 password = EditorSettings::get_singleton()->get("android/debug_keystore_pass");
1371 user = EditorSettings::get_singleton()->get("android/debug_keystore_user");
1372
1373 ep.step("Signing Debug APK..", 103);
1374
1375 } else {
1376 keystore = release_keystore;
1377 password = release_password;
1378 user = release_username;
1379
1380 ep.step("Signing Release APK..", 103);
1381 }
1382
1383 if (!FileAccess::exists(keystore)) {
1384 EditorNode::add_io_error("Could not find keystore, unable to export.");
1385 return ERR_FILE_CANT_OPEN;
1386 }
1387
1388 List<String> args;
1389 args.push_back("-digestalg");
1390 args.push_back("SHA-256");
1391 args.push_back("-sigalg");
1392 args.push_back("SHA256withRSA");
1393 String tsa_url = EditorSettings::get_singleton()->get("android/timestamping_authority_url");
1394 if (tsa_url != "") {
1395 args.push_back("-tsa");
1396 args.push_back(tsa_url);
1397 }
1398 args.push_back("-verbose");
1399 args.push_back("-keystore");
1400 args.push_back(keystore);
1401 args.push_back("-storepass");
1402 args.push_back(password);
1403 args.push_back(unaligned_path);
1404 args.push_back(user);
1405 int retval;
1406 int err = OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
1407 if (retval) {
1408 EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval));
1409 return ERR_CANT_CREATE;
1410 }
1411
1412 ep.step("Verifying APK..", 104);
1413
1414 args.clear();
1415 args.push_back("-verify");
1416 args.push_back(unaligned_path);
1417 args.push_back("-verbose");
1418
1419 err = OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
1420 if (retval) {
1421 EditorNode::add_io_error("'jarsigner' verification of APK failed. Make sure to use jarsigner from Java 6.");
1422 return ERR_CANT_CREATE;
1423 }
1424 }
1425
1426 // Let's zip-align (must be done after signing)
1427
1428 static const int ZIP_ALIGNMENT = 4;
1429
1430 ep.step("Aligning APK..", 105);
1431
1432 unzFile tmp_unaligned = unzOpen2(unaligned_path.utf8().get_data(), &io);
1433 if (!tmp_unaligned) {
1434
1435 EditorNode::add_io_error("Could not find temp unaligned APK.");
1436 return ERR_FILE_NOT_FOUND;
1437 }
1438
1439 ERR_FAIL_COND_V(!tmp_unaligned, ERR_CANT_OPEN);
1440 ret = unzGoToFirstFile(tmp_unaligned);
1441
1442 io2 = io;
1443 dst_f = NULL;
1444 io2.opaque = &dst_f;
1445 zipFile final_apk = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2);
1446
1447 // Take files from the unaligned APK and write them out to the aligned one
1448 // in raw mode, i.e. not uncompressing and recompressing, aligning them as needed,
1449 // following what is done in https://github.com/android/platform_build/blob/master/tools/zipalign/ZipAlign.cpp
1450 int bias = 0;
1451 while (ret == UNZ_OK) {
1452
1453 unz_file_info info;
1454 memset(&info, 0, sizeof(info));
1455
1456 char fname[16384];
1457 char extra[16384];
1458 ret = unzGetCurrentFileInfo(tmp_unaligned, &info, fname, 16384, extra, 16384 - ZIP_ALIGNMENT, NULL, 0);
1459
1460 String file = fname;
1461
1462 Vector<uint8_t> data;
1463 data.resize(info.compressed_size);
1464
1465 // read
1466 int method, level;
1467 unzOpenCurrentFile2(tmp_unaligned, &method, &level, 1); // raw read
1468 long file_offset = unzGetCurrentFileZStreamPos64(tmp_unaligned);
1469 unzReadCurrentFile(tmp_unaligned, data.ptr(), data.size());
1470 unzCloseCurrentFile(tmp_unaligned);
1471
1472 // align
1473 int padding = 0;
1474 if (!info.compression_method) {
1475 // Uncompressed file => Align
1476 long new_offset = file_offset + bias;
1477 padding = (ZIP_ALIGNMENT - (new_offset % ZIP_ALIGNMENT)) % ZIP_ALIGNMENT;
1478 }
1479
1480 memset(extra + info.size_file_extra, 0, padding);
1481
1482 // write
1483 zipOpenNewFileInZip2(final_apk,
1484 file.utf8().get_data(),
1485 NULL,
1486 extra,
1487 info.size_file_extra + padding,
1488 NULL,
1489 0,
1490 NULL,
1491 method,
1492 level,
1493 1); // raw write
1494 zipWriteInFileInZip(final_apk, data.ptr(), data.size());
1495 zipCloseFileInZipRaw(final_apk, info.uncompressed_size, info.crc);
1496
1497 bias += padding;
1498
1499 ret = unzGoToNextFile(tmp_unaligned);
1500 }
1501
1502 zipClose(final_apk, NULL);
1503 unzClose(tmp_unaligned);
1504
1505 if (err) {
1506 return err;
1507 }
1508
1509 return OK;
1510 }
1511
poll_devices()1512 bool EditorExportPlatformAndroid::poll_devices() {
1513
1514 bool dc = devices_changed;
1515 devices_changed = false;
1516 return dc;
1517 }
1518
get_device_count() const1519 int EditorExportPlatformAndroid::get_device_count() const {
1520
1521 device_lock->lock();
1522 int dc = devices.size();
1523 device_lock->unlock();
1524
1525 return dc;
1526 }
1527
get_device_name(int p_device) const1528 String EditorExportPlatformAndroid::get_device_name(int p_device) const {
1529
1530 ERR_FAIL_INDEX_V(p_device, devices.size(), "");
1531 device_lock->lock();
1532 String s = devices[p_device].name;
1533 device_lock->unlock();
1534 return s;
1535 }
1536
get_device_info(int p_device) const1537 String EditorExportPlatformAndroid::get_device_info(int p_device) const {
1538
1539 ERR_FAIL_INDEX_V(p_device, devices.size(), "");
1540 device_lock->lock();
1541 String s = devices[p_device].description;
1542 device_lock->unlock();
1543 return s;
1544 }
1545
_device_poll_thread(void * ud)1546 void EditorExportPlatformAndroid::_device_poll_thread(void *ud) {
1547
1548 EditorExportPlatformAndroid *ea = (EditorExportPlatformAndroid *)ud;
1549
1550 while (!ea->quit_request) {
1551
1552 String adb = EditorSettings::get_singleton()->get("android/adb");
1553 if (FileAccess::exists(adb)) {
1554
1555 String devices;
1556 List<String> args;
1557 args.push_back("devices");
1558 int ec;
1559 Error err = OS::get_singleton()->execute(adb, args, true, NULL, &devices, &ec);
1560 Vector<String> ds = devices.split("\n");
1561 Vector<String> ldevices;
1562 for (int i = 1; i < ds.size(); i++) {
1563
1564 String d = ds[i];
1565 int dpos = d.find("device");
1566 if (dpos == -1)
1567 continue;
1568 d = d.substr(0, dpos).strip_edges();
1569 // print_line("found devuce: "+d);
1570 ldevices.push_back(d);
1571 }
1572
1573 ea->device_lock->lock();
1574
1575 bool different = false;
1576
1577 if (devices.size() != ldevices.size()) {
1578
1579 different = true;
1580 } else {
1581
1582 for (int i = 0; i < ea->devices.size(); i++) {
1583
1584 if (ea->devices[i].id != ldevices[i]) {
1585 different = true;
1586 break;
1587 }
1588 }
1589 }
1590
1591 if (different) {
1592
1593 Vector<Device> ndevices;
1594
1595 for (int i = 0; i < ldevices.size(); i++) {
1596
1597 Device d;
1598 d.id = ldevices[i];
1599 for (int j = 0; j < ea->devices.size(); j++) {
1600 if (ea->devices[j].id == ldevices[i]) {
1601 d.description = ea->devices[j].description;
1602 d.name = ea->devices[j].name;
1603 d.api_level = ea->devices[j].api_level;
1604 }
1605 }
1606
1607 if (d.description == "") {
1608 //in the oven, request!
1609 args.clear();
1610 args.push_back("-s");
1611 args.push_back(d.id);
1612 args.push_back("shell");
1613 args.push_back("getprop");
1614 int ec;
1615 String dp;
1616
1617 Error err = OS::get_singleton()->execute(adb, args, true, NULL, &dp, &ec);
1618
1619 Vector<String> props = dp.split("\n");
1620 String vendor;
1621 String device;
1622 d.description + "Device ID: " + d.id + "\n";
1623 d.api_level = 0;
1624 for (int j = 0; j < props.size(); j++) {
1625
1626 // got information by `shell cat /system/build.prop` before and its format is "property=value"
1627 // it's now changed to use `shell getporp` because of permission issue with Android 8.0 and above
1628 // its format is "[property]: [value]" so changed it as like build.prop
1629 String p = props[j];
1630 p = p.replace("]: ", "=");
1631 p = p.replace("[", "");
1632 p = p.replace("]", "");
1633
1634 if (p.begins_with("ro.product.model=")) {
1635 device = p.get_slice("=", 1).strip_edges();
1636 } else if (p.begins_with("ro.product.brand=")) {
1637 vendor = p.get_slice("=", 1).strip_edges().capitalize();
1638 } else if (p.begins_with("ro.build.display.id=")) {
1639 d.description += "Build: " + p.get_slice("=", 1).strip_edges() + "\n";
1640 } else if (p.begins_with("ro.build.version.release=")) {
1641 d.description += "Release: " + p.get_slice("=", 1).strip_edges() + "\n";
1642 } else if (p.begins_with("ro.build.version.sdk=")) {
1643 d.api_level = p.get_slice("=", 1).to_int();
1644 } else if (p.begins_with("ro.product.cpu.abi=")) {
1645 d.description += "CPU: " + p.get_slice("=", 1).strip_edges() + "\n";
1646 } else if (p.begins_with("ro.product.manufacturer=")) {
1647 d.description += "Manufacturer: " + p.get_slice("=", 1).strip_edges() + "\n";
1648 } else if (p.begins_with("ro.board.platform=")) {
1649 d.description += "Chipset: " + p.get_slice("=", 1).strip_edges() + "\n";
1650 } else if (p.begins_with("ro.opengles.version=")) {
1651 uint32_t opengl = p.get_slice("=", 1).to_int();
1652 d.description += "OpenGL: " + itos(opengl >> 16) + "." + itos((opengl >> 8) & 0xFF) + "." + itos((opengl)&0xFF) + "\n";
1653 }
1654 }
1655
1656 d.name = vendor + " " + device;
1657 // print_line("name: "+d.name);
1658 // print_line("description: "+d.description);
1659 }
1660
1661 ndevices.push_back(d);
1662 }
1663
1664 ea->devices = ndevices;
1665 ea->devices_changed = true;
1666 }
1667
1668 ea->device_lock->unlock();
1669 }
1670
1671 uint64_t wait = 3000000;
1672 uint64_t time = OS::get_singleton()->get_ticks_usec();
1673 while (OS::get_singleton()->get_ticks_usec() - time < wait) {
1674 OS::get_singleton()->delay_usec(1000);
1675 if (ea->quit_request)
1676 break;
1677 }
1678 }
1679
1680 if (EditorSettings::get_singleton()->get("android/shutdown_adb_on_exit")) {
1681 String adb = EditorSettings::get_singleton()->get("android/adb");
1682 if (!FileAccess::exists(adb)) {
1683 return; //adb not configured
1684 }
1685
1686 List<String> args;
1687 args.push_back("kill-server");
1688 OS::get_singleton()->execute(adb, args, true);
1689 };
1690 }
1691
run(int p_device,int p_flags)1692 Error EditorExportPlatformAndroid::run(int p_device, int p_flags) {
1693
1694 ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER);
1695 device_lock->lock();
1696
1697 EditorProgress ep("run", "Running on " + devices[p_device].name, 3);
1698
1699 String adb = EditorSettings::get_singleton()->get("android/adb");
1700 if (adb == "") {
1701
1702 EditorNode::add_io_error("ADB executable not configured in settings, can't run.");
1703 device_lock->unlock();
1704 return ERR_UNCONFIGURED;
1705 }
1706
1707 //export_temp
1708 ep.step("Exporting APK", 0);
1709
1710 const bool use_remote = (p_flags & EXPORT_REMOTE_DEBUG) || (p_flags & EXPORT_DUMB_CLIENT);
1711 const bool use_reverse = devices[p_device].api_level >= 21;
1712
1713 if (use_reverse)
1714 p_flags |= EXPORT_REMOTE_DEBUG_LOCALHOST;
1715
1716 String export_to = EditorSettings::get_singleton()->get_settings_path() + "/tmp/tmpexport.apk";
1717 Error err = export_project(export_to, true, p_flags);
1718 if (err) {
1719 device_lock->unlock();
1720 return err;
1721 }
1722
1723 List<String> args;
1724 int rv;
1725
1726 if (remove_prev) {
1727 ep.step("Uninstalling..", 1);
1728
1729 print_line("Uninstalling previous version: " + devices[p_device].name);
1730
1731 args.push_back("-s");
1732 args.push_back(devices[p_device].id);
1733 args.push_back("uninstall");
1734 args.push_back(get_package_name());
1735
1736 err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv);
1737 #if 0
1738 if (err || rv!=0) {
1739 EditorNode::add_io_error("Could not install to device.");
1740 device_lock->unlock();
1741 return ERR_CANT_CREATE;
1742 }
1743 #endif
1744 }
1745
1746 print_line("Installing into device (please wait..): " + devices[p_device].name);
1747 ep.step("Installing to Device (please wait..)..", 2);
1748
1749 args.clear();
1750 args.push_back("-s");
1751 args.push_back(devices[p_device].id);
1752 args.push_back("install");
1753 args.push_back("-r");
1754 args.push_back(export_to);
1755
1756 err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv);
1757 if (err || rv != 0) {
1758 EditorNode::add_io_error("Could not install to device.");
1759 device_lock->unlock();
1760 return ERR_CANT_CREATE;
1761 }
1762
1763 if (use_remote) {
1764 if (use_reverse) {
1765
1766 static const char *const msg = "** Device API >= 21; debugging over USB **";
1767 EditorNode::get_singleton()->get_log()->add_message(msg);
1768 print_line(String(msg).to_upper());
1769
1770 args.clear();
1771 args.push_back("-s");
1772 args.push_back(devices[p_device].id);
1773 args.push_back("reverse");
1774 args.push_back("--remove-all");
1775 err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv);
1776
1777 if (p_flags & EXPORT_REMOTE_DEBUG) {
1778
1779 int dbg_port = (int)EditorSettings::get_singleton()->get("network/debug_port");
1780 args.clear();
1781 args.push_back("-s");
1782 args.push_back(devices[p_device].id);
1783 args.push_back("reverse");
1784 args.push_back("tcp:" + itos(dbg_port));
1785 args.push_back("tcp:" + itos(dbg_port));
1786
1787 err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv);
1788 print_line("Reverse result: " + itos(rv));
1789 }
1790
1791 if (p_flags & EXPORT_DUMB_CLIENT) {
1792
1793 int fs_port = EditorSettings::get_singleton()->get("file_server/port");
1794
1795 args.clear();
1796 args.push_back("-s");
1797 args.push_back(devices[p_device].id);
1798 args.push_back("reverse");
1799 args.push_back("tcp:" + itos(fs_port));
1800 args.push_back("tcp:" + itos(fs_port));
1801
1802 err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv);
1803 print_line("Reverse result2: " + itos(rv));
1804 }
1805 } else {
1806
1807 static const char *const msg = "** Device API < 21; debugging over Wi-Fi **";
1808 EditorNode::get_singleton()->get_log()->add_message(msg);
1809 print_line(String(msg).to_upper());
1810 }
1811 }
1812
1813 ep.step("Running on Device..", 3);
1814 args.clear();
1815 args.push_back("-s");
1816 args.push_back(devices[p_device].id);
1817 args.push_back("shell");
1818 args.push_back("am");
1819 args.push_back("start");
1820 if (bool(EDITOR_DEF("android/force_system_user", false)) && devices[p_device].api_level >= 17) { // Multi-user introduced in Android 17
1821 args.push_back("--user");
1822 args.push_back("0");
1823 }
1824 args.push_back("-a");
1825 args.push_back("android.intent.action.MAIN");
1826 args.push_back("-n");
1827 args.push_back(get_package_name() + "/org.godotengine.godot.Godot");
1828
1829 err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv);
1830 if (err || rv != 0) {
1831 EditorNode::add_io_error("Could not execute on device.");
1832 device_lock->unlock();
1833 return ERR_CANT_CREATE;
1834 }
1835 device_lock->unlock();
1836 return OK;
1837 }
1838
get_package_name()1839 String EditorExportPlatformAndroid::get_package_name() {
1840
1841 String pname = package;
1842 String basename = Globals::get_singleton()->get("application/name");
1843 basename = basename.to_lower();
1844
1845 String name;
1846 bool first = true;
1847 for (int i = 0; i < basename.length(); i++) {
1848 CharType c = basename[i];
1849 if (c >= '0' && c <= '9' && first) {
1850 continue;
1851 }
1852 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
1853 name += String::chr(c);
1854 first = false;
1855 }
1856 }
1857 if (name == "")
1858 name = "noname";
1859
1860 pname = pname.replace("$genname", name);
1861 return pname;
1862 }
1863
EditorExportPlatformAndroid()1864 EditorExportPlatformAndroid::EditorExportPlatformAndroid() {
1865
1866 version_code = 1;
1867 version_name = "1.0";
1868 package = "org.godotengine.$genname";
1869 name = "";
1870 _signed = true;
1871 apk_expansion = false;
1872 device_lock = Mutex::create();
1873 quit_request = false;
1874 orientation = 0;
1875 remove_prev = true;
1876 use_32_fb = true;
1877 immersive = true;
1878
1879 export_arm = true;
1880 export_arm64 = true;
1881 export_x86 = false;
1882 export_x86_64 = false;
1883
1884 device_thread = Thread::create(_device_poll_thread, this);
1885 devices_changed = true;
1886
1887 Image img(_android_logo);
1888 logo = Ref<ImageTexture>(memnew(ImageTexture));
1889 logo->create_from_image(img);
1890
1891 for (int i = 0; i < 4; i++)
1892 screen_support[i] = true;
1893 }
1894
can_export(String * r_error) const1895 bool EditorExportPlatformAndroid::can_export(String *r_error) const {
1896
1897 bool valid = true;
1898 String adb = EditorSettings::get_singleton()->get("android/adb");
1899 String err;
1900
1901 if (!FileAccess::exists(adb)) {
1902
1903 valid = false;
1904 err += "ADB executable not configured in editor settings.\n";
1905 }
1906
1907 String js = EditorSettings::get_singleton()->get("android/jarsigner");
1908
1909 if (!FileAccess::exists(js)) {
1910
1911 valid = false;
1912 err += "OpenJDK 6 jarsigner not configured in editor settings.\n";
1913 }
1914
1915 String dk = EditorSettings::get_singleton()->get("android/debug_keystore");
1916
1917 if (!FileAccess::exists(dk)) {
1918
1919 valid = false;
1920 err += "Debug Keystore not configured in editor settings.\n";
1921 }
1922
1923 if (!exists_export_template("android_debug.apk") || !exists_export_template("android_release.apk")) {
1924 valid = false;
1925 err += "No export templates found.\nDownload and install export templates.\n";
1926 }
1927
1928 if (custom_debug_package != "" && !FileAccess::exists(custom_debug_package)) {
1929 valid = false;
1930 err += "Custom debug package not found.\n";
1931 }
1932
1933 if (custom_release_package != "" && !FileAccess::exists(custom_release_package)) {
1934 valid = false;
1935 err += "Custom release package not found.\n";
1936 }
1937
1938 if (apk_expansion) {
1939
1940 //if (apk_expansion_salt=="") {
1941 // valid=false;
1942 // err+="Invalid SALT for apk expansion.\n";
1943 //}
1944 if (apk_expansion_pkey == "") {
1945 valid = false;
1946 err += "Invalid public key for apk expansion.\n";
1947 }
1948 }
1949
1950 if (r_error)
1951 *r_error = err;
1952
1953 return valid;
1954 }
1955
~EditorExportPlatformAndroid()1956 EditorExportPlatformAndroid::~EditorExportPlatformAndroid() {
1957
1958 quit_request = true;
1959 Thread::wait_to_finish(device_thread);
1960 memdelete(device_lock);
1961 memdelete(device_thread);
1962 }
1963
register_android_exporter()1964 void register_android_exporter() {
1965
1966 String exe_ext = OS::get_singleton()->get_name() == "Windows" ? "exe" : "";
1967 EDITOR_DEF("android/adb", "");
1968 EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "android/adb", PROPERTY_HINT_GLOBAL_FILE, exe_ext));
1969 EDITOR_DEF("android/jarsigner", "");
1970 EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "android/jarsigner", PROPERTY_HINT_GLOBAL_FILE, exe_ext));
1971 EDITOR_DEF("android/debug_keystore", "");
1972 EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "keystore"));
1973 EDITOR_DEF("android/debug_keystore_user", "androiddebugkey");
1974 EDITOR_DEF("android/debug_keystore_pass", "android");
1975 //EDITOR_DEF("android/release_keystore","");
1976 //EDITOR_DEF("android/release_username","");
1977 //EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING,"android/release_keystore",PROPERTY_HINT_GLOBAL_FILE,"*.keystore"));
1978 EDITOR_DEF("android/force_system_user", false);
1979 EDITOR_DEF("android/timestamping_authority_url", "");
1980 EDITOR_DEF("android/shutdown_adb_on_exit", true);
1981
1982 Ref<EditorExportPlatformAndroid> exporter = Ref<EditorExportPlatformAndroid>(memnew(EditorExportPlatformAndroid));
1983 EditorImportExport::get_singleton()->add_export_platform(exporter);
1984 }
1985